Skip to content

Commit 5d22d1c

Browse files
Monica Limin Tangfacebook-github-bot
authored andcommitted
Show a helpful error if a resolver returns an interface with no implementors (#4428)
Summary: **Context:** Without this we generate a broken Flow type. We will eventually support Relay Resolvers that return interfaces. Today they are not permitted in the common case. However, once we do allow them, we must ensure that the schema includes at least once concrete type that implements that interface. Without that property, we will generate invalid Flow types. **This change** Adds additional validation for implementors of client-defined interfaces if they are being used as a return type for RelayResolvers and tests. Also updates an existing test to have it only test the existing validation for blocking interfaces as a return type. Pull Request resolved: #4428 Test Plan: `./scripts/update-fixtures.sh` `cargo test` specifically: `cargo test -p relay-transforms --test client_edges_test` `cargo test -p relay-compiler --test relay_compiler_compile_relay_artifacts_test relay_resolver_edge_to_interface_with_no_implementors` `cargo test -p relay-compiler --test relay_compiler_compile_relay_artifacts_test relay_resolver_edge_to_interface_with_child_interface_and_no_implementors` Reviewed By: captbaritone Differential Revision: D48852264 Pulled By: monicatang fbshipit-source-id: 5d9ebbee4423eef8f1aaf4dd031e3e3227675f19
1 parent a3fbc12 commit 5d22d1c

10 files changed

+167
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
==================================== INPUT ====================================
2+
# expected-to-throw
3+
query relayResolverEdgeToInterfaceWithChildInterfaceAndNoImplementorsQuery {
4+
resolver_field {
5+
name
6+
}
7+
}
8+
9+
# %extensions%
10+
11+
"""
12+
An interface with no concrete implementors
13+
"""
14+
interface SomeInterface {
15+
name: String
16+
}
17+
18+
interface ChildInterface implements SomeInterface {
19+
name: String
20+
age: Int
21+
}
22+
23+
extend type Query {
24+
resolver_field: SomeInterface
25+
@relay_resolver(import_path: "./path/to/Resolver.js")
26+
}
27+
==================================== ERROR ====================================
28+
✖︎ Client Edges that reference client-defined interface types are not currently supported in Relay.
29+
30+
relay-resolver-edge-to-interface-with-child-interface-and-no-implementors.graphql:3:3
31+
2 │ query relayResolverEdgeToInterfaceWithChildInterfaceAndNoImplementorsQuery {
32+
3 │ resolver_field {
33+
│ ^^^^^^^^^^^^^^
34+
4 │ name
35+
36+
37+
✖︎ No types implement the client interface SomeInterface. Interfaces returned by a @RelayResolver must have at least one concrete implementation.
38+
39+
<generated>:2:44
40+
1 │ # expected-to-throw
41+
2 │ query relayResolverEdgeToInterfaceWithChildInterfaceAndNoImplementorsQuery {
42+
│ ^^^^^^^^^^^^^
43+
3 │ resolver_field {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# expected-to-throw
2+
query relayResolverEdgeToInterfaceWithChildInterfaceAndNoImplementorsQuery {
3+
resolver_field {
4+
name
5+
}
6+
}
7+
8+
# %extensions%
9+
10+
"""
11+
An interface with no concrete implementors
12+
"""
13+
interface SomeInterface {
14+
name: String
15+
}
16+
17+
interface ChildInterface implements SomeInterface {
18+
name: String
19+
age: Int
20+
}
21+
22+
extend type Query {
23+
resolver_field: SomeInterface
24+
@relay_resolver(import_path: "./path/to/Resolver.js")
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
==================================== INPUT ====================================
2+
# expected-to-throw
3+
query relayResolverEdgeToInterfaceWithNoImplementorsQuery {
4+
resolver_field {
5+
name
6+
}
7+
}
8+
9+
# %extensions%
10+
11+
"""
12+
An interface with no implementors
13+
"""
14+
interface SomeInterface {
15+
name: String
16+
}
17+
18+
extend type Query {
19+
resolver_field: SomeInterface
20+
@relay_resolver(import_path: "./path/to/Resolver.js")
21+
}
22+
==================================== ERROR ====================================
23+
✖︎ Client Edges that reference client-defined interface types are not currently supported in Relay.
24+
25+
relay-resolver-edge-to-interface-with-no-implementors.graphql:3:3
26+
2 │ query relayResolverEdgeToInterfaceWithNoImplementorsQuery {
27+
3 │ resolver_field {
28+
│ ^^^^^^^^^^^^^^
29+
4 │ name
30+
31+
32+
✖︎ No types implement the client interface SomeInterface. Interfaces returned by a @RelayResolver must have at least one concrete implementation.
33+
34+
<generated>:2:35
35+
1 │ # expected-to-throw
36+
2 │ query relayResolverEdgeToInterfaceWithNoImplementorsQuery {
37+
│ ^^^^^^^^^^^^^
38+
3 │ resolver_field {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# expected-to-throw
2+
query relayResolverEdgeToInterfaceWithNoImplementorsQuery {
3+
resolver_field {
4+
name
5+
}
6+
}
7+
8+
# %extensions%
9+
10+
"""
11+
An interface with no implementors
12+
"""
13+
interface SomeInterface {
14+
name: String
15+
}
16+
17+
extend type Query {
18+
resolver_field: SomeInterface
19+
@relay_resolver(import_path: "./path/to/Resolver.js")
20+
}

compiler/crates/relay-compiler/tests/compile_relay_artifacts_test.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<4c83edb5a97709b48ab136e569a1bc3e>>
7+
* @generated SignedSource<<c12ec82876dcc0420715ee575dae6f2f>>
88
*/
99

1010
mod compile_relay_artifacts;
@@ -1104,6 +1104,20 @@ fn relay_resolver_backing_client_edge() {
11041104
test_fixture(transform_fixture, "relay-resolver-backing-client-edge.graphql", "compile_relay_artifacts/fixtures/relay-resolver-backing-client-edge.expected", input, expected);
11051105
}
11061106

1107+
#[test]
1108+
fn relay_resolver_edge_to_interface_with_child_interface_and_no_implementors() {
1109+
let input = include_str!("compile_relay_artifacts/fixtures/relay-resolver-edge-to-interface-with-child-interface-and-no-implementors.graphql");
1110+
let expected = include_str!("compile_relay_artifacts/fixtures/relay-resolver-edge-to-interface-with-child-interface-and-no-implementors.expected");
1111+
test_fixture(transform_fixture, "relay-resolver-edge-to-interface-with-child-interface-and-no-implementors.graphql", "compile_relay_artifacts/fixtures/relay-resolver-edge-to-interface-with-child-interface-and-no-implementors.expected", input, expected);
1112+
}
1113+
1114+
#[test]
1115+
fn relay_resolver_edge_to_interface_with_no_implementors() {
1116+
let input = include_str!("compile_relay_artifacts/fixtures/relay-resolver-edge-to-interface-with-no-implementors.graphql");
1117+
let expected = include_str!("compile_relay_artifacts/fixtures/relay-resolver-edge-to-interface-with-no-implementors.expected");
1118+
test_fixture(transform_fixture, "relay-resolver-edge-to-interface-with-no-implementors.graphql", "compile_relay_artifacts/fixtures/relay-resolver-edge-to-interface-with-no-implementors.expected", input, expected);
1119+
}
1120+
11071121
#[test]
11081122
fn relay_resolver_es_modules() {
11091123
let input = include_str!("compile_relay_artifacts/fixtures/relay-resolver-es-modules.graphql");

compiler/crates/relay-transforms/src/client_edges.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,18 @@ impl<'program, 'sc> ClientEdgesTransform<'program, 'sc> {
363363
}
364364

365365
match edge_to_type {
366-
Type::Interface(_) => {
366+
Type::Interface(interface_id) => {
367+
let interface = schema.interface(interface_id);
368+
let implementing_objects =
369+
interface.recursively_implementing_objects(Arc::as_ref(schema));
370+
if implementing_objects.is_empty() {
371+
self.errors.push(Diagnostic::error(
372+
ValidationMessage::RelayResolverClientInterfaceMustBeImplemented {
373+
interface_name: interface.name.item,
374+
},
375+
interface.name.location,
376+
));
377+
}
367378
if !has_output_type(resolver_directive) {
368379
self.errors.push(Diagnostic::error(
369380
ValidationMessage::ClientEdgeToClientInterface,

compiler/crates/relay-transforms/src/errors.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ pub enum ValidationMessage {
168168
},
169169

170170
#[error(
171-
"No types implement the client interface {interface_name}. For a client interface to be used as a @RelayResolver @outputType, at least one Object type must implement the interface."
171+
"No types implement the client interface {interface_name}. Interfaces returned by a @RelayResolver must have at least one concrete implementation."
172172
)]
173173
RelayResolverClientInterfaceMustBeImplemented { interface_name: InterfaceName },
174174

compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-interface.invalid.expected

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ interface ClientOnlyInterface implements Node {
1616
id: ID!
1717
}
1818

19+
# Add a concrete type so that we don't trigger an unrelated compiler error.
20+
type BestFriend implements ClientOnlyInterface {
21+
id: ID!
22+
}
23+
1924
extend type User {
2025
best_friend: ClientOnlyInterface @relay_resolver(fragment_name: "BestFriendResolverFragment_name", import_path: "BestFriendResolver")
2126
}

compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-interface.invalid.graphql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ interface ClientOnlyInterface implements Node {
1515
id: ID!
1616
}
1717

18+
# Add a concrete type so that we don't trigger an unrelated compiler error.
19+
type BestFriend implements ClientOnlyInterface {
20+
id: ID!
21+
}
22+
1823
extend type User {
1924
best_friend: ClientOnlyInterface @relay_resolver(fragment_name: "BestFriendResolverFragment_name", import_path: "BestFriendResolver")
2025
}

compiler/crates/relay-transforms/tests/generate_relay_resolvers_operations_for_nested_objects/fixtures/output-type-with-unimplemented-interface.invalid.expected

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ extend type User {
3232
top_level: IStageName @relay_resolver(fragment_name: "PopStarNameResolverFragment_name", import_path: "PopStarNameResolver", has_output_type: true)
3333
}
3434
==================================== ERROR ====================================
35-
✖︎ No types implement the client interface IStageName. For a client interface to be used as a @RelayResolver @outputType, at least one Object type must implement the interface.
35+
✖︎ No types implement the client interface IStageName. Interfaces returned by a @RelayResolver must have at least one concrete implementation.
3636

3737
output-type-with-unimplemented-interface.invalid.graphql:7:11
3838
6 │
@@ -41,7 +41,7 @@ extend type User {
4141
8 │ value: String
4242

4343

44-
✖︎ No types implement the client interface IStageName. For a client interface to be used as a @RelayResolver @outputType, at least one Object type must implement the interface.
44+
✖︎ No types implement the client interface IStageName. Interfaces returned by a @RelayResolver must have at least one concrete implementation.
4545

4646
output-type-with-unimplemented-interface.invalid.graphql:7:11
4747
6 │
@@ -50,7 +50,7 @@ extend type User {
5050
8 │ value: String
5151

5252

53-
✖︎ No types implement the client interface IStageName. For a client interface to be used as a @RelayResolver @outputType, at least one Object type must implement the interface.
53+
✖︎ No types implement the client interface IStageName. Interfaces returned by a @RelayResolver must have at least one concrete implementation.
5454

5555
output-type-with-unimplemented-interface.invalid.graphql:7:11
5656
6 │

0 commit comments

Comments
 (0)