diff --git a/src/core/blueprint/blueprint.rs b/src/core/blueprint/blueprint.rs index d5dd1a3d06..6fa14f5c67 100644 --- a/src/core/blueprint/blueprint.rs +++ b/src/core/blueprint/blueprint.rs @@ -77,6 +77,7 @@ impl Type { Type::ListType { non_null, .. } => *non_null, } } + /// checks if the type is a list pub fn is_list(&self) -> bool { matches!(self, Type::ListType { .. }) diff --git a/src/core/jit/synth/synth.rs b/src/core/jit/synth/synth.rs index f319e97b0e..387952bb30 100644 --- a/src/core/jit/synth/synth.rs +++ b/src/core/jit/synth/synth.rs @@ -42,6 +42,8 @@ where if !self.include(child) { continue; } + // TODO: in case of error set `child.output_name` to null + // and append error to response error array let val = self.iter(child, None, &DataPath::new())?; data.insert_key(&child.output_name, val); @@ -50,12 +52,6 @@ where Ok(Value::object(data)) } - /// checks if type_of is an array and value is an array - #[inline(always)] - fn is_array(type_of: &crate::core::blueprint::Type, value: &Value) -> bool { - type_of.is_list() == value.as_array().is_some() - } - #[inline(always)] fn iter( &'a self, @@ -80,8 +76,8 @@ where Data::Single(result) => { let value = result.as_ref().map_err(Clone::clone)?; - if !Self::is_array(&node.type_of, value) { - return Ok(Value::null()); + if node.type_of.is_list() != value.as_array().is_some() { + return self.node_nullable_guard(node); } self.iter_inner(node, value, data_path) } @@ -92,12 +88,26 @@ where } } None => match value { - Some(value) => self.iter_inner(node, value, data_path), - None => Ok(Value::null()), + Some(result) => self.iter_inner(node, result, data_path), + None => self.node_nullable_guard(node), }, } } + /// This guard ensures to return Null value only if node type permits it, in + /// case it does not it throws an Error + fn node_nullable_guard( + &'a self, + node: &'a Field, Value>, + ) -> Result> { + // according to GraphQL spec https://spec.graphql.org/October2021/#sec-Handling-Field-Errors + if node.type_of.is_nullable() { + Ok(Value::null()) + } else { + Err(ValidationError::ValueRequired.into()).map_err(|e| self.to_location_error(e, node)) + } + } + #[inline(always)] fn iter_inner( &'a self, @@ -105,12 +115,18 @@ where value: &'a Value, data_path: &DataPath, ) -> Result> { + // skip the field if field is not included in schema if !self.include(node) { return Ok(Value::null()); } let eval_result = if value.is_null() { - if node.type_of.is_nullable() { + // check the nullability of this type unwrapping list modifier + let is_nullable = match &node.type_of { + crate::core::blueprint::Type::NamedType { non_null, .. } => !*non_null, + crate::core::blueprint::Type::ListType { of_type, .. } => of_type.is_nullable(), + }; + if is_nullable { Ok(Value::null()) } else { Err(ValidationError::ValueRequired.into()) diff --git a/tests/core/snapshots/test-required-fields.md_0.snap b/tests/core/snapshots/test-required-fields.md_0.snap new file mode 100644 index 0000000000..c526e94e7a --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_0.snap @@ -0,0 +1,18 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "basicPresent": { + "id": 1, + "bar": "bar_1" + } + } + } +} diff --git a/tests/core/snapshots/test-required-fields.md_1.snap b/tests/core/snapshots/test-required-fields.md_1.snap new file mode 100644 index 0000000000..93309c9793 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_1.snap @@ -0,0 +1,28 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": null, + "errors": [ + { + "message": "internal: non-null types require a return value", + "locations": [ + { + "line": 1, + "column": 32 + } + ], + "path": [ + "basicFieldMissing", + "bar" + ] + } + ] + } +} diff --git a/tests/core/snapshots/test-required-fields.md_10.snap b/tests/core/snapshots/test-required-fields.md_10.snap new file mode 100644 index 0000000000..1621f25fbc --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_10.snap @@ -0,0 +1,24 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "innerPresent": [ + { + "id": 1, + "bar": "bar_1" + }, + { + "id": 2, + "bar": "bar_2" + } + ] + } + } +} diff --git a/tests/core/snapshots/test-required-fields.md_11.snap b/tests/core/snapshots/test-required-fields.md_11.snap new file mode 100644 index 0000000000..7cb54826dc --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_11.snap @@ -0,0 +1,15 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "innerMissing": null + } + } +} diff --git a/tests/core/snapshots/test-required-fields.md_12.snap b/tests/core/snapshots/test-required-fields.md_12.snap new file mode 100644 index 0000000000..b5d4689146 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_12.snap @@ -0,0 +1,29 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": null, + "errors": [ + { + "message": "internal: non-null types require a return value", + "locations": [ + { + "line": 1, + "column": 32 + } + ], + "path": [ + "innerFieldMissing", + 1, + "bar" + ] + } + ] + } +} diff --git a/tests/core/snapshots/test-required-fields.md_13.snap b/tests/core/snapshots/test-required-fields.md_13.snap new file mode 100644 index 0000000000..65e3a4af79 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_13.snap @@ -0,0 +1,29 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": null, + "errors": [ + { + "message": "internal: non-null types require a return value", + "locations": [ + { + "line": 1, + "column": 29 + } + ], + "path": [ + "innerEntryMissing", + 1, + "id" + ] + } + ] + } +} diff --git a/tests/core/snapshots/test-required-fields.md_14.snap b/tests/core/snapshots/test-required-fields.md_14.snap new file mode 100644 index 0000000000..c5e590a3a4 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_14.snap @@ -0,0 +1,24 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "outerPresent": [ + { + "id": 1, + "bar": "bar_1" + }, + { + "id": 2, + "bar": "bar_2" + } + ] + } + } +} diff --git a/tests/core/snapshots/test-required-fields.md_15.snap b/tests/core/snapshots/test-required-fields.md_15.snap new file mode 100644 index 0000000000..e96d3c87a0 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_15.snap @@ -0,0 +1,27 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": null, + "errors": [ + { + "message": "internal: non-null types require a return value", + "locations": [ + { + "line": 1, + "column": 9 + } + ], + "path": [ + "outerMissing" + ] + } + ] + } +} diff --git a/tests/core/snapshots/test-required-fields.md_16.snap b/tests/core/snapshots/test-required-fields.md_16.snap new file mode 100644 index 0000000000..f04b340dcd --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_16.snap @@ -0,0 +1,29 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": null, + "errors": [ + { + "message": "internal: non-null types require a return value", + "locations": [ + { + "line": 1, + "column": 32 + } + ], + "path": [ + "outerFieldMissing", + 1, + "bar" + ] + } + ] + } +} diff --git a/tests/core/snapshots/test-required-fields.md_17.snap b/tests/core/snapshots/test-required-fields.md_17.snap new file mode 100644 index 0000000000..b6f7294e44 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_17.snap @@ -0,0 +1,29 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": null, + "errors": [ + { + "message": "internal: non-null types require a return value", + "locations": [ + { + "line": 1, + "column": 29 + } + ], + "path": [ + "outerEntryMissing", + 1, + "id" + ] + } + ] + } +} diff --git a/tests/core/snapshots/test-required-fields.md_18.snap b/tests/core/snapshots/test-required-fields.md_18.snap new file mode 100644 index 0000000000..f3832a962b --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_18.snap @@ -0,0 +1,24 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "nonePresent": [ + { + "id": 1, + "bar": "bar_1" + }, + { + "id": 2, + "bar": "bar_2" + } + ] + } + } +} diff --git a/tests/core/snapshots/test-required-fields.md_19.snap b/tests/core/snapshots/test-required-fields.md_19.snap new file mode 100644 index 0000000000..52b8480df5 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_19.snap @@ -0,0 +1,15 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "noneMissing": null + } + } +} diff --git a/tests/core/snapshots/test-required-fields.md_2.snap b/tests/core/snapshots/test-required-fields.md_2.snap new file mode 100644 index 0000000000..7f404ec366 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_2.snap @@ -0,0 +1,27 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": null, + "errors": [ + { + "message": "internal: non-null types require a return value", + "locations": [ + { + "line": 1, + "column": 9 + } + ], + "path": [ + "basicMissing" + ] + } + ] + } +} diff --git a/tests/core/snapshots/test-required-fields.md_20.snap b/tests/core/snapshots/test-required-fields.md_20.snap new file mode 100644 index 0000000000..8ec7714e13 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_20.snap @@ -0,0 +1,29 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": null, + "errors": [ + { + "message": "internal: non-null types require a return value", + "locations": [ + { + "line": 1, + "column": 31 + } + ], + "path": [ + "noneFieldMissing", + 1, + "bar" + ] + } + ] + } +} diff --git a/tests/core/snapshots/test-required-fields.md_21.snap b/tests/core/snapshots/test-required-fields.md_21.snap new file mode 100644 index 0000000000..0bee8203d9 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_21.snap @@ -0,0 +1,29 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": null, + "errors": [ + { + "message": "internal: non-null types require a return value", + "locations": [ + { + "line": 1, + "column": 28 + } + ], + "path": [ + "noneEntryMissing", + 1, + "id" + ] + } + ] + } +} diff --git a/tests/core/snapshots/test-required-fields.md_3.snap b/tests/core/snapshots/test-required-fields.md_3.snap new file mode 100644 index 0000000000..635314f4a5 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_3.snap @@ -0,0 +1,18 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "relaxedPresent": { + "id": 1, + "bar": "bar_1" + } + } + } +} diff --git a/tests/core/snapshots/test-required-fields.md_4.snap b/tests/core/snapshots/test-required-fields.md_4.snap new file mode 100644 index 0000000000..39634a5fb0 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_4.snap @@ -0,0 +1,28 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": null, + "errors": [ + { + "message": "internal: non-null types require a return value", + "locations": [ + { + "line": 1, + "column": 34 + } + ], + "path": [ + "relaxedFieldMissing", + "bar" + ] + } + ] + } +} diff --git a/tests/core/snapshots/test-required-fields.md_5.snap b/tests/core/snapshots/test-required-fields.md_5.snap new file mode 100644 index 0000000000..9ecc8ec71f --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_5.snap @@ -0,0 +1,15 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "relaxedMissing": null + } + } +} diff --git a/tests/core/snapshots/test-required-fields.md_6.snap b/tests/core/snapshots/test-required-fields.md_6.snap new file mode 100644 index 0000000000..2355e1e2bb --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_6.snap @@ -0,0 +1,24 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "fullPresent": [ + { + "id": 1, + "bar": "bar_1" + }, + { + "id": 2, + "bar": "bar_2" + } + ] + } + } +} diff --git a/tests/core/snapshots/test-required-fields.md_7.snap b/tests/core/snapshots/test-required-fields.md_7.snap new file mode 100644 index 0000000000..79647f46ac --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_7.snap @@ -0,0 +1,27 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": null, + "errors": [ + { + "message": "internal: non-null types require a return value", + "locations": [ + { + "line": 1, + "column": 9 + } + ], + "path": [ + "fullMissing" + ] + } + ] + } +} diff --git a/tests/core/snapshots/test-required-fields.md_8.snap b/tests/core/snapshots/test-required-fields.md_8.snap new file mode 100644 index 0000000000..d0b7ef5c84 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_8.snap @@ -0,0 +1,29 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": null, + "errors": [ + { + "message": "internal: non-null types require a return value", + "locations": [ + { + "line": 1, + "column": 31 + } + ], + "path": [ + "fullFieldMissing", + 1, + "bar" + ] + } + ] + } +} diff --git a/tests/core/snapshots/test-required-fields.md_9.snap b/tests/core/snapshots/test-required-fields.md_9.snap new file mode 100644 index 0000000000..392bb18994 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_9.snap @@ -0,0 +1,29 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": null, + "errors": [ + { + "message": "internal: non-null types require a return value", + "locations": [ + { + "line": 1, + "column": 28 + } + ], + "path": [ + "fullEntryMissing", + 1, + "id" + ] + } + ] + } +} diff --git a/tests/core/snapshots/test-required-fields.md_client.snap b/tests/core/snapshots/test-required-fields.md_client.snap new file mode 100644 index 0000000000..0d38a402d0 --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_client.snap @@ -0,0 +1,73 @@ +--- +source: tests/core/spec.rs +expression: formatted +--- +scalar Bytes + +scalar Date + +scalar DateTime + +scalar Email + +scalar Empty + +type Foo { + bar: String! + id: Int! +} + +scalar Int128 + +scalar Int16 + +scalar Int32 + +scalar Int64 + +scalar Int8 + +scalar JSON + +scalar PhoneNumber + +type Query { + basicFieldMissing: Foo! + basicMissing: Foo! + basicPresent: Foo! + fullEntryMissing: [Foo!]! + fullFieldMissing: [Foo!]! + fullMissing: [Foo!]! + fullPresent: [Foo!]! + innerEntryMissing: [Foo!] + innerFieldMissing: [Foo!] + innerMissing: [Foo!] + innerPresent: [Foo!] + noneEntryMissing: [Foo] + noneFieldMissing: [Foo] + noneMissing: [Foo] + nonePresent: [Foo] + outerEntryMissing: [Foo]! + outerFieldMissing: [Foo]! + outerMissing: [Foo]! + outerPresent: [Foo]! + relaxedFieldMissing: Foo + relaxedMissing: Foo + relaxedPresent: Foo +} + +scalar UInt128 + +scalar UInt16 + +scalar UInt32 + +scalar UInt64 + +scalar UInt8 + +scalar Url + +schema { + query: Query +} diff --git a/tests/core/snapshots/test-required-fields.md_merged.snap b/tests/core/snapshots/test-required-fields.md_merged.snap new file mode 100644 index 0000000000..3a4bb400ff --- /dev/null +++ b/tests/core/snapshots/test-required-fields.md_merged.snap @@ -0,0 +1,37 @@ +--- +source: tests/core/spec.rs +expression: formatter +--- +schema @server @upstream(baseURL: "http://jsonplaceholder.typicode.com") { + query: Query +} + +type Foo { + bar: String! + id: Int! +} + +type Query { + basicFieldMissing: Foo! @http(path: "/basic-field-missing") + basicMissing: Foo! @http(path: "/basic-missing") + basicPresent: Foo! @http(path: "/basic-present") + fullEntryMissing: [Foo!]! @http(path: "/full-entry-missing") + fullFieldMissing: [Foo!]! @http(path: "/full-field-missing") + fullMissing: [Foo!]! @http(path: "/full-missing") + fullPresent: [Foo!]! @http(path: "/full-present") + innerEntryMissing: [Foo!] @http(path: "/inner-entry-missing") + innerFieldMissing: [Foo!] @http(path: "/inner-field-missing") + innerMissing: [Foo!] @http(path: "/inner-missing") + innerPresent: [Foo!] @http(path: "/inner-present") + noneEntryMissing: [Foo] @http(path: "/none-entry-missing") + noneFieldMissing: [Foo] @http(path: "/none-field-missing") + noneMissing: [Foo] @http(path: "/none-missing") + nonePresent: [Foo] @http(path: "/none-present") + outerEntryMissing: [Foo]! @http(path: "/outer-entry-missing") + outerFieldMissing: [Foo]! @http(path: "/outer-field-missing") + outerMissing: [Foo]! @http(path: "/outer-missing") + outerPresent: [Foo]! @http(path: "/outer-present") + relaxedFieldMissing: Foo @http(path: "/relaxed-field-missing") + relaxedMissing: Foo @http(path: "/relaxed-missing") + relaxedPresent: Foo @http(path: "/relaxed-present") +} diff --git a/tests/execution/test-required-fields.md b/tests/execution/test-required-fields.md new file mode 100644 index 0000000000..115ca86075 --- /dev/null +++ b/tests/execution/test-required-fields.md @@ -0,0 +1,358 @@ +# Test API + +```graphql @config +schema @server @upstream(baseURL: "http://jsonplaceholder.typicode.com") { + query: Query +} + +type Query { + basicPresent: Foo! @http(path: "/basic-present") + basicFieldMissing: Foo! @http(path: "/basic-field-missing") + basicMissing: Foo! @http(path: "/basic-missing") + relaxedPresent: Foo @http(path: "/relaxed-present") + relaxedFieldMissing: Foo @http(path: "/relaxed-field-missing") + relaxedMissing: Foo @http(path: "/relaxed-missing") + fullPresent: [Foo!]! @http(path: "/full-present") + fullMissing: [Foo!]! @http(path: "/full-missing") + fullFieldMissing: [Foo!]! @http(path: "/full-field-missing") + fullEntryMissing: [Foo!]! @http(path: "/full-entry-missing") + innerPresent: [Foo!] @http(path: "/inner-present") + innerMissing: [Foo!] @http(path: "/inner-missing") + innerFieldMissing: [Foo!] @http(path: "/inner-field-missing") + innerEntryMissing: [Foo!] @http(path: "/inner-entry-missing") + outerPresent: [Foo]! @http(path: "/outer-present") + outerMissing: [Foo]! @http(path: "/outer-missing") + outerFieldMissing: [Foo]! @http(path: "/outer-field-missing") + outerEntryMissing: [Foo]! @http(path: "/outer-entry-missing") + nonePresent: [Foo] @http(path: "/none-present") + noneMissing: [Foo] @http(path: "/none-missing") + noneFieldMissing: [Foo] @http(path: "/none-field-missing") + noneEntryMissing: [Foo] @http(path: "/none-entry-missing") +} + +type Foo { + id: Int! + bar: String! +} +``` + +```yml @mock +# this does not fail +- request: + method: GET + url: http://jsonplaceholder.typicode.com/basic-present + response: + status: 200 + body: + id: 1 + bar: bar_1 + +# this fails +- request: + method: GET + url: http://jsonplaceholder.typicode.com/basic-field-missing + response: + status: 200 + body: + id: 1 + bar: null + +# this fails +- request: + method: GET + url: http://jsonplaceholder.typicode.com/basic-missing + response: + status: 200 + body: null + +# this does not fail +- request: + method: GET + url: http://jsonplaceholder.typicode.com/relaxed-present + response: + status: 200 + body: + id: 1 + bar: bar_1 + +# this fails +- request: + method: GET + url: http://jsonplaceholder.typicode.com/relaxed-field-missing + response: + status: 200 + body: + id: 1 + bar: null + +# this does not fail +- request: + method: GET + url: http://jsonplaceholder.typicode.com/relaxed-missing + response: + status: 200 + body: null + +# this does not fail +- request: + method: GET + url: http://jsonplaceholder.typicode.com/full-present + response: + status: 200 + body: + - id: 1 + bar: bar_1 + - id: 2 + bar: bar_2 + +# this fails +- request: + method: GET + url: http://jsonplaceholder.typicode.com/full-missing + response: + status: 200 + body: null + +# this fails +- request: + method: GET + url: http://jsonplaceholder.typicode.com/full-field-missing + response: + status: 200 + body: + - id: 1 + bar: bar_1 + - id: 2 + bar: null + +# this fails +- request: + method: GET + url: http://jsonplaceholder.typicode.com/full-entry-missing + response: + status: 200 + body: + - id: 1 + bar: bar_1 + - null + +# this does not fail +- request: + method: GET + url: http://jsonplaceholder.typicode.com/inner-present + response: + status: 200 + body: + - id: 1 + bar: bar_1 + - id: 2 + bar: bar_2 + +# this does not fail +- request: + method: GET + url: http://jsonplaceholder.typicode.com/inner-missing + response: + status: 200 + body: null + +# this fails +- request: + method: GET + url: http://jsonplaceholder.typicode.com/inner-field-missing + response: + status: 200 + body: + - id: 1 + bar: bar_1 + - id: 2 + bar: null + +# this fails +- request: + method: GET + url: http://jsonplaceholder.typicode.com/inner-entry-missing + response: + status: 200 + body: + - id: 1 + bar: bar_1 + - null + +# this does not fail +- request: + method: GET + url: http://jsonplaceholder.typicode.com/outer-present + response: + status: 200 + body: + - id: 1 + bar: bar_1 + - id: 2 + bar: bar_2 + +# this fails +- request: + method: GET + url: http://jsonplaceholder.typicode.com/outer-missing + response: + status: 200 + body: null + +# this fails +- request: + method: GET + url: http://jsonplaceholder.typicode.com/outer-field-missing + response: + status: 200 + body: + - id: 1 + bar: bar_1 + - id: 2 + bar: null + +# this does not fail +- request: + method: GET + url: http://jsonplaceholder.typicode.com/outer-entry-missing + response: + status: 200 + body: + - id: 1 + bar: bar_1 + - null + +# this does not fail +- request: + method: GET + url: http://jsonplaceholder.typicode.com/none-present + response: + status: 200 + body: + - id: 1 + bar: bar_1 + - id: 2 + bar: bar_2 + +# this does not fail +- request: + method: GET + url: http://jsonplaceholder.typicode.com/none-missing + response: + status: 200 + body: null + +# this fails +- request: + method: GET + url: http://jsonplaceholder.typicode.com/none-field-missing + response: + status: 200 + body: + - id: 1 + bar: bar_1 + - id: 2 + bar: null + +# this does not fail +- request: + method: GET + url: http://jsonplaceholder.typicode.com/none-entry-missing + response: + status: 200 + body: + - id: 1 + bar: bar_1 + - null +``` + +```yml @test +- method: POST + url: http://localhost:8080/graphql + body: + query: query { basicPresent { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { basicFieldMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { basicMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { relaxedPresent { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { relaxedFieldMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { relaxedMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { fullPresent { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { fullMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { fullFieldMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { fullEntryMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { innerPresent { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { innerMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { innerFieldMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { innerEntryMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { outerPresent { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { outerMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { outerFieldMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { outerEntryMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { nonePresent { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { noneMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { noneFieldMissing { id bar } } +- method: POST + url: http://localhost:8080/graphql + body: + query: query { noneEntryMissing { id bar } } +```