From 98823ea3d853e2013d175d4af31c21f8239c505a Mon Sep 17 00:00:00 2001 From: Monica Tang Date: Fri, 7 Jun 2024 16:00:56 -0700 Subject: [PATCH] add model resolvers for unions Reviewed By: captbaritone Differential Revision: D58219919 fbshipit-source-id: 4ae4b4abfb2abec46759a265b01d18d7e6a816bb --- .../relay-transforms/src/client_edges.rs | 87 +++++++++++-------- .../crates/relay-transforms/src/errors.rs | 12 +-- .../client-edge-to-client-union.expected | 74 ++++++++++++++++ .../client-edge-to-client-union.graphql | 25 ++++++ ...ient-edge-to-client-union.invalid.expected | 27 ------ ...lient-edge-to-client-union.invalid.graphql | 18 ---- .../tests/client_edges_test.rs | 10 +-- 7 files changed, 159 insertions(+), 94 deletions(-) create mode 100644 compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.expected create mode 100644 compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.graphql delete mode 100644 compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.invalid.expected delete mode 100644 compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.invalid.graphql diff --git a/compiler/crates/relay-transforms/src/client_edges.rs b/compiler/crates/relay-transforms/src/client_edges.rs index af2148bcc84d2..a702cddde71eb 100644 --- a/compiler/crates/relay-transforms/src/client_edges.rs +++ b/compiler/crates/relay-transforms/src/client_edges.rs @@ -379,41 +379,14 @@ impl<'program, 'pc> ClientEdgesTransform<'program, 'pc> { field.alias_or_name_location(), )); } - let mut model_resolvers: Vec = implementing_objects - .iter() - .filter_map(|object_id| { - let model_resolver = self.get_client_edge_model_resolver_for_object(*object_id); - model_resolver.or_else(|| { - let object = Type::Object(*object_id); - let schema = self.program.schema.as_ref(); - if !object.is_weak_resolver_object(schema) && object.is_resolver_object(schema) { - let model_name = self.program.schema.object(*object_id).name; - self.errors.push(Diagnostic::error( - ValidationMessage::ClientEdgeToClientInterfaceImplementingObjectMissingModelResolver { - interface_name: interface.name.item, - type_name: model_name.item, - }, - model_name.location, - )); - } - None - }) - }) - .collect(); - model_resolvers.sort(); - Some(ClientEdgeMetadataDirective::ClientObject { - type_name: None, - model_resolvers, - unique_id: self.get_key(), - }) + self.get_client_object_for_abstract_type( + implementing_objects.iter(), + interface.name.item.0, + ) } - Type::Union(_) => { - self.errors.push(Diagnostic::error( - ValidationMessage::ClientEdgeToClientUnion, - field.alias_or_name_location(), - )); - // TODO model resolvers for ClientEdgeToClientUnion - None + Type::Union(union) => { + let union = self.program.schema.union(union); + self.get_client_object_for_abstract_type(union.members.iter(), union.name.item.0) } Type::Object(object_id) => { let type_name = self.program.schema.object(object_id).name.item; @@ -432,6 +405,50 @@ impl<'program, 'pc> ClientEdgesTransform<'program, 'pc> { } } + fn get_client_object_for_abstract_type<'a>( + &mut self, + members: impl Iterator, + abstract_type_name: StringKey, + ) -> Option { + let mut model_resolvers: Vec = members + .filter_map(|object_id| { + let model_resolver = self.get_client_edge_model_resolver_for_object(*object_id); + model_resolver.or_else(|| { + self.maybe_report_error_for_missing_model_resolver( + object_id, + abstract_type_name, + ); + None + }) + }) + .collect(); + model_resolvers.sort(); + Some(ClientEdgeMetadataDirective::ClientObject { + type_name: None, + model_resolvers, + unique_id: self.get_key(), + }) + } + + fn maybe_report_error_for_missing_model_resolver( + &mut self, + object_id: &ObjectID, + abstract_type_name: StringKey, + ) { + let object = Type::Object(*object_id); + let schema = self.program.schema.as_ref(); + if !object.is_weak_resolver_object(schema) && object.is_resolver_object(schema) { + let model_name = self.program.schema.object(*object_id).name; + self.errors.push(Diagnostic::error( + ValidationMessage::ClientEdgeImplementingObjectMissingModelResolver { + name: abstract_type_name, + type_name: model_name.item, + }, + model_name.location, + )); + } + } + fn get_client_edge_model_resolver_for_object( &mut self, object_id: ObjectID, @@ -596,7 +613,7 @@ fn create_inline_fragment_for_client_edge( type_condition: None, directives: inline_fragment_directives, selections: vec![ - Selection::LinkedField(transformed_field.clone()), + Selection::LinkedField(Arc::clone(&transformed_field)), Selection::LinkedField(transformed_field), ], spread_location: Location::generated(), diff --git a/compiler/crates/relay-transforms/src/errors.rs b/compiler/crates/relay-transforms/src/errors.rs index 3fb93f7e7efee..8198ebd2a1600 100644 --- a/compiler/crates/relay-transforms/src/errors.rs +++ b/compiler/crates/relay-transforms/src/errors.rs @@ -130,18 +130,12 @@ pub enum ValidationMessage { ClientEdgeToClientInterface, #[error( - "The client edge pointing to the interface `{interface_name}` with implementing object, `{type_name}`, is missing its corresponding model resolver. The concrete type `{type_name}` and its resolver fields should be defined with the newer dot notation resolver syntax. See https://relay.dev/docs/guides/relay-resolvers/." + "The client edge pointing to `{name}` with implementing object, `{type_name}`, is missing its corresponding model resolver. The concrete type `{type_name}` and its resolver fields should be defined with the newer dot notation resolver syntax. See https://relay.dev/docs/guides/relay-resolvers/." )] - ClientEdgeToClientInterfaceImplementingObjectMissingModelResolver { - interface_name: InterfaceName, + ClientEdgeImplementingObjectMissingModelResolver { + name: StringKey, type_name: ObjectName, }, - - #[error( - "Client Edges that reference client-defined union types are not currently supported in Relay." - )] - ClientEdgeToClientUnion, - #[error("Invalid directive combination. @alias may not be combined with other directives.")] FragmentAliasIncompatibleDirective, diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.expected new file mode 100644 index 0000000000000..c1e1115b6e10d --- /dev/null +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.expected @@ -0,0 +1,74 @@ +==================================== INPUT ==================================== +fragment FeedbackFragmentType on User { + feedback_as_union { + ... on Like { + __typename + } + } +} + +# %extensions% + +type Like @__RelayResolverModel { + id: ID! + __relay_model_instance: RelayResolverValue @relay_resolver(import_path: "LikeResolver", fragment_name: "Like__id", inject_fragment_data: "id") +} + +type Heart @__RelayResolverModel { + id: ID! + __relay_model_instance: RelayResolverValue @relay_resolver(import_path: "HeartResolver", fragment_name: "Heart__id", inject_fragment_data: "id") +} + +union ClientOnlyUnion = Comment | Like | Heart + +extend type User { + feedback_as_union: ClientOnlyUnion @relay_resolver(import_path: "FeedbackResolver") +} +==================================== OUTPUT =================================== +fragment FeedbackFragmentType on User { + ... @__ClientEdgeMetadataDirective + # ClientObject { + # type_name: None, + # unique_id: 0, + # model_resolvers: [ + # ClientEdgeModelResolver { + # type_name: WithLocation { + # location: :199:204, + # item: ObjectName( + # "Heart", + # ), + # }, + # is_live: false, + # }, + # ClientEdgeModelResolver { + # type_name: WithLocation { + # location: :7:11, + # item: ObjectName( + # "Like", + # ), + # }, + # is_live: false, + # }, + # ], + # } + { + __id @__RelayResolverMetadata + # RelayResolverMetadata { + # field_id: FieldID(530), + # import_path: "FeedbackResolver", + # import_name: None, + # field_alias: None, + # field_path: "feedback_as_union", + # field_arguments: [], + # live: false, + # output_type_info: EdgeTo, + # fragment_data_injection_mode: None, + # } + + feedback_as_union { + ... on Like { + __typename + } + } + } +} diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.graphql b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.graphql new file mode 100644 index 0000000000000..db74cb8b7c3ff --- /dev/null +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.graphql @@ -0,0 +1,25 @@ +fragment FeedbackFragmentType on User { + feedback_as_union { + ... on Like { + __typename + } + } +} + +# %extensions% + +type Like @__RelayResolverModel { + id: ID! + __relay_model_instance: RelayResolverValue @relay_resolver(import_path: "LikeResolver", fragment_name: "Like__id", inject_fragment_data: "id") +} + +type Heart @__RelayResolverModel { + id: ID! + __relay_model_instance: RelayResolverValue @relay_resolver(import_path: "HeartResolver", fragment_name: "Heart__id", inject_fragment_data: "id") +} + +union ClientOnlyUnion = Comment | Like | Heart + +extend type User { + feedback_as_union: ClientOnlyUnion @relay_resolver(import_path: "FeedbackResolver") +} diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.invalid.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.invalid.expected deleted file mode 100644 index 8581f0a185b1e..0000000000000 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.invalid.expected +++ /dev/null @@ -1,27 +0,0 @@ -==================================== INPUT ==================================== -# expected-to-throw -fragment Foo_user on User { - best_friend { - __typename - } -} - -fragment BestFriendResolverFragment_name on ClientOnlyUnion { - __typename -} - -# %extensions% - -union ClientOnlyUnion = Comment | Feedback - -extend type User { - best_friend: ClientOnlyUnion @relay_resolver(fragment_name: "BestFriendResolverFragment_name", import_path: "BestFriendResolver") -} -==================================== ERROR ==================================== -✖︎ Client Edges that reference client-defined union types are not currently supported in Relay. - - client-edge-to-client-union.invalid.graphql:3:3 - 2 │ fragment Foo_user on User { - 3 │ best_friend { - │ ^^^^^^^^^^^ - 4 │ __typename diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.invalid.graphql b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.invalid.graphql deleted file mode 100644 index d5c0ca9a84880..0000000000000 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-union.invalid.graphql +++ /dev/null @@ -1,18 +0,0 @@ -# expected-to-throw -fragment Foo_user on User { - best_friend { - __typename - } -} - -fragment BestFriendResolverFragment_name on ClientOnlyUnion { - __typename -} - -# %extensions% - -union ClientOnlyUnion = Comment | Feedback - -extend type User { - best_friend: ClientOnlyUnion @relay_resolver(fragment_name: "BestFriendResolverFragment_name", import_path: "BestFriendResolver") -} diff --git a/compiler/crates/relay-transforms/tests/client_edges_test.rs b/compiler/crates/relay-transforms/tests/client_edges_test.rs index 142fa5a2282a6..a291dd457b5cc 100644 --- a/compiler/crates/relay-transforms/tests/client_edges_test.rs +++ b/compiler/crates/relay-transforms/tests/client_edges_test.rs @@ -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<> + * @generated SignedSource<<0fcfe34726846687001fb9cf0431f725>> */ mod client_edges; @@ -55,10 +55,10 @@ async fn client_edge_to_client_object() { } #[tokio::test] -async fn client_edge_to_client_union_invalid() { - let input = include_str!("client_edges/fixtures/client-edge-to-client-union.invalid.graphql"); - let expected = include_str!("client_edges/fixtures/client-edge-to-client-union.invalid.expected"); - test_fixture(transform_fixture, file!(), "client-edge-to-client-union.invalid.graphql", "client_edges/fixtures/client-edge-to-client-union.invalid.expected", input, expected).await; +async fn client_edge_to_client_union() { + let input = include_str!("client_edges/fixtures/client-edge-to-client-union.graphql"); + let expected = include_str!("client_edges/fixtures/client-edge-to-client-union.expected"); + test_fixture(transform_fixture, file!(), "client-edge-to-client-union.graphql", "client_edges/fixtures/client-edge-to-client-union.expected", input, expected).await; } #[tokio::test]