在GraphQL类型系统中,类型树的叶子节点成为Scalar。一旦访问到了Scalar类型的数据,就无法在该类型基础上进一步访问其下的类型层次结构。Scalar类型意味着该类型的值无法再细分。
在GraphQL规范中,要求其所有实现都必须具有如下Scalar类型:
GraphQL - Java补充添加了如下几种额外类型:
graphql.Scalars类中包含了所有现有的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视线中,核心工作是数据映射的实现部分。主要需要实现如下三个方法:
自定义的scalar中,必须要实现两个input的转换(parseValue / parseLiteral)和一个output的转换(serialize)。
例如,对于如下的执行语句:
mutation Contact($mainContact: Email!) {
makeContact(mainContactEmail: $mainContact, backupContactEmail: "backup@company.com") {
id
mainContactEmail
}
}
自定义的Email类型scalar将会有如下调用:
scalar定义中也可以验证输入或输出的数据是否有效。例如:email是否是有效的email数据类型。
graphql.schema.Coercing中有如下规定:
有些人视图依赖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) + "'"
);
}
}