https://dreamylost.cn/
文档 version = 1.4 仅供参考
Scalars
graphql类型系统的叶节点称为标量。一旦达到标量类型,就无法进一步下降到类型层次结构中。标量类型旨在表示不可分的值。这里是因为查询是document解析,也就是文档树。
graphql规范指出,所有实现都必须具有以下标量类型。
- String 又名 GraphQLString - 一个UTF‐8字符序列
- Boolean 又名 GraphQLBoolean - true 或 false
- Int 又名 GraphQLInt - 一个有符号的32位整数
- Float 又名 GraphQLFloat - 一个有符号双精度浮点数
- ID 又名 GraphQLID - 唯一标识符,以与String相同的方式序列化;但是,将其定义为ID表示它不是人类可读的。
graphql-java添加了以下标量类型,这些标量类型在基于Java的系统中很有用
- Long GraphQLLong - java.lang.Long
- Short GraphQLShort - java.lang.Short
- Byte GraphQLByte - java.lang.Byte
- BigDecimal GraphQLBigDecimal - java.math.BigDecimal
- BigInteger GraphQLBigInteger - java.math.BigInteger
graphql.Scalars类包含所提供标量类型的单例实例
Writing your Own Custom Scalars
您可以编写自己的自定义标量实现。这样,您将负责在运行时强制限制值,我们将在稍后解释。
想象我们决定需要一个电子邮件标量类型。它将电子邮件地址作为输入和输出。
我们将为此创建一个单例graphql.schema.GraphQLScalarType实例。
public static final GraphQLScalarType EMAIL = new GraphQLScalarType("email", "A custom scalar that handles emails", new Coercing() {
//接受一个Java对象并将其转换为该标量的输出形式@Overridepublic Object serialize(Object dataFetcherResult) {
return serializeEmail(dataFetcherResult);}//接受变量输入对象并转换为Java运行时表示形式@Overridepublic Object parseValue(Object input) {
return parseEmailFromVariable(input);}//将AST文字graphql.language.Value作为输入并转换为Java运行时表示形式@Overridepublic Object parseLiteral(Object input) {
return parseEmailFromAstLiteral(input);}
});
Coercing values
任何自定义标量实现中的实际工作都是实现graphql.schema.Coercing。
您的自定义标量代码必须处理2种形式的输入(parseValue/parseLiteral)和1种形式的输出(序列化)。
想象一下这个查询,它使用变量,AST文字并输出我们的标量类型电子邮件。
mutation Contact($mainContact: Email!) {makeContact(mainContactEmail: $mainContact, backupContactEmail: "backup@company.com") {idmainContactEmail}
}
我们的自定义电子邮件标量将
- 通过parseValue调用以将$mainContact变量值转换为运行时对象
- 通过parseLiteral调用以将AST graphql.language.StringValue “backup@company.com” 转换为运行时对象
- 通过序列化调用,以将mainContactEmail的运行时表示形式转换为可用于输出的表单
Validation of input and output
该方法可以验证接收到的输入是否有意义。例如,我们的电子邮件标量将尝试验证输入和输出确实是电子邮件地址。
graphql.schema.Coercing的JavaDoc方法协定如下
- 只允许从序列化抛出graphql.schema.CoercingSerializeException。这表明该值不能序列化为适当的形式。您不得允许其他运行时异常转义此方法以获取正常的graphql行为进行验证。您必须返回非null值。
- 仅允许从parseValue引发graphql.schema.CoercingParseValueException。这表明该值不能解析为适当形式的输入。您不得允许其他运行时异常转义此方法以获取正常的graphql行为进行验证。您必须返回非null值。
- 仅允许从parseLiteral引发graphql.schema.CoercingParseLiteralException。这表明AST值不能解析为适当形式的输入。您不得允许任何运行时异常转义此方法,以获取正常的graphql行为进行验证。
有些人试图依靠运行时异常进行验证,并希望它们以graphql错误的形式出现。然而并发如此,您必须遵循Coercing方法协定,以允许graphql-java引擎根据有关标量类型的graphql规范工作。
Example implementation
以下是我们想象中的email标量类型的一个非常粗略的实现,以向您展示如何实现这种标量的Coercing方法。
public static class EmailScalar {
public static final GraphQLScalarType Email = new GraphQLScalarType("email", "A custom scalar that handles emails", new Coercing() {
@Overridepublic Object serialize(Object dataFetcherResult) {
return serializeEmail(dataFetcherResult);}@Overridepublic Object parseValue(Object input) {
return parseEmailFromVariable(input);}@Overridepublic Object parseLiteral(Object input) {
return parseEmailFromAstLiteral(input);}});private static boolean looksLikeAnEmailAddress(String possibleEmailValue) {
// 这个正则不是标准的验证邮件地址的那个return Pattern.matches("^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[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) + "'");}
}
使用自定义标量
使用上面定义的Email类型完成请求与相应
//自定义标量,......表示省略
......//在StarWarsWiring中定义(或直接使用,主要为了使用依赖注入)
GraphQLScalarType Email = EmailScalar.Email;......//在织入时绑定
private GraphQLSchema buildSchema(String sdl) {
//读取从resources下加载解析的gql schema文件的字符串TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);//创建运行时类型织入RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring();//动态映射//运行时织入包含DataFetcher、TypeResolvers和自定义Scalar,它们是制作完全可执行的schema所必需的。builder.type(newTypeWiring("Query").dataFetcher("hero", starWarsWiring.heroDataFetcher).dataFetcher("human", starWarsWiring.humanDataFetcher).dataFetcher("humans", starWarsWiring.humansDataFetcher).dataFetcher("droid", starWarsWiring.droidDataFetcher));builder.type(newTypeWiring("Human").dataFetcher("friends", starWarsWiring.friendsDataFetcher));builder.type(newTypeWiring("Droid").dataFetcher("friends", starWarsWiring.friendsDataFetcher));builder.type(newTypeWiring("Character").typeResolver(starWarsWiring.characterTypeResolver));builder.type(newTypeWiring("Episode").enumValues(starWarsWiring.episodeResolver));builder.scalar(starWarsWiring.Email);//注入StarWarsWiring中实现的自定义的标量SchemaGenerator schemaGenerator = new SchemaGenerator();//创建gql schema对象,构造可执行请求的gqlreturn schemaGenerator.makeExecutableSchema(typeRegistry, builder.build());
}
在starWarsSchemaAnnotated.graphqls文件修改为
scalar Email......
type Human implements Character {id: ID!name: String!friends: [Character]appearsIn: [Episode]!homePlanet: StringsecretBackstory : String @deprecated(reason : "We have decided that this is not canon")# 添加一个属性,使用了自定义标量email: Email!
}
......
修改Java POJO,为Human类增加String类型 email字段
使用如下查询
{humans {nameemail}
}
返回信息(开了追踪所以有extensions属性)
{
"data": {
"humans": [{
"name": "Luke Skywalker","email": "dreamylost@outlook.com"},{
"name": "Darth Vader","email": "dreamylost@outlook.com"},{
"name": "Han Solo","email": "dreamylost@outlook.com"},{
"name": "Leia Organa","email": "dreamylost@outlook.com"}]},"extensions": {
"tracing": {
"version": 1,"startTime": "2020-04-03T09:25:00.753Z","endTime": "2020-04-03T09:25:00.757Z","duration": 3874865,"parsing": {
"startOffset": 491647,"duration": 404205},"validation": {
"startOffset": 754108,"duration": 231721},"execution": {
"resolvers": [{
"path": ["humans"],"parentType": "Query","returnType": "[Human]","fieldName": "humans","startOffset": 1317556,"duration": 506334},{
"path": ["humans",0,"name"],"parentType": "Human","returnType": "String!","fieldName": "name","startOffset": 2007874,"duration": 58120},{
"path": ["humans",0,"email"],"parentType": "Human","returnType": "Email!","fieldName": "email","startOffset": 2199262,"duration": 58219},{
"path": ["humans",1,"name"],"parentType": "Human","returnType": "String!","fieldName": "name","startOffset": 2507644,"duration": 36967},{
"path": ["humans",1,"email"],"parentType": "Human","returnType": "Email!","fieldName": "email","startOffset": 2643613,"duration": 27507},{
"path": ["humans",2,"name"],"parentType": "Human","returnType": "String!","fieldName": "name","startOffset": 2880688,"duration": 34660},{
"path": ["humans",2,"email"],"parentType": "Human","returnType": "Email!","fieldName": "email","startOffset": 3097616,"duration": 47421},{
"path": ["humans",3,"name"],"parentType": "Human","returnType": "String!","fieldName": "name","startOffset": 3369605,"duration": 30026},{
"path": ["humans",3,"email"],"parentType": "Human","returnType": "Email!","fieldName": "email","startOffset": 3505375,"duration": 45639}]}},"dataloader": {
"overall-statistics": {
"loadCount": 8,"loadErrorCount": 0,"loadErrorRatio": 0.0,"batchInvokeCount": 1,"batchLoadCount": 4,"batchLoadRatio": 0.5,"batchLoadExceptionCount": 0,"batchLoadExceptionRatio": 0.0,"cacheHitCount": 4,"cacheHitRatio": 0.5},"individual-statistics": {
"characters": {
"loadCount": 8,"loadErrorCount": 0,"loadErrorRatio": 0.0,"batchInvokeCount": 1,"batchLoadCount": 4,"batchLoadRatio": 0.5,"batchLoadExceptionCount": 0,"batchLoadExceptionRatio": 0.0,"cacheHitCount": 4,"cacheHitRatio": 0.5}}}}
}
完整代码 https://github.com/jxnu-liguobin/springboot-examples