Skip to content

Commit 4fd9902

Browse files
committed
Move shape specifics to a \nDetails: ... error line.
#8616 (comment) The first line is still an improvement over the previous logic because it gives the short names of _both_ `mismatch.expected` and `mismatch.received`, rather than just the received shape name. The `Details: {} did not accept {}` line is important for providing more exact information, including explaining situations where the short names collide (two "object" shapes might be incompatible for any number of reasons, for example). The structure of this shape display syntax should also be helpful for LLMs to make sense of these errors, whereas the previous errors were rather opaque.
1 parent faca41d commit 4fd9902

10 files changed

+56
-63
lines changed

apollo-federation/src/connectors/validation/expression.rs

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use apollo_compiler::parser::LineColumn;
1313
use itertools::Itertools;
1414
use shape::Shape;
1515
use shape::ShapeCase;
16-
use shape::ShapeMismatch;
1716
use shape::graphql::shape_for_arguments;
1817
use shape::location::Location;
1918
use shape::location::SourceId;
@@ -334,10 +333,11 @@ pub(crate) fn validate(
334333
Err(Message {
335334
code: context.code,
336335
message: format!(
337-
"expected `{}` rejects `{}`{}",
336+
"expected {} but received incompatible {}\nDetails: `{}` does not accept `{}`",
337+
short_shape_name(&mismatch.expected),
338+
short_shape_name(&mismatch.received),
338339
mismatch.expected.pretty_print(),
339340
mismatch.received.pretty_print(),
340-
explain_mismatch(&mismatch),
341341
),
342342
locations: transform_locations(mismatch.received.locations(), context, expression),
343343
})
@@ -346,32 +346,6 @@ pub(crate) fn validate(
346346
}
347347
}
348348

349-
/// Generate a more specific explanation by examining the mismatch causes hierarchy
350-
fn explain_mismatch(mismatch: &ShapeMismatch) -> String {
351-
// Follow the chain of first causes
352-
fn follow_first_cause(m: &ShapeMismatch) -> Vec<String> {
353-
if let Some(first_cause) = m.causes.first() {
354-
let mut path = vec![format!(
355-
"`{}` rejects `{}`",
356-
first_cause.expected.pretty_print(),
357-
first_cause.received.pretty_print()
358-
)];
359-
path.extend(follow_first_cause(first_cause));
360-
path
361-
} else {
362-
Vec::new()
363-
}
364-
}
365-
366-
let explanations = follow_first_cause(mismatch);
367-
368-
if explanations.is_empty() {
369-
String::new()
370-
} else {
371-
format!(" (because {})", explanations.join(", because "))
372-
}
373-
}
374-
375349
/// Validate that the shape is an acceptable output shape for an Expression.
376350
fn resolve_shape(
377351
shape: &Shape,
@@ -616,6 +590,25 @@ fn transform_locations<'a>(
616590
locations
617591
}
618592

593+
/// A simplified shape name for error messages
594+
fn short_shape_name(shape: &Shape) -> &'static str {
595+
match shape.case() {
596+
ShapeCase::Bool(_) => "boolean",
597+
ShapeCase::String(_) => "string",
598+
ShapeCase::Int(_) => "number",
599+
ShapeCase::Float => "number",
600+
ShapeCase::Null => "null",
601+
ShapeCase::Array { .. } => "array",
602+
ShapeCase::Object { .. } => "object",
603+
ShapeCase::One(_) => "union",
604+
ShapeCase::All(_) => "intersection",
605+
ShapeCase::Name(_, _) => "named type",
606+
ShapeCase::Unknown => "unknown",
607+
ShapeCase::None => "none",
608+
ShapeCase::Error(_) => "error",
609+
}
610+
}
611+
619612
pub(crate) struct MappingArgument {
620613
pub(crate) expression: Expression,
621614
pub(crate) node: Node<Value>,

apollo-federation/src/connectors/validation/snapshots/[email protected]

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ input_file: apollo-federation/src/connectors/validation/test_data/env-vars.graph
66
[
77
Message {
88
code: InvalidHeader,
9-
message: "In `@source(http.headers:)`: expected `One<Float, Bool, String, null, None>` rejects `Dict<String>`",
9+
message: "In `@source(http.headers:)`: expected union but received incompatible object\nDetails: `One<Float, Bool, String, null, None>` does not accept `Dict<String>`",
1010
locations: [
1111
19:36..19:40,
1212
],
@@ -22,7 +22,7 @@ input_file: apollo-federation/src/connectors/validation/test_data/env-vars.graph
2222
},
2323
Message {
2424
code: InvalidUrl,
25-
message: "In `GET` in `@connect(http:)` on `Query.invalidObject`: expected `One<Float, Bool, String, null, None>` rejects `Dict<String>`",
25+
message: "In `GET` in `@connect(http:)` on `Query.invalidObject`: expected union but received incompatible object\nDetails: `One<Float, Bool, String, null, None>` does not accept `Dict<String>`",
2626
locations: [
2727
36:44..36:48,
2828
],

apollo-federation/src/connectors/validation/snapshots/[email protected]

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ input_file: apollo-federation/src/connectors/validation/test_data/errors.graphql
66
[
77
Message {
88
code: InvalidErrorsMessage,
9-
message: "In `@source(name: \"invalid_source_message_not_string\" errors.message:)`: expected `String` rejects `{ message: Unknown }`",
9+
message: "In `@source(name: \"invalid_source_message_not_string\" errors.message:)`: expected string but received incompatible object\nDetails: `String` does not accept `{ message: Unknown }`",
1010
locations: [
1111
13:25..13:47,
1212
],
@@ -41,14 +41,14 @@ input_file: apollo-federation/src/connectors/validation/test_data/errors.graphql
4141
},
4242
Message {
4343
code: InvalidErrorsMessage,
44-
message: "In `@connect(errors.message:)` on `Query.invalid_sourceless_message_not_string`: expected `String` rejects `{ message: Unknown }`",
44+
message: "In `@connect(errors.message:)` on `Query.invalid_sourceless_message_not_string`: expected string but received incompatible object\nDetails: `String` does not accept `{ message: Unknown }`",
4545
locations: [
4646
86:27..86:49,
4747
],
4848
},
4949
Message {
5050
code: InvalidErrorsMessage,
51-
message: "In `@connect(errors.message:)` on `Query.invalid_sourceless_message_not_string_from_args`: expected `String` rejects `One<Int, null>` (because `String` rejects `Int`)",
51+
message: "In `@connect(errors.message:)` on `Query.invalid_sourceless_message_not_string_from_args`: expected string but received incompatible union\nDetails: `String` does not accept `One<Int, null>`",
5252
locations: [
5353
97:58..97:61,
5454
97:51..97:61,

apollo-federation/src/connectors/validation/snapshots/validation_tests@headers__expressions_that_evaluate_to_invalid_types_v0_2.graphql.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ input_file: apollo-federation/src/connectors/validation/test_data/headers/expres
66
[
77
Message {
88
code: InvalidHeader,
9-
message: "In `@source(http.headers:)`: expected `One<Float, Bool, String, null, None>` rejects `{}`",
9+
message: "In `@source(http.headers:)`: expected union but received incompatible object\nDetails: `One<Float, Bool, String, null, None>` does not accept `{}`",
1010
locations: [
1111
12:45..12:47,
1212
],
1313
},
1414
Message {
1515
code: InvalidHeader,
16-
message: "In `@connect(http.headers:)` on `Query.blah`: expected `One<Float, Bool, String, null, None>` rejects `One<List<One<String, null>>, null>` (because `One<Float, Bool, String, null, None>` rejects `List<One<String, null>>`)",
16+
message: "In `@connect(http.headers:)` on `Query.blah`: expected union but received incompatible union\nDetails: `One<Float, Bool, String, null, None>` does not accept `One<List<One<String, null>>, null>`",
1717
locations: [
1818
19:10..19:27,
1919
21:73..21:80,

apollo-federation/src/connectors/validation/snapshots/validation_tests@headers__expressions_that_evaluate_to_invalid_types_v0_3.graphql.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,21 @@ input_file: apollo-federation/src/connectors/validation/test_data/headers/expres
66
[
77
Message {
88
code: InvalidHeader,
9-
message: "In `@source(http.headers:)`: expected `One<Float, Bool, String, null, None>` rejects `[]`",
9+
message: "In `@source(http.headers:)`: expected union but received incompatible array\nDetails: `One<Float, Bool, String, null, None>` does not accept `[]`",
1010
locations: [
1111
11:50..11:52,
1212
],
1313
},
1414
Message {
1515
code: InvalidHeader,
16-
message: "In `@source(http.headers:)`: expected `One<Float, Bool, String, null, None>` rejects `{}`",
16+
message: "In `@source(http.headers:)`: expected union but received incompatible object\nDetails: `One<Float, Bool, String, null, None>` does not accept `{}`",
1717
locations: [
1818
12:45..12:47,
1919
],
2020
},
2121
Message {
2222
code: InvalidHeader,
23-
message: "In `@source(http.headers:)`: expected `One<Float, Bool, String, null, None>` rejects `One<1, {}, None>` (because `One<Float, Bool, String, null, None>` rejects `{}`)",
23+
message: "In `@source(http.headers:)`: expected union but received incompatible union\nDetails: `One<Float, Bool, String, null, None>` does not accept `One<1, {}, None>`",
2424
locations: [
2525
13:65..13:66,
2626
13:55..13:60,
@@ -29,7 +29,7 @@ input_file: apollo-federation/src/connectors/validation/test_data/headers/expres
2929
},
3030
Message {
3131
code: InvalidHeader,
32-
message: "In `@connect(http.headers:)` on `Query.blah`: expected `One<Float, Bool, String, null, None>` rejects `One<List<One<String, null>>, null>` (because `One<Float, Bool, String, null, None>` rejects `List<One<String, null>>`)",
32+
message: "In `@connect(http.headers:)` on `Query.blah`: expected union but received incompatible union\nDetails: `One<Float, Bool, String, null, None>` does not accept `One<List<One<String, null>>, null>`",
3333
locations: [
3434
19:10..19:27,
3535
21:73..21:80,

apollo-federation/src/connectors/validation/snapshots/validation_tests@request_headers.graphql.snap

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,21 @@ input_file: apollo-federation/src/connectors/validation/test_data/request_header
66
[
77
Message {
88
code: InvalidHeader,
9-
message: "In `@source(http.headers:)`: expected `One<Float, Bool, String, null, None>` rejects `One<List<String>, None>` (because `One<Float, Bool, String, null, None>` rejects `List<String>`)",
9+
message: "In `@source(http.headers:)`: expected union but received incompatible union\nDetails: `One<Float, Bool, String, null, None>` does not accept `One<List<String>, None>`",
1010
locations: [
1111
5:101..5:111,
1212
],
1313
},
1414
Message {
1515
code: InvalidHeader,
16-
message: "In `@connect(http.headers:)` on `Query.failOnArray`: expected `One<Float, Bool, String, null, None>` rejects `One<List<String>, None>` (because `One<Float, Bool, String, null, None>` rejects `List<String>`)",
16+
message: "In `@connect(http.headers:)` on `Query.failOnArray`: expected union but received incompatible union\nDetails: `One<Float, Bool, String, null, None>` does not accept `One<List<String>, None>`",
1717
locations: [
1818
28:68..28:78,
1919
],
2020
},
2121
Message {
2222
code: InvalidUrl,
23-
message: "In `GET` in `@connect(http:)` on `Query.failOnArray`: expected `One<Float, Bool, String, null, None>` rejects `One<List<String>, None>` (because `One<Float, Bool, String, null, None>` rejects `List<String>`)",
23+
message: "In `GET` in `@connect(http:)` on `Query.failOnArray`: expected union but received incompatible union\nDetails: `One<Float, Bool, String, null, None>` does not accept `One<List<String>, None>`",
2424
locations: [
2525
27:61..27:71,
2626
],
@@ -43,14 +43,14 @@ input_file: apollo-federation/src/connectors/validation/test_data/request_header
4343
},
4444
Message {
4545
code: InvalidHeader,
46-
message: "In `@connect(http.headers:)` on `Query.failOnInvalidObject`: expected `One<Float, Bool, String, null, None>` rejects `Dict<List<String>>`",
46+
message: "In `@connect(http.headers:)` on `Query.failOnInvalidObject`: expected union but received incompatible object\nDetails: `One<Float, Bool, String, null, None>` does not accept `Dict<List<String>>`",
4747
locations: [
4848
44:60..44:67,
4949
],
5050
},
5151
Message {
5252
code: InvalidUrl,
53-
message: "In `GET` in `@connect(http:)` on `Query.failOnInvalidObject`: expected `One<Float, Bool, String, null, None>` rejects `Dict<List<String>>`",
53+
message: "In `GET` in `@connect(http:)` on `Query.failOnInvalidObject`: expected union but received incompatible object\nDetails: `One<Float, Bool, String, null, None>` does not accept `Dict<List<String>>`",
5454
locations: [
5555
43:53..43:60,
5656
],

apollo-federation/src/connectors/validation/snapshots/validation_tests@uri_templates__invalid_source_url_template.graphql.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ input_file: apollo-federation/src/connectors/validation/test_data/uri_templates/
2020
},
2121
Message {
2222
code: InvalidUrl,
23-
message: "Invalid @source `baseURL` template: expected `One<Float, Bool, String, null, None>` rejects `{}`",
23+
message: "Invalid @source `baseURL` template: expected union but received incompatible object\nDetails: `One<Float, Bool, String, null, None>` does not accept `{}`",
2424
locations: [
2525
7:52..7:54,
2626
],

apollo-federation/src/connectors/validation/snapshots/validation_tests@uri_templates__invalid_types.graphql.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ input_file: apollo-federation/src/connectors/validation/test_data/uri_templates/
66
[
77
Message {
88
code: InvalidUrl,
9-
message: "In `GET` in `@connect(http:)` on `Query.argIsArray`: expected `One<Float, Bool, String, null, None>` rejects `One<List<One<String, null>>, null>` (because `One<Float, Bool, String, null, None>` rejects `List<One<String, null>>`)",
9+
message: "In `GET` in `@connect(http:)` on `Query.argIsArray`: expected union but received incompatible union\nDetails: `One<Float, Bool, String, null, None>` does not accept `One<List<One<String, null>>, null>`",
1010
locations: [
1111
8:16..8:29,
1212
10:47..10:50,
1313
],
1414
},
1515
Message {
1616
code: InvalidUrl,
17-
message: "In `GET` in `@connect(http:)` on `Query.argIsObject`: expected `One<Float, Bool, String, null, None>` rejects `One<{ val: Unknown }, null>` (because `One<Float, Bool, String, null, None>` rejects `{ val: Unknown }`)",
17+
message: "In `GET` in `@connect(http:)` on `Query.argIsObject`: expected union but received incompatible union\nDetails: `One<Float, Bool, String, null, None>` does not accept `One<{ val: Unknown }, null>`",
1818
locations: [
1919
36:1..38:2,
2020
14:22..14:27,
@@ -24,15 +24,15 @@ input_file: apollo-federation/src/connectors/validation/test_data/uri_templates/
2424
},
2525
Message {
2626
code: InvalidUrl,
27-
message: "In `GET` in `@connect(http:)` on `This.thisIsArray`: expected `One<Float, Bool, String, null, None>` rejects `One<List<One<String, null>>, null>` (because `One<Float, Bool, String, null, None>` rejects `List<One<String, null>>`)",
27+
message: "In `GET` in `@connect(http:)` on `This.thisIsArray`: expected union but received incompatible union\nDetails: `One<Float, Bool, String, null, None>` does not accept `One<List<One<String, null>>, null>`",
2828
locations: [
2929
22:5..22:32,
3030
25:47..25:54,
3131
],
3232
},
3333
Message {
3434
code: InvalidUrl,
35-
message: "In `GET` in `@connect(http:)` on `This.requiresAnObject`: expected `One<Float, Bool, String, null, None>` rejects `One<\n { __typename: One<\"Object\", None>, stuff: Unknown },\n null,\n>` (because `One<Float, Bool, String, null, None>` rejects `{ __typename: One<\"Object\", None>, stuff: Unknown }`)",
35+
message: "In `GET` in `@connect(http:)` on `This.requiresAnObject`: expected union but received incompatible union\nDetails: `One<Float, Bool, String, null, None>` does not accept `One<\n { __typename: One<\"Object\", None>, stuff: Unknown },\n null,\n>`",
3636
locations: [
3737
40:1..42:2,
3838
28:15..28:21,

apollo-federation/src/connectors/validation/snapshots/validation_tests@url_properties__path.graphql.snap

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ input_file: apollo-federation/src/connectors/validation/test_data/url_properties
66
[
77
Message {
88
code: InvalidUrlProperty,
9-
message: "In `@source(name: \"v1\")`, the `path` argument is invalid: expected `List<One<String, Float, Bool>>` rejects `[\"good\", 42, true, null, \"\"]` (because `One<String, Float, Bool>` rejects `null`)",
9+
message: "In `@source(name: \"v1\")`, the `path` argument is invalid: expected array but received incompatible array\nDetails: `List<One<String, Float, Bool>>` does not accept `[\"good\", 42, true, null, \"\"]`",
1010
locations: [
1111
10:16..10:44,
1212
],
@@ -20,28 +20,28 @@ input_file: apollo-federation/src/connectors/validation/test_data/url_properties
2020
},
2121
Message {
2222
code: InvalidUrlProperty,
23-
message: "In `@source(name: \"v3\")`, the `path` argument is invalid: expected `List<One<String, Float, Bool>>` rejects `\"bad\"`",
23+
message: "In `@source(name: \"v3\")`, the `path` argument is invalid: expected array but received incompatible string\nDetails: `List<One<String, Float, Bool>>` does not accept `\"bad\"`",
2424
locations: [
2525
16:54..16:59,
2626
],
2727
},
2828
Message {
2929
code: InvalidUrlProperty,
30-
message: "In `@source(name: \"v4\")`, the `path` argument is invalid: expected `List<One<String, Float, Bool>>` rejects `{ a: \"bad\" }`",
30+
message: "In `@source(name: \"v4\")`, the `path` argument is invalid: expected array but received incompatible object\nDetails: `List<One<String, Float, Bool>>` does not accept `{ a: \"bad\" }`",
3131
locations: [
3232
20:54..20:66,
3333
],
3434
},
3535
Message {
3636
code: InvalidUrlProperty,
37-
message: "In `@connect` on `Query.resources`, the `path` argument is invalid: expected `List<One<String, Float, Bool>>` rejects `[\"good\", 42, true, null, \"\"]` (because `One<String, Float, Bool>` rejects `null`)",
37+
message: "In `@connect` on `Query.resources`, the `path` argument is invalid: expected array but received incompatible array\nDetails: `List<One<String, Float, Bool>>` does not accept `[\"good\", 42, true, null, \"\"]`",
3838
locations: [
3939
27:34..27:62,
4040
],
4141
},
4242
Message {
4343
code: InvalidUrlProperty,
44-
message: "In `@connect` on `Query.resources`, the `path` argument is invalid: expected `List<One<String, Float, Bool>>` rejects `[\n One<String, null>,\n One<Int, null>,\n One<Float, null>,\n One<Bool, null>,\n]` (because `One<String, Float, Bool>` rejects `One<String, null>`, because `One<String, Float, Bool>` rejects `null`)",
44+
message: "In `@connect` on `Query.resources`, the `path` argument is invalid: expected array but received incompatible array\nDetails: `List<One<String, Float, Bool>>` does not accept `[\n One<String, null>,\n One<Int, null>,\n One<Float, null>,\n One<Bool, null>,\n]`",
4545
locations: [
4646
32:34..32:70,
4747
],
@@ -55,21 +55,21 @@ input_file: apollo-federation/src/connectors/validation/test_data/url_properties
5555
},
5656
Message {
5757
code: InvalidUrlProperty,
58-
message: "In `@connect` on `Query.resources`, the `path` argument is invalid: expected `List<One<String, Float, Bool>>` rejects `\"bad\"`",
58+
message: "In `@connect` on `Query.resources`, the `path` argument is invalid: expected array but received incompatible string\nDetails: `List<One<String, Float, Bool>>` does not accept `\"bad\"`",
5959
locations: [
6060
36:55..36:60,
6161
],
6262
},
6363
Message {
6464
code: InvalidUrlProperty,
65-
message: "In `@connect` on `Query.resources`, the `path` argument is invalid: expected `List<One<String, Float, Bool>>` rejects `{ a: \"bad\" }`",
65+
message: "In `@connect` on `Query.resources`, the `path` argument is invalid: expected array but received incompatible object\nDetails: `List<One<String, Float, Bool>>` does not accept `{ a: \"bad\" }`",
6666
locations: [
6767
39:34..39:46,
6868
],
6969
},
7070
Message {
7171
code: InvalidUrlProperty,
72-
message: "In `@connect` on `Query.resources`, the `path` argument is invalid: expected `List<One<String, Float, Bool>>` rejects `One<String, null>` (because `List<One<String, Float, Bool>>` rejects `String`)",
72+
message: "In `@connect` on `Query.resources`, the `path` argument is invalid: expected array but received incompatible union\nDetails: `List<One<String, Float, Bool>>` does not accept `One<String, null>`",
7373
locations: [
7474
24:16..24:22,
7575
24:13..24:22,

0 commit comments

Comments
 (0)