Hi!
Setup
We're in the process of implementing a GraphQL server for the programming language Erlang, and we're trying to get by with only reading the specification and looking relatively little at code, in the hope this will uncover some inconsistencies in the spec. In general, I'm really happy with the specification, but I need some clarification on the combination of non-null when it is combined with the list type.
I feel this combination is part of the dynamic semantics of GraphQL, which is execution semantics. Yet, the only section in the draft spec which covers this is section 4.2.2.9, http://facebook.github.io/graphql/#sec-Combining-List-and-Non-Null which is part of the introspection system. I'm not sure why it is there, and not in the execution semantics of the spec, where I would have expected to find this. Is there another place where this is written down, and am I reading the spec with too much skimming?
Also, I'm not sure how to read the 4.2.2.9 section with respect to evaluation semantics. In order to be able to ask the question, here is my example, taken from an internal test schema where we are building a dungeon containing rooms, monsters, and so on:
type Stats {
shellScripting: Int! # How good a monster is at shell scripting
yell: String
}
type Monster {
id: Id!
name: String!
statsV1: [Stats]
statsV2: [Stats]!
statsV3: [Stats!]
statsV4: [Stats!]!
...
}
As you can see, we have monsters, and monsters have stats, possibly many in a list. We can represent a monster internally as a record:
#monster {
id = "1",
name = "Two-Headed Ogre"
stats = [
#stats { shell_scripting = null, yell = "I'm Ready!" },
#stats { shell_scripting = 5, yell = "I'm NOT Ready!!!" }
]
}
And we can resolve the fields of statsV1, ..., statsV4 by using the stats field of that record. The question is how it would resolve.
Expectation
The two-headed ogre we just defined has a bug: when rendering stats for the two heads, the first head has a shell scripting value of null which will produce an error for the Stats object of a non-nullable shellScripting field. This means an error will be raised for the first head, while the second head will render okay.
Lets set up a query document:
query Q1 { monster(id: "1") { statsV1 { shellScripting, yell }}}
query Q2 { monster(id: "1") { statsV2 { shellScripting, yell }}}
query Q3 { monster(id: "1") { statsV3 { shellScripting, yell }}}
query Q4 { monster(id: "1") { statsV4 { shellScripting, yell }}}
Q1
In this case, I think the output would be
{ data:
monster: {
statsV1: [null, { shellScripting: 5, yell: "I'm NOT Ready!!!"}]
}
}
The resolver for Stats will return null for the shellScripting field which is non-null and thus the whole object becomes null. It is reflected in the list.
Q2
I think the output would be
{ data:
monster: null
}
}
Since the [null, ...] output for statsV2 means there is a null value in the context of [Stats]! and this is not allowed. Thus the whole monster becomes null.
Q3
I think the output would be
{ data:
monster: {
statsV3: [{ shellScripting: 5, yell: "I'm NOT Ready!!!"}]
}
}
Since the rule is that null entries are removed from the list (at least that is how I read 4.2.2.9 in this case).
Q4
Either disallowed by the static semantics, or it essentially behaves as Q2.
Current implementation I have
I run this by a recursion over the list entry. Resolution of the underlying objects in the list returns a value that is either "ok" or an "error"
{ok, Object} | {error, Reason}
This means we have the following list resolved in the execution engine:
[{error, {null_field, "shellScripting"}}, {ok, SecondHeadObject}]
Where the binding of SecondHeadObject is #{ <<"shellScripting">> => 5, <<"yell">> => <<"I'm NOT Ready!!!">> }, an Erlang map() type we can turn into a JSON object later. We can now scrutinize the modifiers. There are 4 cases:
- Q1: Turn
{error, _} into null values, and {ok, _} into normal values. Easy.
- Q2: The case is a
[...]!, i.e., a list wrapped in a non-null modifier. Check the list for {error, _} terms. If one is found, return {error, ...} for this field. Otherwise, turn {ok, _} into normal values and return those.
- Q3: The case is
[...!], i.e., a non-null modifier wrapped in a list. Filter the list and pick {ok, _} values only.
- Q4: Handle as Q2.
What I don't particularly like about this implementation is that it doesn't compose too well. I have to look at which modifier is the outer-one (List/Non-null) and which is the inner one and then handle these by looking at both at the same time. I'd much have preferred a semantics where I could have handled non-null and list separately in the execution engine. But perhaps my semantics are just off?
Question:
Am I right or completely off track here? Can I help improving the specification in any way (Note: I'm not a native English speaker, this may have an effect)? But before I can submit a request on the specification, I would need to know the logic behind the dynamic execution semantics of the above queries in question.
Thanks in Advance!
Hi!
Setup
We're in the process of implementing a GraphQL server for the programming language Erlang, and we're trying to get by with only reading the specification and looking relatively little at code, in the hope this will uncover some inconsistencies in the spec. In general, I'm really happy with the specification, but I need some clarification on the combination of non-null when it is combined with the list type.
I feel this combination is part of the dynamic semantics of GraphQL, which is execution semantics. Yet, the only section in the draft spec which covers this is section 4.2.2.9, http://facebook.github.io/graphql/#sec-Combining-List-and-Non-Null which is part of the introspection system. I'm not sure why it is there, and not in the execution semantics of the spec, where I would have expected to find this. Is there another place where this is written down, and am I reading the spec with too much skimming?
Also, I'm not sure how to read the 4.2.2.9 section with respect to evaluation semantics. In order to be able to ask the question, here is my example, taken from an internal test schema where we are building a dungeon containing rooms, monsters, and so on:
As you can see, we have monsters, and monsters have stats, possibly many in a list. We can represent a monster internally as a record:
And we can resolve the fields of
statsV1, ..., statsV4by using thestatsfield of that record. The question is how it would resolve.Expectation
The two-headed ogre we just defined has a bug: when rendering stats for the two heads, the first head has a shell scripting value of
nullwhich will produce an error for theStatsobject of a non-nullableshellScriptingfield. This means an error will be raised for the first head, while the second head will render okay.Lets set up a query document:
Q1
In this case, I think the output would be
The resolver for
Statswill returnnullfor theshellScriptingfield which is non-null and thus the whole object becomesnull. It is reflected in the list.Q2
I think the output would be
Since the
[null, ...]output forstatsV2means there is anullvalue in the context of[Stats]!and this is not allowed. Thus the whole monster becomes null.Q3
I think the output would be
Since the rule is that
nullentries are removed from the list (at least that is how I read 4.2.2.9 in this case).Q4
Either disallowed by the static semantics, or it essentially behaves as Q2.
Current implementation I have
I run this by a recursion over the list entry. Resolution of the underlying objects in the list returns a value that is either "ok" or an "error"
This means we have the following list resolved in the execution engine:
Where the binding of
SecondHeadObjectis#{ <<"shellScripting">> => 5, <<"yell">> => <<"I'm NOT Ready!!!">> }, an Erlangmap()type we can turn into a JSON object later. We can now scrutinize the modifiers. There are 4 cases:{error, _}into null values, and{ok, _}into normal values. Easy.[...]!, i.e., a list wrapped in a non-null modifier. Check the list for{error, _}terms. If one is found, return{error, ...}for this field. Otherwise, turn{ok, _}into normal values and return those.[...!], i.e., a non-null modifier wrapped in a list. Filter the list and pick{ok, _}values only.What I don't particularly like about this implementation is that it doesn't compose too well. I have to look at which modifier is the outer-one (List/Non-null) and which is the inner one and then handle these by looking at both at the same time. I'd much have preferred a semantics where I could have handled non-null and list separately in the execution engine. But perhaps my semantics are just off?
Question:
Am I right or completely off track here? Can I help improving the specification in any way (Note: I'm not a native English speaker, this may have an effect)? But before I can submit a request on the specification, I would need to know the logic behind the dynamic execution semantics of the above queries in question.
Thanks in Advance!