GraphQL Java - Scalars

GraphQL中的Scalar

Scalar(原子類型)

在GraphQL類型系統中,類型樹的葉子節點成爲Scalar。一旦訪問到了Scalar類型的數據,就沒法在該類型基礎上進一步訪問其下的類型層次結構。Scalar類型意味着該類型的值沒法再細分。java

在GraphQL規範中,要求其全部實現都必須具備以下Scalar類型:ide

  • 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類型的單例實例。scala

編寫自定義的Scalar類型

能夠自定義Scalar類型。在這種實現方式下,須要在運行時本身實現數據到類型的映射機制。code

假設咱們有一個email的scalar類型,它將使用email地址做爲輸出和輸出。對象

建立一個EMAIL的scalar類型以下:ci

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視線中,核心工做是數據映射的實現部分。主要須要實現以下三個方法:get

  • parseValue:接收一個input變量,而後在運行時轉換爲Java中的對象。
  • parseLiteral:接收一個AST常量(graphql.language.Value類型)做爲輸入,在運行時轉換爲Java中的對象。
  • serialize:接收一個Java對象,將它轉換爲scalar中的output表示形式。

自定義的scalar中,必需要實現兩個input的轉換(parseValue / parseLiteral)和一個output的轉換(serialize)。input

例如,對於以下的執行語句:graphql

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) + "'"
            );
        }
    }
相關文章
相關標籤/搜索