GraphQL Java - Scalars

GraphQL中的Scalar

Scalar(原子类型)

在GraphQL类型系统中,类型树的叶子节点成为Scalar。一旦访问到了Scalar类型的数据,就无法在该类型基础上进一步访问其下的类型层次结构。Scalar类型意味着该类型的值无法再细分。

在GraphQL规范中,要求其所有实现都必须具有如下Scalar类型:

  • String类型(GraphQLString):UTF-8编码的字符序列
  • Boolean类型(GraphQLBoolean):true或false
  • Int类型(GraphQLInt):32位的有符号类型
  • Float类型(GraphQLFloat):单精度浮点类型
  • ID类型(GraphQLID):唯一标识符类型(可以被序列化为String)。但ID类型的数据可读性较差。

GraphQL - Java补充添加了如下几种额外类型:

  • Long类型(GraphQLLong):基于java.lang.Long的scalar类型
  • Short类型(GraphQLShort):基于java.lang.Short的scalar类型
  • Byte类型(GraphQLByte):基于java.lang.Byte的scalar类型
  • BigDecimal类型(GraphQLBigDecimal):基于java.math.BigDecimal的scalar类型
  • BigInteger类型(GraphQLBigInteger):基于java.math.BigInteger的scalar类型

graphql.Scalars类中包含了所有现有的scalar类型的单例实例。

编写自定义的Scalar类型

可以自定义Scalar类型。在这种实现方式下,需要在运行时自己实现数据到类型的映射机制。

假设我们有一个email的scalar类型,它将使用email地址作为输出和输出。

创建一个EMAIL的scalar类型如下:

        public static final GraphQLScalarType EMAIL = new GraphQLScalarType("email", "A custom scalar that handles emails", new Coercing() {
            @Override
            public Object serialize(Object dataFetcherResult) {
                return serializeEmail(dataFetcherResult);
            }

            @Override
            public Object parseValue(Object input) {
                return parseEmailFromVariable(input);
            }

            @Override
            public Object parseLiteral(Object input) {
                return parseEmailFromAstLiteral(input);
            }
        });

数据映射

自定义的scalar视线中,核心工作是数据映射的实现部分。主要需要实现如下三个方法:

  • parseValue:接收一个input变量,然后在运行时转换为Java中的对象。
  • parseLiteral:接收一个AST常量(graphql.language.Value类型)作为输入,在运行时转换为Java中的对象。
  • serialize:接收一个Java对象,将它转换为scalar中的output表示形式。

自定义的scalar中,必须要实现两个input的转换(parseValue / parseLiteral)和一个output的转换(serialize)。

例如,对于如下的执行语句:

    mutation Contact($mainContact: Email!) {
      makeContact(mainContactEmail: $mainContact, backupContactEmail: "backup@company.com") {
        id
        mainContactEmail
      }
    }

自定义的Email类型scalar将会有如下调用:

  • parseValue方法被调用,将$mailContact变量转换为Java中的运行时数据。
  • parseLiterial方法被调用,将AST的graphql.language.StringValue(backup@company.com)转换为Java中的运行时数据。
  • serialise方法被调用,将mainContactEmail的运行时表示,转换为graphQL的输出形式。

输入和输出有效性验证

scalar定义中也可以验证输入或输出的数据是否有效。例如:email是否是有效的email数据类型。

graphql.schema.Coercing中有如下规定:

  • serialise只允许抛出graphql.schema.CoercingSerializeException类型的异常。它意味着value值不能被序列化为合适的数据形式。同时,也必须保证其他类型的runtime exception不会从serialise方法中抛出。另外,返回值必须是非空值。
  • parseValue只允许抛出graphql.schema.CoercingSerializeException类型的异常。它意味着value值不能被解析为合适的input数据形式。同时,也必须保证其他类型的runtime exception不会从parseValue方法中抛出。另外,返回值必须是非空值。
  • parseLiteral只允许抛出graphql.schema.CoercingSerializeException类型的异常。它意味着AST常量值不能被解析为合适的input数据形式。同时,也必须保证其他类型的runtime exception不会从parseLiteral方法中抛出。另外,返回值必须是非空值。

有些人视图依赖runtime exception来实现验证,然后期望它们以graphql error形式输出。但事实并不是这样。在自定义Scalar时,必须遵循Coercing方法的规范,才能使GraphQL - Java正确运行。

示例

下面是一个复杂的Email类新的Scalar实现。

    public static class EmailScalar {

        public static final GraphQLScalarType EMAIL = new GraphQLScalarType("email", "A custom scalar that handles emails", new Coercing() {
            @Override
            public Object serialize(Object dataFetcherResult) {
                return serializeEmail(dataFetcherResult);
            }

            @Override
            public Object parseValue(Object input) {
                return parseEmailFromVariable(input);
            }

            @Override
            public Object parseLiteral(Object input) {
                return parseEmailFromAstLiteral(input);
            }
        });


        private static boolean looksLikeAnEmailAddress(String possibleEmailValue) {
            // ps.  I am not trying to replicate RFC-3696 clearly
            return Pattern.matches("[A-Za-z0-9]@[.*]", possibleEmailValue);
        }

        private static Object serializeEmail(Object dataFetcherResult) {
            String possibleEmailValue = String.valueOf(dataFetcherResult);
            if (looksLikeAnEmailAddress(possibleEmailValue)) {
                return possibleEmailValue;
            } else {
                throw new CoercingSerializeException("Unable to serialize " + possibleEmailValue + " as an email address");
            }
        }

        private static Object parseEmailFromVariable(Object input) {
            if (input instanceof String) {
                String possibleEmailValue = input.toString();
                if (looksLikeAnEmailAddress(possibleEmailValue)) {
                    return possibleEmailValue;
                }
            }
            throw new CoercingParseValueException("Unable to parse variable value " + input + " as an email address");
        }

        private static Object parseEmailFromAstLiteral(Object input) {
            if (input instanceof StringValue) {
                String possibleEmailValue = ((StringValue) input).getValue();
                if (looksLikeAnEmailAddress(possibleEmailValue)) {
                    return possibleEmailValue;
                }
            }
            throw new CoercingParseLiteralException(
                    "Value is not any email address : '" + String.valueOf(input) + "'"
            );
        }
    }

posted on 2019-09-16 16:42  PKU_荐辕  阅读(1135)  评论(0编辑  收藏  举报

导航