Skip to content

Commit 699fea2

Browse files
docs
1 parent 54b5a8d commit 699fea2

File tree

2 files changed

+773
-0
lines changed

2 files changed

+773
-0
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
---
2+
title: "Scalars"
3+
date: 2018-09-09T12:52:46+10:00
4+
draft: false
5+
tags: [documentation]
6+
weight: 116
7+
description: Scalars
8+
---
9+
# Scalars in graphql
10+
11+
## Scalars
12+
13+
The leaf nodes of the graphql type system are called scalars. Once you reach a scalar type you
14+
cannot descend down any further into the type hierarchy. A scalar type is meant to represent
15+
an indivisible value.
16+
17+
The graphql specification says that all implementations must have the following scalar types.
18+
19+
* String aka ``GraphQLString`` - A UTF‐8 character sequence.
20+
* Boolean aka ``GraphQLBoolean`` - true or false.
21+
* Int aka ``GraphQLInt`` - A signed 32‐bit integer.
22+
* Float aka ``GraphQLFloat`` - A signed double-precision floating-point value.
23+
* ID aka ``GraphQLID`` - A unique identifier which is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable.
24+
25+
graphql-java adds the following scalar types which are useful in Java based systems
26+
27+
* Long aka ``GraphQLLong`` - a java.lang.Long based scalar
28+
* Short aka ``GraphQLShort`` - a java.lang.Short based scalar
29+
* Byte aka ``GraphQLByte`` - a java.lang.Byte based scalar
30+
* BigDecimal aka ``GraphQLBigDecimal`` - a java.math.BigDecimal based scalar
31+
* BigInteger aka ``GraphQLBigInteger`` - a java.math.BigInteger based scalar
32+
33+
34+
The class ``graphql.Scalars`` contains singleton instances of the provided scalar types
35+
36+
## Writing your Own Custom Scalars
37+
38+
You can write your own custom scalar implementations. In doing so you take on the responsibility for coercing values
39+
at runtime, which we will explain in a moment.
40+
41+
Imagine we decide we need to have an email scalar type. It will take email addresses as input and output.
42+
43+
We would create a singleton ``graphql.schema.GraphQLScalarType`` instance for this like so.
44+
45+
{{< highlight java "linenos=table" >}}
46+
public static final GraphQLScalarType EMAIL = new GraphQLScalarType("email", "A custom scalar that handles emails", new Coercing() {
47+
@Override
48+
public Object serialize(Object dataFetcherResult) {
49+
return serializeEmail(dataFetcherResult);
50+
}
51+
52+
@Override
53+
public Object parseValue(Object input) {
54+
return parseEmailFromVariable(input);
55+
}
56+
57+
@Override
58+
public Object parseLiteral(Object input) {
59+
return parseEmailFromAstLiteral(input);
60+
}
61+
});
62+
63+
{{< / highlight >}}
64+
65+
66+
67+
## Coercing values
68+
69+
The real work in any custom scalar implementation is the ``graphql.schema.Coercing`` implementation. This is responsible for 3 functions
70+
71+
* ``parseValue`` - takes a variable input object and converts into the Java runtime representation
72+
* ``parseLiteral`` - takes an AST literal ``graphql.language.Value` as input and converts into the Java runtime representation
73+
* ``serialize`` - takes a Java object and converts it into the output shape for that scalar
74+
75+
So your custom scalar code has to handle 2 forms of input (parseValue / parseLiteral) and 1 form of output (serialize).
76+
77+
Imagine this query, which uses variables, AST literals and outputs our scalar type ```email``.
78+
79+
{{< highlight graphql "linenos=table" >}}
80+
mutation Contact($mainContact: Email!) {
81+
makeContact(mainContactEmail: $mainContact, backupContactEmail: "backup@company.com") {
82+
id
83+
mainContactEmail
84+
}
85+
}
86+
87+
{{< / highlight >}}
88+
89+
90+
Our custom Email scalar will
91+
92+
* be called via ``parseValue`` to convert the ``$mainContact`` variable value into a runtime object
93+
* be called via ``parseLiteral`` to convert the AST ``graphql.language.StringValue`` "backup@company.com" into a runtime object
94+
* be called via ``serialise`` to turn the runtime representation of mainContactEmail into a form ready for output
95+
96+
## Validation of input and output
97+
98+
The methods can validate that the received input makes sense. For example our email scalar will try to validate that the input
99+
and output are indeed email addresses.
100+
101+
The JavaDoc method contract of ``graphql.schema.Coercing`` says the following
102+
103+
* The ``serialise`` MUST ONLY allow ``graphql.schema.CoercingSerializeException`` to be thrown from it. This indicates that the
104+
value cannot be serialised into an appropriate form. You must not allow other runtime exceptions to escape this method to get
105+
the normal graphql behaviour for validation. You MUST return a non null value
106+
107+
108+
* The ``parseValue`` MUST ONLY allow ``graphql.schema.CoercingParseValueException`` to be thrown from it. This indicates that the
109+
value cannot be parsed as input into an appropriate form. You must not allow other runtime exceptions to escape this method to get
110+
the normal graphql behaviour for validation. You MUST return a non null value.
111+
112+
* The ``parseLiteral`` MUST ONLY allow ``graphql.schema.CoercingParseLiteralException`` to be thrown from it. This indicates that the
113+
AST value cannot be parsed as input into an appropriate form. You must not allow any runtime exceptions to escape this method to get
114+
the normal graphql behaviour for validation.
115+
116+
Some people try to rely on runtime exceptions for validation and hope that they come out as graphql errors. This is not the case. You
117+
MUST follow the ``Coercing`` method contracts to allow the graphql-java engine to work according to the graphql specification on scalar types.
118+
119+
## Example implementation
120+
121+
The following is a really rough implementation of our imagined ``email`` scalar type to show you how one might implement the ``Coercing`` methods
122+
such a scalar.
123+
124+
{{< highlight java "linenos=table" >}}
125+
public static class EmailScalar {
126+
127+
public static final GraphQLScalarType EMAIL = new GraphQLScalarType("email", "A custom scalar that handles emails", new Coercing() {
128+
@Override
129+
public Object serialize(Object dataFetcherResult) {
130+
return serializeEmail(dataFetcherResult);
131+
}
132+
133+
@Override
134+
public Object parseValue(Object input) {
135+
return parseEmailFromVariable(input);
136+
}
137+
138+
@Override
139+
public Object parseLiteral(Object input) {
140+
return parseEmailFromAstLiteral(input);
141+
}
142+
});
143+
144+
145+
private static boolean looksLikeAnEmailAddress(String possibleEmailValue) {
146+
// ps. I am not trying to replicate RFC-3696 clearly
147+
return Pattern.matches("[A-Za-z0-9]@[.*]", possibleEmailValue);
148+
}
149+
150+
private static Object serializeEmail(Object dataFetcherResult) {
151+
String possibleEmailValue = String.valueOf(dataFetcherResult);
152+
if (looksLikeAnEmailAddress(possibleEmailValue)) {
153+
return possibleEmailValue;
154+
} else {
155+
throw new CoercingSerializeException("Unable to serialize " + possibleEmailValue + " as an email address");
156+
}
157+
}
158+
159+
private static Object parseEmailFromVariable(Object input) {
160+
if (input instanceof String) {
161+
String possibleEmailValue = input.toString();
162+
if (looksLikeAnEmailAddress(possibleEmailValue)) {
163+
return possibleEmailValue;
164+
}
165+
}
166+
throw new CoercingParseValueException("Unable to parse variable value " + input + " as an email address");
167+
}
168+
169+
private static Object parseEmailFromAstLiteral(Object input) {
170+
if (input instanceof StringValue) {
171+
String possibleEmailValue = ((StringValue) input).getValue();
172+
if (looksLikeAnEmailAddress(possibleEmailValue)) {
173+
return possibleEmailValue;
174+
}
175+
}
176+
throw new CoercingParseLiteralException(
177+
"Value is not any email address : '" + String.valueOf(input) + "'"
178+
);
179+
}
180+
}
181+
182+
183+
{{< / highlight >}}
184+

0 commit comments

Comments
 (0)