Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test @inaccessible in response formatting #1033

Merged
merged 6 commits into from
May 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ Studio reporting now aggregates at the Router only. This architectural change al
### Add message to logs when Apollo usage reporting is enabled [PR #1029](https://github.com/apollographql/router/pull/1029)
When studio reporting is enabled the user is notified in the router logs that data is sent to Apollo.

### Check that an object's `__typename` is part of the schema [PR #1033](https://github.com/apollographql/router/pull/1033)
In case a subgraph returns an object with a `__typename` field referring to a type that is not in the API schema, as with usage of the `@inaccessible` directive on object types, the whole object should be replaced with a `null`.

## 🛠 Maintenance ( :hammer_and_wrench: )
## 📚 Documentation ( :books: )
### Add documentation for the endpoint configuration in server ([PR #1000](https://github.com/apollographql/router/pull/1000))
Expand Down
211 changes: 207 additions & 4 deletions apollo-router-core/src/spec/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,15 @@ impl Query {

match input {
Value::Object(ref mut input_object) => {
if let Some(input_type) =
input_object.get(TYPENAME).and_then(|val| val.as_str())
{
if !schema.object_types.contains_key(input_type) {
*output = Value::Null;
return Ok(());
}
}

if output.is_null() {
*output = Value::Object(Object::default());
}
Expand Down Expand Up @@ -847,17 +856,18 @@ mod tests {
}};

($schema:expr, $query:expr, $response:expr, $operation:expr, $variables:expr, $expected:expr $(,)?) => {{
let schema: Schema = with_supergraph_boilerplate($schema)
.parse()
let schema = with_supergraph_boilerplate($schema)
.parse::<Schema>()
.expect("could not parse schema");
let api_schema = schema.api_schema();
let query = Query::parse($query, &schema).expect("could not parse query");
let mut response = Response::builder().data($response.clone()).build();

query.format_response(
&mut response,
$operation,
$variables.as_object().unwrap().clone(),
&schema,
api_schema,
);
assert_eq_and_ordered!(response.data.as_ref().unwrap(), &$expected);
}};
Expand All @@ -869,11 +879,14 @@ mod tests {
r#"
schema
@core(feature: "https://specs.apollo.dev/core/v0.1")
@core(feature: "https://specs.apollo.dev/join/v0.1") {
@core(feature: "https://specs.apollo.dev/join/v0.1")
@core(feature: "https://specs.apollo.dev/inaccessible/v0.1")
{
query: Query
}
directive @core(feature: String!) repeatable on SCHEMA
directive @join__graph(name: String!, url: String!) on ENUM_VALUE
directive @inaccessible on OBJECT | FIELD_DEFINITION | INTERFACE | UNION
enum join__Graph {
TEST @join__graph(name: "test", url: "http://localhost:4001/graphql")
}
Expand All @@ -883,6 +896,78 @@ mod tests {
)
}

macro_rules! assert_format_response_fed2 {
($schema:expr, $query:expr, $response:expr, $operation:expr, $expected:expr $(,)?) => {{
assert_format_response_fed2!(
$schema,
$query,
$response,
$operation,
Value::Object(Object::default()),
$expected
);
}};

($schema:expr, $query:expr, $response:expr, $operation:expr, $variables:expr, $expected:expr $(,)?) => {{
let schema = with_supergraph_boilerplate_fed2($schema)
.parse::<Schema>()
.expect("could not parse schema");
let api_schema = schema.api_schema();
println!("generated API chema:\n{}", api_schema.as_str());
let query = Query::parse($query, &schema).expect("could not parse query");
let mut response = Response::builder().data($response.clone()).build();

query.format_response(
&mut response,
$operation,
$variables.as_object().unwrap().clone(),
api_schema,
);
assert_eq_and_ordered!(response.data.as_ref().unwrap(), &$expected);
}};
}

fn with_supergraph_boilerplate_fed2(content: &str) -> String {
format!(
"{}\n{}",
r#"
schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/join/v0.2", for: EXECUTION)
@link(url: "https://specs.apollo.dev/inaccessible/v0.2", for: SECURITY)
{
query: Query
}

directive @join__field(graph: join__Graph!, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @join__graph(name: String!, url: String!) on ENUM_VALUE
directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE
directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ENUM | ENUM_VALUE | SCALAR | INPUT_OBJECT | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION

scalar join__FieldSet
scalar link__Import
enum link__Purpose {
"""
`SECURITY` features provide metadata necessary to securely resolve fields.
"""
SECURITY

"""
`EXECUTION` features provide metadata necessary for operation execution.
"""
EXECUTION
}

enum join__Graph {
TEST @join__graph(name: "test", url: "http://localhost:4001/graphql")
}
"#,
content
)
}

#[test]
fn reformat_response_data_field() {
assert_format_response!(
Expand Down Expand Up @@ -4024,4 +4109,122 @@ mod tests {
}},
);
}

#[test]
fn inaccessible_on_interface() {
let schema = "type Query
{
test_interface: Interface
test_union: U
test_enum: E
}

type Object implements Interface @inaccessible {
foo: String
other: String
}

type Object2 implements Interface {
foo: String
other: String @inaccessible
}

interface Interface {
foo: String
}

type A @inaccessible {
common: String
a: String
}

type B {
common: String
b: String
}

union U = A | B

enum E {
X @inaccessible
Y @inaccessible
Z
}
";

assert_format_response_fed2!(
schema,
"query {
test_interface {
__typename
foo
}

test_interface2: test_interface {
__typename
foo
}

test_union {
...on B {
__typename
common
}
}

test_union2: test_union {
...on B {
__typename
common
}
}

test_enum
test_enum2: test_enum
}",
json! {{
"test_interface": {
"__typename": "Object",
"foo": "bar",
"other": "a"
},

"test_interface2": {
"__typename": "Object2",
"foo": "bar",
"other": "a"
},

"test_union": {
"__typename": "A",
"common": "hello",
"a": "A"
},

"test_union2": {
"__typename": "B",
"common": "hello",
"b": "B"
},

"test_enum": "X",
"test_enum2": "Z"
}},
None,
json! {{
"test_interface": null,
"test_interface2": {
"__typename": "Object2",
"foo": "bar",
},
"test_union": null,
"test_union2": {
"__typename": "B",
"common": "hello",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, how come b is not output for union2?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be my mistake, or it could be a separate issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because the query does not call for this field:

test_union2: test_union {
    ...on B {
        __typename
        common
    }
}

},
"test_enum": null,
"test_enum2": "Z"
}},
);
}
}