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

[relay] Show a helpful error if a resolver returns an interface with no implementors #4428

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
==================================== INPUT ====================================
# expected-to-throw
query relayResolverEdgeToInterfaceWithNoImplementorsQuery {
resolver_field {
name
}
}

# %extensions%

"""
An interface with no implementors
"""
interface SomeInterface {
name: String
}

extend type Query {
resolver_field: SomeInterface
@relay_resolver(import_path: "./path/to/Resolver.js")
}
==================================== ERROR ====================================
✖︎ Client Edges that reference client-defined interface types are not currently supported in Relay.

relay-resolver-edge-to-interface-with-no-implementors.graphql:3:3
2 │ query relayResolverEdgeToInterfaceWithNoImplementorsQuery {
3 │ resolver_field {
│ ^^^^^^^^^^^^^^
4 │ name


✖︎ No types implement the client interface SomeInterface. For a client interface to be used as a @RelayResolver @outputType, at least one Object type must implement the interface.
monicatang marked this conversation as resolved.
Show resolved Hide resolved

<generated>:2:35
1 │ # expected-to-throw
2 │ query relayResolverEdgeToInterfaceWithNoImplementorsQuery {
│ ^^^^^^^^^^^^^
Copy link
Contributor

Choose a reason for hiding this comment

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

This underline being in the wrong place is not a problem with your diff. Our test util is not fully roust in this case and does not print location info correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, it should be underlining resolver_field here. Just wondering, where does that get determined?

Copy link
Contributor

Choose a reason for hiding this comment

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

Here:

.map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))?;

The problem is that the fixture file actually models two different files, and so the error location should actually get mapped to an offset based on if its an error in the schema portion of the file or the fragment portion.

3 │ resolver_field {
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# expected-to-throw
query relayResolverEdgeToInterfaceWithNoImplementorsQuery {
resolver_field {
name
}
}

# %extensions%

"""
An interface with no implementors
"""
interface SomeInterface {
name: String
}

extend type Query {
resolver_field: SomeInterface
@relay_resolver(import_path: "./path/to/Resolver.js")
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<4c83edb5a97709b48ab136e569a1bc3e>>
* @generated SignedSource<<27fd5fb3f07346c7af49129d773317a8>>
*/

mod compile_relay_artifacts;
Expand Down Expand Up @@ -1104,6 +1104,13 @@ fn relay_resolver_backing_client_edge() {
test_fixture(transform_fixture, "relay-resolver-backing-client-edge.graphql", "compile_relay_artifacts/fixtures/relay-resolver-backing-client-edge.expected", input, expected);
}

#[test]
fn relay_resolver_edge_to_interface_with_no_implementors() {
let input = include_str!("compile_relay_artifacts/fixtures/relay-resolver-edge-to-interface-with-no-implementors.graphql");
let expected = include_str!("compile_relay_artifacts/fixtures/relay-resolver-edge-to-interface-with-no-implementors.expected");
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);
}

#[test]
fn relay_resolver_es_modules() {
let input = include_str!("compile_relay_artifacts/fixtures/relay-resolver-es-modules.graphql");
Expand Down
13 changes: 12 additions & 1 deletion compiler/crates/relay-transforms/src/client_edges.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,18 @@ impl<'program, 'sc> ClientEdgesTransform<'program, 'sc> {
}

match edge_to_type {
Type::Interface(_) => {
Type::Interface(interface_id) => {
let interface = schema.interface(interface_id);
let implementing_objects =
interface.recursively_implementing_objects(Arc::as_ref(schema));
monicatang marked this conversation as resolved.
Show resolved Hide resolved
if implementing_objects.is_empty() {
self.errors.push(Diagnostic::error(
ValidationMessage::RelayResolverClientInterfaceMustBeImplemented {
interface_name: interface.name.item,
},
interface.name.location,
));
}
if !has_output_type(resolver_directive) {
self.errors.push(Diagnostic::error(
ValidationMessage::ClientEdgeToClientInterface,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ interface ClientOnlyInterface implements Node {
id: ID!
}

type BestFriend implements ClientOnlyInterface {
id: ID!
}

extend type User {
best_friend: ClientOnlyInterface @relay_resolver(fragment_name: "BestFriendResolverFragment_name", import_path: "BestFriendResolver")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ interface ClientOnlyInterface implements Node {
id: ID!
}

type BestFriend implements ClientOnlyInterface {
monicatang marked this conversation as resolved.
Show resolved Hide resolved
id: ID!
}

extend type User {
best_friend: ClientOnlyInterface @relay_resolver(fragment_name: "BestFriendResolverFragment_name", import_path: "BestFriendResolver")
}
Loading