From d117221ad792e3989dfd7edd6f1276451f050cb9 Mon Sep 17 00:00:00 2001 From: Andrey Lunyov Date: Fri, 8 Jul 2022 09:44:10 -0700 Subject: [PATCH] Allow @required with client-edges fields Summary: This diffs allows adding required and client edge. Reviewed By: captbaritone Differential Revision: D37187697 fbshipit-source-id: 5f2625b8f4fa3d9792442d9383d8cd3d4ba96272 --- ...esolver-with-required-client-edge.expected | 312 +++++++++++++++++- ...resolver-with-required-client-edge.graphql | 2 - .../relay-transforms/src/client_edges.rs | 14 +- .../client-edge-with-required.expected | 78 +++++ ...phql => client-edge-with-required.graphql} | 1 - ...client-edge-with-required.invalid.expected | 29 -- .../tests/client_edges_test.rs | 10 +- ...lay-resolver-client-edge-required.expected | 69 +++- ...elay-resolver-client-edge-required.graphql | 1 - 9 files changed, 459 insertions(+), 57 deletions(-) create mode 100644 compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.expected rename compiler/crates/relay-transforms/tests/client_edges/fixtures/{client-edge-with-required.invalid.graphql => client-edge-with-required.graphql} (94%) delete mode 100644 compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.invalid.expected diff --git a/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-with-required-client-edge.expected b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-with-required-client-edge.expected index dc178fc221197..73fb00b9d292b 100644 --- a/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-with-required-client-edge.expected +++ b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-with-required-client-edge.expected @@ -1,6 +1,4 @@ ==================================== INPUT ==================================== -# expected-to-throw - fragment relayResolverWithRequiredClientEdge_best_friend_resolver on User { actor_key } @@ -22,11 +20,307 @@ extend type User { import_path: "./foo/bar/baz/BestFriendResolver.js" ) } -==================================== ERROR ==================================== -✖︎ Unexpected directive on Client Edge field. The `@required` directive is not currently supported on fields backed by Client Edges. +==================================== OUTPUT =================================== +{ + "fragment": { + "argumentDefinitions": [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" + } + ], + "kind": "Fragment", + "metadata": null, + "name": "ClientEdgeQuery_relayResolverWithRequiredClientEdgeQuery_me__best_friend", + "selections": [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } + ], + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "RefetchableClientEdgeQuery_relayResolverWithRequiredClientEdgeQuery_me__best_friend" + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" + } + ], + "kind": "Operation", + "name": "ClientEdgeQuery_relayResolverWithRequiredClientEdgeQuery_me__best_friend", + "selections": [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } + ], + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "1abebb591e62d121570c709a0e09a29c", + "id": null, + "metadata": {}, + "name": "ClientEdgeQuery_relayResolverWithRequiredClientEdgeQuery_me__best_friend", + "operationKind": "query", + "text": null + } +} + +QUERY: + +query ClientEdgeQuery_relayResolverWithRequiredClientEdgeQuery_me__best_friend( + $id: ID! +) { + node(id: $id) { + __typename + ...RefetchableClientEdgeQuery_relayResolverWithRequiredClientEdgeQuery_me__best_friend + id + } +} + +fragment RefetchableClientEdgeQuery_relayResolverWithRequiredClientEdgeQuery_me__best_friend on User { + name + id +} + + +{ + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "hasClientEdges": true + }, + "name": "relayResolverWithRequiredClientEdgeQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "me", + "plural": false, + "selections": [ + { + "kind": "ClientEdgeToServerObject", + "operation": require('ClientEdgeQuery_relayResolverWithRequiredClientEdgeQuery_me__best_friend.graphql'), + "backingField": { + "kind": "RequiredField", + "field": { + "alias": null, + "args": null, + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "relayResolverWithRequiredClientEdge_best_friend_resolver" + }, + "kind": "RelayResolver", + "name": "best_friend", + "resolverModule": require('BestFriendResolver'), + "path": "me.best_friend" + }, + "action": "THROW", + "path": "me.best_friend" + }, + "linkedField": { + "kind": "RequiredField", + "field": { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "best_friend", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } + ], + "storageKey": null + }, + "action": "THROW", + "path": "me.best_friend" + } + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "relayResolverWithRequiredClientEdgeQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "me", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "actor_key", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "c38847c6495c061f05952cde25b648e7", + "id": null, + "metadata": {}, + "name": "relayResolverWithRequiredClientEdgeQuery", + "operationKind": "query", + "text": null + } +} + +QUERY: + +query relayResolverWithRequiredClientEdgeQuery { + me { + ...relayResolverWithRequiredClientEdge_best_friend_resolver + id + } +} + +fragment relayResolverWithRequiredClientEdge_best_friend_resolver on User { + actor_key +} - relay-resolver-with-required-client-edge.graphql:9:28 - 8 │ me { - 9 │ best_friend @waterfall @required(action: THROW) { - │ ^^^^^^^^^ - 10 │ name + +{ + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "refetch": { + "connection": null, + "fragmentPathInResult": [ + "node" + ], + "operation": require('ClientEdgeQuery_relayResolverWithRequiredClientEdgeQuery_me__best_friend.graphql'), + "identifierField": "id" + } + }, + "name": "RefetchableClientEdgeQuery_relayResolverWithRequiredClientEdgeQuery_me__best_friend", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null +} + +{ + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "relayResolverWithRequiredClientEdge_best_friend_resolver", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "actor_key", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null +} diff --git a/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-with-required-client-edge.graphql b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-with-required-client-edge.graphql index 4b48df8c18d05..f092de761c1d6 100644 --- a/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-with-required-client-edge.graphql +++ b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-with-required-client-edge.graphql @@ -1,5 +1,3 @@ -# expected-to-throw - fragment relayResolverWithRequiredClientEdge_best_friend_resolver on User { actor_key } diff --git a/compiler/crates/relay-transforms/src/client_edges.rs b/compiler/crates/relay-transforms/src/client_edges.rs index a9ff1fb0e1e13..f7f75d644c154 100644 --- a/compiler/crates/relay-transforms/src/client_edges.rs +++ b/compiler/crates/relay-transforms/src/client_edges.rs @@ -7,7 +7,9 @@ use crate::refetchable_fragment::RefetchableFragment; use crate::refetchable_fragment::REFETCHABLE_NAME; +use crate::RequiredMetadataDirective; use crate::ValidationMessage; +use crate::REQUIRED_DIRECTIVE_NAME; use graphql_syntax::OperationKind; use intern::string_key::Intern; use intern::string_key::StringKey; @@ -298,10 +300,20 @@ impl<'program, 'sc> ClientEdgesTransform<'program, 'sc> { return self.default_transform_linked_field(field); } + let allowed_dirctive_names = [ + *CLIENT_EDGE_WATERFALL_DIRECTIVE_NAME, + *REQUIRED_DIRECTIVE_NAME, + RequiredMetadataDirective::directive_name(), + ]; + let other_directives = field .directives .iter() - .filter(|directive| directive.name() != *CLIENT_EDGE_WATERFALL_DIRECTIVE_NAME) + .filter(|directive| { + !allowed_dirctive_names + .iter() + .any(|item| directive.name() == *item) + }) .collect::>(); for directive in other_directives { diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.expected new file mode 100644 index 0000000000000..f971745f1d5f6 --- /dev/null +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.expected @@ -0,0 +1,78 @@ +==================================== INPUT ==================================== +fragment Foo_user on User { + best_friend @waterfall @required(action: NONE) { + name + } +} + +fragment BestFriendResolverFragment_name on User { + __typename +} + +# %extensions% + +extend type User { + best_friend: User + @relay_resolver( + fragment_name: "BestFriendResolverFragment_name" + import_path: "BestFriendResolver" + ) +} +==================================== OUTPUT =================================== +fragment BestFriendResolverFragment_name on User { + __typename +} + +fragment Foo_user on User { + ... @__ClientEdgeMetadataDirective + # ServerObject { + # query_name: "ClientEdgeQuery_Foo_user_best_friend", + # unique_id: 0, + # } + { + ...BestFriendResolverFragment_name @__RelayResolverMetadata + # RelayResolverMetadata { + # field_parent_type: "User", + # import_path: "BestFriendResolver", + # field_name: "best_friend", + # field_alias: None, + # field_path: "best_friend", + # field_arguments: [], + # live: false, + # } + @waterfall @required(action: NONE) + best_friend @waterfall @required(action: NONE) { + name + } + } +} + +fragment RefetchableClientEdgeQuery_Foo_user_best_friend on User @__clientEdgeGeneratedFragment(clientEdgeSourceDocument: "Foo_user") @__RefetchableMetadata +# RefetchableMetadata { +# operation_name: "ClientEdgeQuery_Foo_user_best_friend", +# path: [ +# "node", +# ], +# identifier_field: Some( +# "id", +# ), +# } + { + name + id +} + +query ClientEdgeQuery_Foo_user_best_friend( + $id: ID! +) @__ClientEdgeGeneratedQueryMetadataDirective +# ClientEdgeGeneratedQueryMetadataDirective { +# source_name: WithLocation { +# location: client-edge-with-required.graphql:9:17, +# item: "Foo_user", +# }, +# } + { + node(id: $id) { + ...RefetchableClientEdgeQuery_Foo_user_best_friend + } +} diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.invalid.graphql b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.graphql similarity index 94% rename from compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.invalid.graphql rename to compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.graphql index 1126fd06e02d1..2a87c67683cc4 100644 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.invalid.graphql +++ b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.graphql @@ -1,4 +1,3 @@ -# expected-to-throw fragment Foo_user on User { best_friend @waterfall @required(action: NONE) { name diff --git a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.invalid.expected b/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.invalid.expected deleted file mode 100644 index e9ca74dbcbd82..0000000000000 --- a/compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-with-required.invalid.expected +++ /dev/null @@ -1,29 +0,0 @@ -==================================== INPUT ==================================== -# expected-to-throw -fragment Foo_user on User { - best_friend @waterfall @required(action: NONE) { - name - } -} - -fragment BestFriendResolverFragment_name on User { - __typename -} - -# %extensions% - -extend type User { - best_friend: User - @relay_resolver( - fragment_name: "BestFriendResolverFragment_name" - import_path: "BestFriendResolver" - ) -} -==================================== ERROR ==================================== -✖︎ Unexpected directive on Client Edge field. The `@required` directive is not currently supported on fields backed by Client Edges. - - client-edge-with-required.invalid.graphql:3:26 - 2 │ fragment Foo_user on User { - 3 │ best_friend @waterfall @required(action: NONE) { - │ ^^^^^^^^^ - 4 │ name diff --git a/compiler/crates/relay-transforms/tests/client_edges_test.rs b/compiler/crates/relay-transforms/tests/client_edges_test.rs index 599e249f3026b..d204647ec7ea0 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<<8c1db64dd61b27b06de1e7acf3861ba9>> */ mod client_edges; @@ -62,10 +62,10 @@ fn client_edge_variables() { } #[test] -fn client_edge_with_required_invalid() { - let input = include_str!("client_edges/fixtures/client-edge-with-required.invalid.graphql"); - let expected = include_str!("client_edges/fixtures/client-edge-with-required.invalid.expected"); - test_fixture(transform_fixture, "client-edge-with-required.invalid.graphql", "client_edges/fixtures/client-edge-with-required.invalid.expected", input, expected); +fn client_edge_with_required() { + let input = include_str!("client_edges/fixtures/client-edge-with-required.graphql"); + let expected = include_str!("client_edges/fixtures/client-edge-with-required.expected"); + test_fixture(transform_fixture, "client-edge-with-required.graphql", "client_edges/fixtures/client-edge-with-required.expected", input, expected); } #[test] diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/relay-resolver-client-edge-required.expected b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/relay-resolver-client-edge-required.expected index 8629911f7a0cd..283835e00bab5 100644 --- a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/relay-resolver-client-edge-required.expected +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/relay-resolver-client-edge-required.expected @@ -1,5 +1,4 @@ ==================================== INPUT ==================================== -# expected-to-throw fragment relayResolver_BestFriendResolverFragment_name on User { name } @@ -21,11 +20,63 @@ extend type User { import_path: "./foo/bar/baz/BestFriendResolver.js" ) } -==================================== ERROR ==================================== -✖︎ Unexpected directive on Client Edge field. The `@required` directive is not currently supported on fields backed by Client Edges. - - relay-resolver-client-edge-required.graphql:8:28 - 7 │ me { - 8 │ best_friend @waterfall @required(action: THROW) { - │ ^^^^^^^^^ - 9 │ name +==================================== OUTPUT =================================== +import type { RefetchableClientEdgeQuery_relayResolver_Query_me__best_friend$fragmentType } from "RefetchableClientEdgeQuery_relayResolver_Query_me__best_friend.graphql"; +export type ClientEdgeQuery_relayResolver_Query_me__best_friend$variables = {| + id: string, +|}; +export type ClientEdgeQuery_relayResolver_Query_me__best_friend$data = {| + +node: ?{| + +$fragmentSpreads: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friend$fragmentType, + |}, +|}; +export type ClientEdgeQuery_relayResolver_Query_me__best_friend = {| + response: ClientEdgeQuery_relayResolver_Query_me__best_friend$data, + variables: ClientEdgeQuery_relayResolver_Query_me__best_friend$variables, +|}; +------------------------------------------------------------------------------- +import type { relayResolver_BestFriendResolverFragment_name$key } from "relayResolver_BestFriendResolverFragment_name.graphql"; +import userBestFriendResolver from "BestFriendResolver"; +// Type assertion validating that `userBestFriendResolver` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(userBestFriendResolver: ( + rootKey: relayResolver_BestFriendResolverFragment_name$key, +) => mixed); +export type relayResolver_Query$variables = {||}; +export type relayResolver_Query$data = {| + +me: ?{| + +best_friend: {| + +name: ?string, + |}, + |}, +|}; +export type relayResolver_Query = {| + response: relayResolver_Query$data, + variables: relayResolver_Query$variables, +|}; +------------------------------------------------------------------------------- +import type { FragmentType } from "relay-runtime"; +declare export opaque type RefetchableClientEdgeQuery_relayResolver_Query_me__best_friend$fragmentType: FragmentType; +import type { ClientEdgeQuery_relayResolver_Query_me__best_friend$variables } from "ClientEdgeQuery_relayResolver_Query_me__best_friend.graphql"; +export type RefetchableClientEdgeQuery_relayResolver_Query_me__best_friend$data = {| + +id: string, + +name: ?string, + +$fragmentType: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friend$fragmentType, +|}; +export type RefetchableClientEdgeQuery_relayResolver_Query_me__best_friend$key = { + +$data?: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friend$data, + +$fragmentSpreads: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friend$fragmentType, + ... +}; +------------------------------------------------------------------------------- +import type { FragmentType } from "relay-runtime"; +declare export opaque type relayResolver_BestFriendResolverFragment_name$fragmentType: FragmentType; +export type relayResolver_BestFriendResolverFragment_name$data = {| + +name: ?string, + +$fragmentType: relayResolver_BestFriendResolverFragment_name$fragmentType, +|}; +export type relayResolver_BestFriendResolverFragment_name$key = { + +$data?: relayResolver_BestFriendResolverFragment_name$data, + +$fragmentSpreads: relayResolver_BestFriendResolverFragment_name$fragmentType, + ... +}; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/relay-resolver-client-edge-required.graphql b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/relay-resolver-client-edge-required.graphql index 26a617e435937..d7eb3a5c023bf 100644 --- a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/relay-resolver-client-edge-required.graphql +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/relay-resolver-client-edge-required.graphql @@ -1,4 +1,3 @@ -# expected-to-throw fragment relayResolver_BestFriendResolverFragment_name on User { name }