use apollo_compiler::validation::Valid;
use apollo_compiler::ExecutableDocument;
use apollo_compiler::Schema;
use expect_test::expect;
use expect_test::Expect;
use std::sync::OnceLock;
use unindent::unindent;
const GRAPHQL_JS_TEST_SCHEMA: &str = r#"
interface Mammal {
mother: Mammal
father: Mammal
}
interface Pet {
name(surname: Boolean): String
}
interface Canine implements Mammal {
name(surname: Boolean): String
mother: Canine
father: Canine
}
enum DogCommand {
SIT
HEEL
DOWN
}
type Dog implements Pet & Mammal & Canine {
name(surname: Boolean): String
nickname: String
barkVolume: Int
barks: Boolean
doesKnowCommand(dogCommand: DogCommand): Boolean
isHouseTrained(atOtherHomes: Boolean = true): Boolean
isAtLocation(x: Int, y: Int): Boolean
mother: Dog
father: Dog
}
type Cat implements Pet {
name(surname: Boolean): String
nickname: String
meows: Boolean
meowsVolume: Int
furColor: FurColor
}
union CatOrDog = Cat | Dog
type Human {
name(surname: Boolean): String
pets: [Pet]
relatives: [Human]!
}
enum FurColor {
BROWN
BLACK
TAN
SPOTTED
NO_FUR
UNKNOWN
}
input ComplexInput {
requiredField: Boolean!
nonNullField: Boolean! = false
intField: Int
stringField: String
booleanField: Boolean
stringListField: [String]
}
# TODO oneOf not supported in apollo-rs
input OneOfInput { # @oneOf
stringField: String
intField: Int
}
type ComplicatedArgs {
# TODO List
# TODO Coercion
# TODO NotNulls
intArgField(intArg: Int): String
nonNullIntArgField(nonNullIntArg: Int!): String
stringArgField(stringArg: String): String
booleanArgField(booleanArg: Boolean): String
enumArgField(enumArg: FurColor): String
floatArgField(floatArg: Float): String
idArgField(idArg: ID): String
stringListArgField(stringListArg: [String]): String
stringListNonNullArgField(stringListNonNullArg: [String!]): String
complexArgField(complexArg: ComplexInput): String
oneOfArgField(oneOfArg: OneOfInput): String
multipleReqs(req1: Int!, req2: Int!): String
nonNullFieldWithDefault(arg: Int! = 0): String
multipleOpts(opt1: Int = 0, opt2: Int = 0): String
multipleOptAndReq(req1: Int!, req2: Int!, opt1: Int = 0, opt2: Int = 0): String
}
type QueryRoot {
human(id: ID): Human
dog: Dog
cat: Cat
pet: Pet
catOrDog: CatOrDog
complicatedArgs: ComplicatedArgs
}
schema {
query: QueryRoot
}
directive @onField on FIELD
"#;
fn test_schema() -> &'static Valid<Schema> {
static SCHEMA: OnceLock<Valid<Schema>> = OnceLock::new();
SCHEMA.get_or_init(|| {
Schema::parse_and_validate(unindent(GRAPHQL_JS_TEST_SCHEMA), "schema.graphql").unwrap()
})
}
#[track_caller]
fn expect_valid(query: &'static str) {
let schema = test_schema();
ExecutableDocument::parse_and_validate(schema, unindent(query), "query.graphql").unwrap();
}
fn expect_errors(query: &'static str, expect: Expect) {
let schema = test_schema();
let errors = ExecutableDocument::parse_and_validate(schema, unindent(query), "query.graphql")
.expect_err("should have errors")
.errors;
expect.assert_eq(&errors.to_string());
}
#[test]
fn unique_fields() {
expect_valid(
r#"
fragment uniqueFields on Dog {
name
nickname
}
{ dog { ...uniqueFields } }
"#,
);
}
#[test]
fn identical_fields() {
expect_valid(
r#"
fragment mergeIdenticalFields on Dog {
name
name
}
{ dog { ...mergeIdenticalFields } }
"#,
);
}
#[test]
fn identical_fields_with_identical_args() {
expect_valid(
r#"
fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand(dogCommand: SIT)
}
{ dog { ...mergeIdenticalFieldsWithIdenticalArgs } }
"#,
);
}
#[test]
fn identical_fields_with_identical_directives() {
expect_valid(
r#"
fragment mergeSameFieldsWithSameDirectives on Dog {
name @include(if: true)
name @include(if: true)
}
{ dog { ...mergeSameFieldsWithSameDirectives } }
"#,
);
}
#[test]
fn different_args_with_different_aliases() {
expect_valid(
r#"
fragment differentArgsWithDifferentAliases on Dog {
knowsSit: doesKnowCommand(dogCommand: SIT)
knowsDown: doesKnowCommand(dogCommand: DOWN)
}
{ dog { ...differentArgsWithDifferentAliases } }
"#,
);
}
#[test]
fn different_directives_with_different_aliases() {
expect_valid(
r#"
fragment differentDirectivesWithDifferentAliases on Dog {
nameIfTrue: name @include(if: true)
nameIfFalse: name @include(if: false)
}
{ dog { ...differentDirectivesWithDifferentAliases } }
"#,
);
}
#[test]
fn different_skip_include_directives() {
expect_valid(
r#"
fragment differentDirectivesWithDifferentAliases on Dog {
name @include(if: true)
name @include(if: false)
}
{ dog { ...differentDirectivesWithDifferentAliases } }
"#,
);
}
#[test]
fn same_aliases_with_different_field_targets() {
expect_errors(
r#"
fragment sameAliasesWithDifferentFieldTargets on Dog {
fido: name
fido: nickname
}
{ dog { ...sameAliasesWithDifferentFieldTargets } }
"#,
expect![[r#"
Error: cannot select different fields into the same alias `fido`
╭─[ query.graphql:3:3 ]
│
2 │ fido: name
│ ─────┬────
│ ╰────── `fido` is selected from `Dog.name` here
3 │ fido: nickname
│ ───────┬──────
│ ╰──────── `fido` is selected from `Dog.nickname` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
───╯
"#]],
);
}
#[test]
fn same_aliases_on_non_overlapping_fields() {
expect_valid(
r#"
fragment sameAliasesWithDifferentFieldTargets on Pet {
... on Dog {
name
}
... on Cat {
name: nickname
}
}
{ pet { ...sameAliasesWithDifferentFieldTargets } }
"#,
);
}
#[test]
fn alias_masking_direct_field_access() {
expect_errors(
r#"
fragment aliasMaskingDirectFieldAccess on Dog {
name: nickname
name
}
{ dog { ...aliasMaskingDirectFieldAccess } }
"#,
expect![[r#"
Error: cannot select different fields into the same alias `name`
╭─[ query.graphql:3:3 ]
│
2 │ name: nickname
│ ───────┬──────
│ ╰──────── `name` is selected from `Dog.nickname` here
3 │ name
│ ──┬─
│ ╰─── `name` is selected from `Dog.name` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
───╯
"#]],
);
}
#[test]
fn different_args_second_adds_argument() {
expect_errors(
r#"
fragment conflictingArgs on Dog {
doesKnowCommand
doesKnowCommand(dogCommand: HEEL)
}
{ dog { ...conflictingArgs } }
"#,
expect![[r#"
Error: operation must not provide conflicting field arguments for the same name `doesKnowCommand`
╭─[ query.graphql:3:3 ]
│
3 │ doesKnowCommand(dogCommand: HEEL)
│ ────────────────┬────────────────
│ ╰────────────────── `doesKnowCommand` is selected with argument `dogCommand` here
│
├─[ query.graphql:3:3 ]
│
2 │ doesKnowCommand
│ ───────┬───────
│ ╰───────── but argument `dogCommand` is not provided here
│
│ Help: The same name cannot be selected multiple times with different arguments, because it's not clear which set of arguments should be used to fill the response. If you intend to use diverging arguments, consider adding an alias to differentiate
───╯
"#]],
);
}
#[test]
fn different_args_second_missess_argument() {
expect_errors(
r#"
fragment conflictingArgs on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand
}
{ dog { ...conflictingArgs } }
"#,
expect![[r#"
Error: operation must not provide conflicting field arguments for the same name `doesKnowCommand`
╭─[ query.graphql:3:3 ]
│
2 │ doesKnowCommand(dogCommand: SIT)
│ ────────────────┬───────────────
│ ╰───────────────── `doesKnowCommand` is selected with argument `dogCommand` here
3 │ doesKnowCommand
│ ───────┬───────
│ ╰───────── but argument `dogCommand` is not provided here
│
│ Help: The same name cannot be selected multiple times with different arguments, because it's not clear which set of arguments should be used to fill the response. If you intend to use diverging arguments, consider adding an alias to differentiate
───╯
"#]],
);
}
#[test]
fn conflicting_arg_values() {
expect_errors(
r#"
fragment conflictingArgs on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand(dogCommand: HEEL)
}
{ dog { ...conflictingArgs } }
"#,
expect![[r#"
Error: operation must not provide conflicting field arguments for the same name `doesKnowCommand`
╭─[ query.graphql:3:3 ]
│
2 │ doesKnowCommand(dogCommand: SIT)
│ ────────────────┬───────────────
│ ╰───────────────── `Dog.doesKnowCommand(dogCommand:)` is used with one argument value here
3 │ doesKnowCommand(dogCommand: HEEL)
│ ────────────────┬────────────────
│ ╰────────────────── but a different value here
│
│ Help: The same name cannot be selected multiple times with different arguments, because it's not clear which set of arguments should be used to fill the response. If you intend to use diverging arguments, consider adding an alias to differentiate
───╯
"#]],
);
}
#[test]
fn conflicting_arg_names() {
expect_errors(
r#"
fragment conflictingArgs on Dog {
isAtLocation(x: 0)
isAtLocation(y: 0)
}
{ dog { ...conflictingArgs } }
"#,
expect![[r#"
Error: operation must not provide conflicting field arguments for the same name `isAtLocation`
╭─[ query.graphql:3:3 ]
│
2 │ isAtLocation(x: 0)
│ ─────────┬────────
│ ╰────────── `isAtLocation` is selected with argument `x` here
3 │ isAtLocation(y: 0)
│ ─────────┬────────
│ ╰────────── but argument `x` is not provided here
│
│ Help: The same name cannot be selected multiple times with different arguments, because it's not clear which set of arguments should be used to fill the response. If you intend to use diverging arguments, consider adding an alias to differentiate
───╯
"#]],
);
}
#[test]
fn different_non_conflicting_args() {
expect_valid(
r#"
fragment conflictingArgs on Pet {
... on Dog {
name(surname: true)
}
... on Cat {
name
}
}
{ pet { ...conflictingArgs } }
"#,
);
}
#[test]
fn different_order_args() {
expect_valid(
r#"
{
dog {
isAtLocation(x: 0, y: 1)
isAtLocation(y: 1, x: 0)
}
}
"#,
);
}
#[test]
fn different_order_input_args() {
expect_valid(
r#"
{
complicatedArgs {
complexArgField(complexArg: { intField: 1, requiredField: true })
complexArgField(complexArg: { requiredField: true, intField: 1 })
}
}
"#,
);
}
mod field_conflicts {
use apollo_compiler::validation::Valid;
use apollo_compiler::ExecutableDocument;
use apollo_compiler::Schema;
use expect_test::expect;
use expect_test::Expect;
use std::sync::OnceLock;
use unindent::unindent;
const CONFLICT_TEST_SCHEMA: &str = r#"
type Type {
a: Int
b: Int
c: Int
d: Int
}
type T {
a: Boolean
b: Boolean
c: Boolean
d: Boolean
deepField: Type
}
type Query {
f1: Type
f2: Type
f3: Type
field: T
}
"#;
fn test_schema() -> &'static Valid<Schema> {
static SCHEMA: OnceLock<Valid<Schema>> = OnceLock::new();
SCHEMA.get_or_init(|| {
Schema::parse_and_validate(unindent(CONFLICT_TEST_SCHEMA), "schema.graphql").unwrap()
})
}
fn expect_errors(query: &'static str, expect: Expect) {
let schema = test_schema();
let errors =
ExecutableDocument::parse_and_validate(schema, unindent(query), "query.graphql")
.expect_err("should have errors")
.errors;
expect.assert_eq(&errors.to_string());
}
#[test]
fn conflicts_in_fragments() {
expect_errors(
r#"
{
f1 {
...A
...B
}
}
fragment A on Type {
x: a
}
fragment B on Type {
x: b
}
"#,
expect![[r#"
Error: cannot select different fields into the same alias `x`
╭─[ query.graphql:11:3 ]
│
8 │ x: a
│ ──┬─
│ ╰─── `x` is selected from `Type.a` here
│
11 │ x: b
│ ──┬─
│ ╰─── `x` is selected from `Type.b` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
"#]],
);
}
#[test]
fn dedupe_conflicts() {
expect_errors(
r#"
{
f1 {
...A
...B
}
f2 {
...B
...A
}
f3 {
...A
...B
x: c
}
}
fragment A on Type {
x: a
}
fragment B on Type {
x: b
}
"#,
expect![[r#"
Error: cannot select different fields into the same alias `x`
╭─[ query.graphql:17:3 ]
│
20 │ x: b
│ ──┬─
│ ╰─── `x` is selected from `Type.b` here
│
├─[ query.graphql:17:3 ]
│
17 │ x: a
│ ──┬─
│ ╰─── `x` is selected from `Type.a` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
Error: cannot select different fields into the same alias `x`
╭─[ query.graphql:17:3 ]
│
13 │ x: c
│ ──┬─
│ ╰─── `x` is selected from `Type.c` here
│
17 │ x: a
│ ──┬─
│ ╰─── `x` is selected from `Type.a` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
Error: cannot select different fields into the same alias `x`
╭─[ query.graphql:20:3 ]
│
17 │ x: a
│ ──┬─
│ ╰─── `x` is selected from `Type.a` here
│
20 │ x: b
│ ──┬─
│ ╰─── `x` is selected from `Type.b` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
Error: cannot select different fields into the same alias `x`
╭─[ query.graphql:20:3 ]
│
13 │ x: c
│ ──┬─
│ ╰─── `x` is selected from `Type.c` here
│
20 │ x: b
│ ──┬─
│ ╰─── `x` is selected from `Type.b` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
"#]],
);
}
#[test]
fn deep_conflict() {
expect_errors(
r#"
{
f1 {
x: a
},
f1 {
x: b
}
}
"#,
expect![[r#"
Error: cannot select different fields into the same alias `x`
╭─[ query.graphql:6:5 ]
│
3 │ x: a
│ ──┬─
│ ╰─── `x` is selected from `Type.a` here
│
6 │ x: b
│ ──┬─
│ ╰─── `x` is selected from `Type.b` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
───╯
"#]],
);
}
#[test]
fn deep_conflict_multiple_issues() {
expect_errors(
r#"
{
f1 {
x: a
y: c
},
f1 {
x: b
y: d
}
}
"#,
expect![[r#"
Error: cannot select different fields into the same alias `x`
╭─[ query.graphql:7:5 ]
│
3 │ x: a
│ ──┬─
│ ╰─── `x` is selected from `Type.a` here
│
7 │ x: b
│ ──┬─
│ ╰─── `x` is selected from `Type.b` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
───╯
Error: cannot select different fields into the same alias `y`
╭─[ query.graphql:8:5 ]
│
4 │ y: c
│ ──┬─
│ ╰─── `y` is selected from `Type.c` here
│
8 │ y: d
│ ──┬─
│ ╰─── `y` is selected from `Type.d` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
───╯
"#]],
);
}
#[test]
fn very_deep_conflict() {
expect_errors(
r#"
{
field {
deepField {
x: a
}
},
field {
deepField {
x: b
}
}
}
"#,
expect![[r#"
Error: cannot select different fields into the same alias `x`
╭─[ query.graphql:9:7 ]
│
4 │ x: a
│ ──┬─
│ ╰─── `x` is selected from `Type.a` here
│
9 │ x: b
│ ──┬─
│ ╰─── `x` is selected from `Type.b` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
───╯
"#]],
);
}
#[test]
fn deep_conflict_in_fragments() {
expect_errors(
r#"
{
field {
...F
}
field {
...I
}
}
fragment F on T {
x: a
...G
}
fragment G on T {
y: c
}
fragment I on T {
y: d
...J
}
fragment J on T {
x: b
}
"#,
expect![[r#"
Error: cannot select different fields into the same alias `y`
╭─[ query.graphql:14:3 ]
│
17 │ y: d
│ ──┬─
│ ╰─── `y` is selected from `T.d` here
│
├─[ query.graphql:14:3 ]
│
14 │ y: c
│ ──┬─
│ ╰─── `y` is selected from `T.c` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
Error: cannot select different fields into the same alias `x`
╭─[ query.graphql:21:3 ]
│
10 │ x: a
│ ──┬─
│ ╰─── `x` is selected from `T.a` here
│
21 │ x: b
│ ──┬─
│ ╰─── `x` is selected from `T.b` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
"#]],
);
}
}
mod return_types {
use apollo_compiler::validation::Valid;
use apollo_compiler::ExecutableDocument;
use apollo_compiler::Schema;
use expect_test::expect;
use expect_test::Expect;
use std::sync::OnceLock;
use unindent::unindent;
const RETURN_TYPES_TEST_SCHEMA: &str = r#"
interface SomeBox {
deepBox: SomeBox
unrelatedField: String
}
type StringBox implements SomeBox {
scalar: String
deepBox: StringBox
unrelatedField: String
listStringBox: [StringBox]
stringBox: StringBox
intBox: IntBox
}
type IntBox implements SomeBox {
scalar: Int
deepBox: IntBox
unrelatedField: String
listStringBox: [StringBox]
stringBox: StringBox
intBox: IntBox
}
interface NonNullStringBox1 {
scalar: String!
}
type NonNullStringBox1Impl implements SomeBox & NonNullStringBox1 {
scalar: String!
unrelatedField: String
deepBox: SomeBox
}
interface NonNullStringBox2 {
scalar: String!
}
type NonNullStringBox2Impl implements SomeBox & NonNullStringBox2 {
scalar: String!
unrelatedField: String
deepBox: SomeBox
}
type Connection {
edges: [Edge]
}
type Edge {
node: Node
}
type Node {
id: ID
name: String
}
type Query {
someBox: SomeBox
connection: Connection
}
"#;
fn test_schema() -> &'static Valid<Schema> {
static SCHEMA: OnceLock<Valid<Schema>> = OnceLock::new();
SCHEMA.get_or_init(|| {
Schema::parse_and_validate(unindent(RETURN_TYPES_TEST_SCHEMA), "schema.graphql")
.unwrap()
})
}
#[track_caller]
fn expect_valid(query: &'static str) {
let schema = test_schema();
ExecutableDocument::parse_and_validate(schema, unindent(query), "query.graphql").unwrap();
}
fn expect_errors(query: &'static str, expect: Expect) {
let schema = test_schema();
let errors =
ExecutableDocument::parse_and_validate(schema, unindent(query), "query.graphql")
.expect_err("should have errors")
.errors;
expect.assert_eq(&errors.to_string());
}
#[test]
fn conflicting_return_types_with_potential_overlap() {
expect_errors(
r#"
{
someBox {
...on IntBox {
scalar
}
...on NonNullStringBox1 {
scalar
}
}
}
"#,
expect![[r#"
Error: operation must not select different types using the same name `scalar`
╭─[ query.graphql:7:7 ]
│
4 │ scalar
│ ───┬──
│ ╰──── `scalar` is selected from `IntBox.scalar: Int` here
│
7 │ scalar
│ ───┬──
│ ╰──── `scalar` is selected from `NonNullStringBox1.scalar: String!` here
───╯
"#]],
);
}
#[test]
fn compatible_return_shapes_on_different_return_types() {
expect_valid(
r#"
{
someBox {
... on SomeBox {
deepBox {
unrelatedField
}
}
... on StringBox {
deepBox {
unrelatedField
}
}
}
}
"#,
);
}
#[test]
fn no_differing_return_types_despite_no_overlap() {
expect_errors(
r#"
{
someBox {
... on IntBox {
scalar
}
... on StringBox {
scalar
}
}
}
"#,
expect![[r#"
Error: operation must not select different types using the same name `scalar`
╭─[ query.graphql:7:7 ]
│
4 │ scalar
│ ───┬──
│ ╰──── `scalar` is selected from `IntBox.scalar: Int` here
│
7 │ scalar
│ ───┬──
│ ╰──── `scalar` is selected from `StringBox.scalar: String` here
───╯
"#]],
);
}
#[test]
fn non_exclusive_follows_exclusive() {
expect_errors(
r#"
{
someBox {
... on IntBox {
deepBox {
...X
}
}
}
someBox {
... on StringBox {
deepBox {
...Y
}
}
}
memoed: someBox {
... on IntBox {
deepBox {
...X
}
}
}
memoed: someBox {
... on StringBox {
deepBox {
...Y
}
}
}
other: someBox {
...X
}
other: someBox {
...Y
}
}
fragment X on SomeBox {
scalar
}
fragment Y on SomeBox {
scalar: unrelatedField
}
"#,
expect![[r#"
Error: type `SomeBox` does not have a field `scalar`
╭─[ query.graphql:38:3 ]
│
38 │ scalar
│ ───┬──
│ ╰──── field `scalar` selected here
│
├─[ schema.graphql:1:11 ]
│
1 │ interface SomeBox {
│ ───┬───
│ ╰───── type `SomeBox` defined here
│
│ Note: path to the field: `fragment X → scalar`
────╯
"#]],
);
}
#[test]
fn no_differing_nullability_despite_no_overlap() {
expect_errors(
r#"
{
someBox {
... on NonNullStringBox1 {
scalar
}
... on StringBox {
scalar
}
}
}
"#,
expect![[r#"
Error: operation must not select different types using the same name `scalar`
╭─[ query.graphql:7:7 ]
│
4 │ scalar
│ ───┬──
│ ╰──── `scalar` is selected from `NonNullStringBox1.scalar: String!` here
│
7 │ scalar
│ ───┬──
│ ╰──── `scalar` is selected from `StringBox.scalar: String` here
───╯
"#]],
);
}
#[test]
fn no_differing_list_despite_no_overlap() {
expect_errors(
r#"
{
someBox {
... on IntBox {
box: listStringBox {
scalar
}
}
... on StringBox {
box: stringBox {
scalar
}
}
}
}
"#,
expect![[r#"
Error: operation must not select different types using the same name `box`
╭─[ query.graphql:9:7 ]
│
4 │ ╭───▶ box: listStringBox {
┆ ┆
6 │ ├───▶ }
│ │
│ ╰─────────────── `box` is selected from `IntBox.listStringBox: [StringBox]` here
│
9 │ ╭─▶ box: stringBox {
┆ ┆
11 │ ├─▶ }
│ │
│ ╰───────────── `box` is selected from `StringBox.stringBox: StringBox` here
────╯
"#]],
);
expect_errors(
r#"
{
someBox {
... on IntBox {
box: stringBox {
scalar
}
}
... on StringBox {
box: listStringBox {
scalar
}
}
}
}
"#,
expect![[r#"
Error: operation must not select different types using the same name `box`
╭─[ query.graphql:9:7 ]
│
4 │ ╭─▶ box: stringBox {
┆ ┆
6 │ ├─▶ }
│ │
│ ╰───────────── `box` is selected from `IntBox.stringBox: StringBox` here
│
9 │ ╭───▶ box: listStringBox {
┆ ┆
11 │ ├───▶ }
│ │
│ ╰─────────────── `box` is selected from `StringBox.listStringBox: [StringBox]` here
────╯
"#]],
);
}
#[test]
fn differing_sub_fields() {
expect_errors(
r#"
{
someBox {
... on IntBox {
box: stringBox {
val: scalar
val: unrelatedField
}
}
... on StringBox {
box: stringBox {
val: scalar
}
}
}
}
"#,
expect![[r#"
Error: cannot select different fields into the same alias `val`
╭─[ query.graphql:6:9 ]
│
5 │ val: scalar
│ ─────┬─────
│ ╰─────── `val` is selected from `StringBox.scalar` here
6 │ val: unrelatedField
│ ─────────┬─────────
│ ╰─────────── `val` is selected from `StringBox.unrelatedField` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
───╯
"#]],
);
}
#[test]
fn differing_deep_return_types() {
expect_errors(
r#"
{
someBox {
... on IntBox {
box: stringBox {
scalar
}
}
... on StringBox {
box: intBox {
scalar
}
}
}
}
"#,
expect![[r#"
Error: operation must not select different types using the same name `scalar`
╭─[ query.graphql:10:9 ]
│
5 │ scalar
│ ───┬──
│ ╰──── `scalar` is selected from `StringBox.scalar: String` here
│
10 │ scalar
│ ───┬──
│ ╰──── `scalar` is selected from `IntBox.scalar: Int` here
────╯
"#]],
);
}
#[test]
fn non_conflicting_overlapping_types() {
expect_valid(
r#"
{
someBox {
... on IntBox {
scalar: unrelatedField
}
... on StringBox {
scalar
}
}
}
"#,
);
}
#[test]
fn same_scalars() {
expect_valid(
r#"
{
someBox {
...on NonNullStringBox1 {
scalar
}
...on NonNullStringBox2 {
scalar
}
}
}
"#,
);
}
#[test]
fn deep_types_including_list() {
expect_errors(
r#"
{
connection {
...edgeID
edges {
node {
id: name
}
}
}
}
fragment edgeID on Connection {
edges {
node {
id
}
}
}
"#,
expect![[r#"
Error: operation must not select different types using the same name `id`
╭─[ query.graphql:15:7 ]
│
6 │ id: name
│ ────┬───
│ ╰───── `id` is selected from `Node.name: String` here
│
15 │ id
│ ─┬
│ ╰── `id` is selected from `Node.id: ID` here
────╯
Error: cannot select different fields into the same alias `id`
╭─[ query.graphql:15:7 ]
│
6 │ id: name
│ ────┬───
│ ╰───── `id` is selected from `Node.name` here
│
15 │ id
│ ─┬
│ ╰── `id` is selected from `Node.id` here
│
│ Help: Both fields may be present on the schema type, so it's not clear which one should be used to fill the response
────╯
"#]],
);
}
#[test]
fn unknown_types() {
expect_errors(
r#"
someBox {
...on UnknownType {
scalar
}
...on NonNullStringBox2 {
scalar
}
}
}
"#,
expect![[r#"
Error: syntax error: expected definition
╭─[ query.graphql:1:3 ]
│
1 │ someBox {
│ ───┬───
│ ╰───── expected definition
───╯
Error: type condition `UnknownType` of inline fragment is not a type defined in the schema
╭─[ query.graphql:2:11 ]
│
2 │ ...on UnknownType {
│ ─────┬─────
│ ╰─────── type condition here
│
│ Note: path to the inline fragment: `query → ...`
───╯
Error: inline fragment with type condition `NonNullStringBox2` cannot be applied to `Query`
╭─[ query.graphql:5:5 ]
│
5 │ ╭─▶ ...on NonNullStringBox2 {
┆ ┆
7 │ ├─▶ }
│ │
│ ╰─────────── inline fragment cannot be applied
│
├─[ schema.graphql:57:1 ]
│
57 │ ╭─▶ type Query {
┆ ┆
60 │ ├─▶ }
│ │
│ ╰─────── type condition `NonNullStringBox2` is not assignable to this type
────╯
Error: syntax error: expected a StringValue, Name or OperationDefinition
╭─[ query.graphql:9:1 ]
│
9 │ }
│ ┬
│ ╰── expected a StringValue, Name or OperationDefinition
───╯
"#]],
);
}
}