TL;DR: I'm proposing that Argo add a PATH wire type and an ERROR wire type. See examples (C) and (D) below.
Why?
The current reference implementation of Argo only supports SelfDescribingErrors and OutOfBandFieldErrors.
The purpose of this issue is to explore and discuss what non-self-describing errors might look like in their encoded form and whether having additional wire types for PATH and ERROR would be beneficial.
All of the examples below use the following JSON value as an example GraphQL response:
{
"data": {
"a": "i",
"b": [{"x": {"y": null}}]
},
"errors": [
{
"message": "b-x-y-error",
"location": [{"line": 5, "column": 12}],
"path": ["b", 0, "x", "y"],
"extensions": {"z": 1}
}
]
}
The original GraphQL query isn't important, but it could be imagined to look like this:
query {
a
b {
x {
y
}
}
}
(A) Error type as DESC
Reference implementation wire type (using DESC for the Error type):
{
data: {
a?: STRING
b: {
x: {
y: STRING?
}
}[]?
}
errors?: DESC[]?
}
Encoding with InlineEverything, OutOfBandFieldErrors, and SelfDescribingErrors enabled:
# 97-bytes Base-16
0d00026900020100000204080e6d6573736167650816622d782d792d6572726f72106c6f636174696f6e06020404086c696e650c0a0c636f6c756d6e0c18087061746806080802620c0008027808027914657874656e73696f6e730402027a0c02
# 97-bytes Escaped
\r\0\x02i\0\x02\x01\0\0\x02\x04\x08\x0emessage\x08\x16b-x-y-error\x10location\x06\x02\x04\x04\x08line\x0c\n\x0ccolumn\x0c\x18\x08path\x06\x08\x08\x02b\x0c\0\x08\x02x\x08\x02y\x14extensions\x04\x02\x02z\x0c\x02
(B) Error type as RECORD with STRING[] path
Generally follows section 5.8.1 from the Argo 1.0.0 spec:
{
data: {
a?: STRING
b: {
x: {
y: STRING?
}
}[]?
}
errors?: {
message: STRING
location?: {
line: VARINT
column: VARINT
}[]
path?: STRING[]
extensions?: DESC
}[]?
}
Encoding with InlineEverything and OutOfBandFieldErrors enabled:
# 43-bytes Base-16
0500026900020100000216622d782d792d6572726f7200020a1800080262023002780279000402027a0c02
# 43-bytes Escaped
\x05\0\x02i\0\x02\x01\0\0\x02\x16b-x-y-error\0\x02\n\x18\0\x08\x02b\x020\x02x\x02y\0\x04\x02\x02z\x0c\x02
(C) Error type as RECORD with PATH path
New proposed PATH type instead of STRING[]:
{
data: {
a?: STRING
b: {
x: {
y: STRING?
}
}[]?
}
errors?: {
message: STRING
location?: {
line: VARINT
column: VARINT
}[]
path?: PATH
extensions?: DESC
}[]?
}
Encoding with InlineEverything and OutOfBandFieldErrors enabled:
# 43-bytes Base-16
0500026900020100000216622d782d792d6572726f7200020a1800080262000002780279000402027a0c02
# 43-bytes Escaped
\x05\0\x02i\0\x02\x01\0\0\x02\x16b-x-y-error\0\x02\n\x18\0\x08\x02b\0\0\x02x\x02y\0\x04\x02\x02z\x0c\x02
The main difference here being that path segments may only be one of the following:
enum PathSegment {
FieldName(NonEmptyString),
ListIndex(UnsignedInteger),
}
It's expected that most path segments will be a non-empty String for a field name or alias, so by enforcing that the length of a FieldName segment is non-zero, we can use the NON_NULL label (which is 0) as a reserved label to indicate that a VARINT for a ListIndex is expected next instead of a String.
So, using the example from (B), the zero 0 path segment encoding from ["b", 0, "x", "y"] would normally encode as a String:
With the PATH type, it would be encoded as follows:
Where the first Varint() refers to the label NON_NULL.
For single field errors, the difference between PATH and STRING[] is negligible and should normally result in same-size or slightly smaller encodings compared to the STRING[] variant (cases where a ListIndex with an integer value larger than 9 would result in a smaller encoding).
More benefits from PATH will be described in more detail in a future issue related to @defer and @stream support, which heavily relies on the PATH type.
(D) Error type as ERROR
New proposed ERROR type, which is identical to (C) above, but can be re-used wherever the Error type may need to be used:
{
data: {
a?: STRING
b: {
x: {
y: STRING?
}
}[]?
}
errors?: ERROR[]?
}
Encoding with InlineEverything and OutOfBandFieldErrors enabled:
# 43-bytes Base-16
0500026900020100000216622d782d792d6572726f7200020a1800080262000002780279000402027a0c02
# 43-bytes Escaped
\x05\0\x02i\0\x02\x01\0\0\x02\x16b-x-y-error\0\x02\n\x18\0\x08\x02b\0\0\x02x\x02y\0\x04\x02\x02z\x0c\x02
TODO: (E) Field errors encoding (not yet implemented)
For non-OutOfBandFieldErrors mode, the ERROR label could be written to an otherwise null field and immediately be followed by a list(?) of ERROR[].
I don't think the GraphQL specification limits how many errors might be returned for a particular path, so I'm assuming Argo would need to support a list of field errors.
It might be possible to drop the path field when encoded as a field error. This is assuming that the path could be reconstructed by walking the decoded field itself in combination with the given wire type. For field errors involving arrays, this might work, but for incremental updates it's unclear whether path may be omitted or not.
TL;DR: I'm proposing that Argo add a
PATHwire type and anERRORwire type. See examples (C) and (D) below.Why?
The current reference implementation of Argo only supports
SelfDescribingErrorsandOutOfBandFieldErrors.The purpose of this issue is to explore and discuss what non-self-describing errors might look like in their encoded form and whether having additional wire types for
PATHandERRORwould be beneficial.All of the examples below use the following JSON value as an example GraphQL response:
{ "data": { "a": "i", "b": [{"x": {"y": null}}] }, "errors": [ { "message": "b-x-y-error", "location": [{"line": 5, "column": 12}], "path": ["b", 0, "x", "y"], "extensions": {"z": 1} } ] }The original GraphQL query isn't important, but it could be imagined to look like this:
(A) Error type as
DESCReference implementation wire type (using
DESCfor the Error type):Encoding with
InlineEverything,OutOfBandFieldErrors, andSelfDescribingErrorsenabled:(B) Error type as
RECORDwithSTRING[]pathGenerally follows section 5.8.1 from the Argo 1.0.0 spec:
Encoding with
InlineEverythingandOutOfBandFieldErrorsenabled:(C) Error type as
RECORDwithPATHpathNew proposed
PATHtype instead ofSTRING[]:Encoding with
InlineEverythingandOutOfBandFieldErrorsenabled:The main difference here being that path segments may only be one of the following:
It's expected that most path segments will be a non-empty String for a field name or alias, so by enforcing that the length of a
FieldNamesegment is non-zero, we can use theNON_NULLlabel (which is0) as a reserved label to indicate that aVARINTfor aListIndexis expected next instead of a String.So, using the example from (B), the zero
0path segment encoding from["b", 0, "x", "y"]would normally encode as a String:With the
PATHtype, it would be encoded as follows:Where the first
Varint()refers to the labelNON_NULL.For single field errors, the difference between
PATHandSTRING[]is negligible and should normally result in same-size or slightly smaller encodings compared to theSTRING[]variant (cases where aListIndexwith an integer value larger than 9 would result in a smaller encoding).More benefits from
PATHwill be described in more detail in a future issue related to@deferand@streamsupport, which heavily relies on thePATHtype.(D) Error type as
ERRORNew proposed
ERRORtype, which is identical to (C) above, but can be re-used wherever the Error type may need to be used:Encoding with
InlineEverythingandOutOfBandFieldErrorsenabled:TODO: (E) Field errors encoding (not yet implemented)
For non-
OutOfBandFieldErrorsmode, theERRORlabel could be written to an otherwisenullfield and immediately be followed by a list(?) ofERROR[].I don't think the GraphQL specification limits how many errors might be returned for a particular
path, so I'm assuming Argo would need to support a list of field errors.It might be possible to drop the
pathfield when encoded as a field error. This is assuming that thepathcould be reconstructed by walking the decoded field itself in combination with the given wire type. For field errors involving arrays, this might work, but for incremental updates it's unclear whetherpathmay be omitted or not.