Skip to content

Commit

Permalink
Add compiler validation to error on resolver that returns plural serv…
Browse files Browse the repository at this point in the history
…er type

Reviewed By: tyao1

Differential Revision: D70109268

fbshipit-source-id: a8a0fbc58ee2c3c4a22b6344799a7d4f1c348eef
  • Loading branch information
captbaritone authored and facebook-github-bot committed Feb 27, 2025
1 parent cf4f547 commit 687e996
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 129 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
==================================== INPUT ====================================
//- PersonComponent.js
graphql`query PersonComponentQuery {
plural_server @waterfall {
name
}
}`

//- QueryResolvers.js
/**
* @RelayResolver Query.plural_server: [User]
*/

//- relay.config.json
{
"language": "flow",
"jsModuleFormat": "haste",
"schema": "schema.graphql"
}

//- schema.graphql
type Query {
greeting: String
node(id: ID!): Node
}

interface Node {
id: ID!
}

type User implements Node {
id: ID!
name: String
}
==================================== OUTPUT ===================================
✖︎ Unexpected Relay Resolver returning plual edge to type defined on the server. Relay Resolvers do not curretly support returning plural edges to server types. As a work around, consider defining a plural edge to a client type which has a singular edge to the server type.

QueryResolvers.js:2:25
1 │ *
2 │ * @RelayResolver Query.plural_server: [User]
│ ^^^^^^^^^^^^^
3 │
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//- PersonComponent.js
graphql`query PersonComponentQuery {
plural_server @waterfall {
name
}
}`

//- QueryResolvers.js
/**
* @RelayResolver Query.plural_server: [User]
*/

//- relay.config.json
{
"language": "flow",
"jsModuleFormat": "haste",
"schema": "schema.graphql"
}

//- schema.graphql
type Query {
greeting: String
node(id: ID!): Node
}

interface Node {
id: ID!
}

type User implements Node {
id: ID!
name: String
}
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<<73cd290ab5054a643e2e27b6b67d7d1b>>
* @generated SignedSource<<18ad721b702d974f6b034f393230738d>>
*/

mod relay_compiler_integration;
Expand Down Expand Up @@ -278,6 +278,13 @@ async fn resolver_returns_interface_of_live_and_non_live_strong_model_type() {
test_fixture(transform_fixture, file!(), "resolver_returns_interface_of_live_and_non_live_strong_model_type.input", "relay_compiler_integration/fixtures/resolver_returns_interface_of_live_and_non_live_strong_model_type.expected", input, expected).await;
}

#[tokio::test]
async fn resolver_returns_plural_server_type_invalid() {
let input = include_str!("relay_compiler_integration/fixtures/resolver_returns_plural_server_type.invalid.input");
let expected = include_str!("relay_compiler_integration/fixtures/resolver_returns_plural_server_type.invalid.expected");
test_fixture(transform_fixture, file!(), "resolver_returns_plural_server_type.invalid.input", "relay_compiler_integration/fixtures/resolver_returns_plural_server_type.invalid.expected", input, expected).await;
}

#[tokio::test]
async fn resolver_returns_union_of_cse() {
let input = include_str!("relay_compiler_integration/fixtures/resolver_returns_union_of_cse.input");
Expand Down
6 changes: 6 additions & 0 deletions compiler/crates/relay-transforms/src/client_edges.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,12 @@ impl<'program, 'pc> ClientEdgesTransform<'program, 'pc> {
waterfall_directive: Option<&Directive>,
selections: Vec<Selection>,
) -> ClientEdgeMetadataDirective {
if field_type.type_.is_list() {
self.errors.push(Diagnostic::error(
ValidationMessage::ClientEdgeToServerObjectList,
field_type.name.location,
));
}
// Client Edges to server objects must be annotated with @waterfall
if waterfall_directive.is_none() {
self.errors.push(Diagnostic::error_with_data(
Expand Down
6 changes: 6 additions & 0 deletions compiler/crates/relay-transforms/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ pub enum ValidationMessage {
name: StringKey,
type_name: ObjectName,
},

#[error(
"Unexpected Relay Resolver returning plual edge to type defined on the server. Relay Resolvers do not curretly support returning plural edges to server types. As a work around, consider defining a plural edge to a client type which has a singular edge to the server type."
)]
ClientEdgeToServerObjectList,

#[error("Invalid directive combination. @alias may not be combined with other directives.")]
FragmentAliasIncompatibleDirective,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,26 @@ fragment relayResolver_BestFriendResolverFragment_name on User {

query relayResolver_Query {
me {
best_friends @waterfall {
best_friends {
name
}
}
}

# %extensions%

type ClientUser {
name: String
}

extend type User {
best_friends: [User!] @relay_resolver(fragment_name: "relayResolver_BestFriendResolverFragment_name", import_path: "./foo/bar/baz/BestFriendResolver.js")
best_friends: [ClientUser!]
@relay_resolver(
fragment_name: "relayResolver_BestFriendResolverFragment_name"
import_path: "./foo/bar/baz/BestFriendResolver.js"
)
}
==================================== OUTPUT ===================================
import type { RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType } from "RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends.graphql";
export type ClientEdgeQuery_relayResolver_Query_me__best_friends$variables = {|
id: string,
|};
export type ClientEdgeQuery_relayResolver_Query_me__best_friends$data = {|
+node: ?{|
+$fragmentSpreads: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType,
|},
|};
export type ClientEdgeQuery_relayResolver_Query_me__best_friends = {|
response: ClientEdgeQuery_relayResolver_Query_me__best_friends$data,
variables: ClientEdgeQuery_relayResolver_Query_me__best_friends$variables,
|};
-------------------------------------------------------------------------------
import type { DataID } from "relay-runtime";
import type { relayResolver_BestFriendResolverFragment_name$key } from "relayResolver_BestFriendResolverFragment_name.graphql";
import userBestFriendsResolverType from "BestFriendResolver";
Expand All @@ -55,20 +49,6 @@ export type relayResolver_Query = {|
|};
-------------------------------------------------------------------------------
import type { FragmentType } from "relay-runtime";
declare export opaque type RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType: FragmentType;
import type { ClientEdgeQuery_relayResolver_Query_me__best_friends$variables } from "ClientEdgeQuery_relayResolver_Query_me__best_friends.graphql";
export type RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$data = {|
+id: string,
+name: ?string,
+$fragmentType: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType,
|};
export type RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$key = {
+$data?: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$data,
+$fragmentSpreads: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType,
...
};
-------------------------------------------------------------------------------
import type { FragmentType } from "relay-runtime";
declare export opaque type relayResolver_BestFriendResolverFragment_name$fragmentType: FragmentType;
export type relayResolver_BestFriendResolverFragment_name$data = {|
+name: ?string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ fragment relayResolver_BestFriendResolverFragment_name on User {

query relayResolver_Query {
me {
best_friends @waterfall {
best_friends {
name
}
}
}

# %extensions%

type ClientUser {
name: String
}

extend type User {
best_friends: [User!] @relay_resolver(fragment_name: "relayResolver_BestFriendResolverFragment_name", import_path: "./foo/bar/baz/BestFriendResolver.js")
best_friends: [ClientUser!]
@relay_resolver(
fragment_name: "relayResolver_BestFriendResolverFragment_name"
import_path: "./foo/bar/baz/BestFriendResolver.js"
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,26 @@ fragment relayResolver_BestFriendResolverFragment_name on User {

query relayResolver_Query {
me {
best_friends @waterfall {
best_friends {
name
}
}
}

# %extensions%

type ClientUser {
name: String
}

extend type User {
best_friends: [User] @relay_resolver(fragment_name: "relayResolver_BestFriendResolverFragment_name", import_path: "./foo/bar/baz/BestFriendResolver.js")
best_friends: [ClientUser]
@relay_resolver(
fragment_name: "relayResolver_BestFriendResolverFragment_name"
import_path: "./foo/bar/baz/BestFriendResolver.js"
)
}
==================================== OUTPUT ===================================
import type { RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType } from "RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends.graphql";
export type ClientEdgeQuery_relayResolver_Query_me__best_friends$variables = {|
id: string,
|};
export type ClientEdgeQuery_relayResolver_Query_me__best_friends$data = {|
+node: ?{|
+$fragmentSpreads: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType,
|},
|};
export type ClientEdgeQuery_relayResolver_Query_me__best_friends = {|
response: ClientEdgeQuery_relayResolver_Query_me__best_friends$data,
variables: ClientEdgeQuery_relayResolver_Query_me__best_friends$variables,
|};
-------------------------------------------------------------------------------
import type { DataID } from "relay-runtime";
import type { relayResolver_BestFriendResolverFragment_name$key } from "relayResolver_BestFriendResolverFragment_name.graphql";
import userBestFriendsResolverType from "BestFriendResolver";
Expand All @@ -55,20 +49,6 @@ export type relayResolver_Query = {|
|};
-------------------------------------------------------------------------------
import type { FragmentType } from "relay-runtime";
declare export opaque type RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType: FragmentType;
import type { ClientEdgeQuery_relayResolver_Query_me__best_friends$variables } from "ClientEdgeQuery_relayResolver_Query_me__best_friends.graphql";
export type RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$data = {|
+id: string,
+name: ?string,
+$fragmentType: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType,
|};
export type RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$key = {
+$data?: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$data,
+$fragmentSpreads: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType,
...
};
-------------------------------------------------------------------------------
import type { FragmentType } from "relay-runtime";
declare export opaque type relayResolver_BestFriendResolverFragment_name$fragmentType: FragmentType;
export type relayResolver_BestFriendResolverFragment_name$data = {|
+name: ?string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ fragment relayResolver_BestFriendResolverFragment_name on User {

query relayResolver_Query {
me {
best_friends @waterfall {
best_friends {
name
}
}
}

# %extensions%

type ClientUser {
name: String
}

extend type User {
best_friends: [User] @relay_resolver(fragment_name: "relayResolver_BestFriendResolverFragment_name", import_path: "./foo/bar/baz/BestFriendResolver.js")
best_friends: [ClientUser]
@relay_resolver(
fragment_name: "relayResolver_BestFriendResolverFragment_name"
import_path: "./foo/bar/baz/BestFriendResolver.js"
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,26 @@ fragment relayResolver_BestFriendResolverFragment_name on User {

query relayResolver_Query {
me {
best_friends @waterfall {
best_friends {
name
}
}
}

# %extensions%

type ClientUser {
name: String
}

extend type User {
best_friends: [User!]! @relay_resolver(fragment_name: "relayResolver_BestFriendResolverFragment_name", import_path: "./foo/bar/baz/BestFriendResolver.js")
best_friends: [ClientUser!]!
@relay_resolver(
fragment_name: "relayResolver_BestFriendResolverFragment_name"
import_path: "./foo/bar/baz/BestFriendResolver.js"
)
}
==================================== OUTPUT ===================================
import type { RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType } from "RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends.graphql";
export type ClientEdgeQuery_relayResolver_Query_me__best_friends$variables = {|
id: string,
|};
export type ClientEdgeQuery_relayResolver_Query_me__best_friends$data = {|
+node: ?{|
+$fragmentSpreads: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType,
|},
|};
export type ClientEdgeQuery_relayResolver_Query_me__best_friends = {|
response: ClientEdgeQuery_relayResolver_Query_me__best_friends$data,
variables: ClientEdgeQuery_relayResolver_Query_me__best_friends$variables,
|};
-------------------------------------------------------------------------------
import type { DataID } from "relay-runtime";
import type { relayResolver_BestFriendResolverFragment_name$key } from "relayResolver_BestFriendResolverFragment_name.graphql";
import userBestFriendsResolverType from "BestFriendResolver";
Expand All @@ -55,20 +49,6 @@ export type relayResolver_Query = {|
|};
-------------------------------------------------------------------------------
import type { FragmentType } from "relay-runtime";
declare export opaque type RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType: FragmentType;
import type { ClientEdgeQuery_relayResolver_Query_me__best_friends$variables } from "ClientEdgeQuery_relayResolver_Query_me__best_friends.graphql";
export type RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$data = {|
+id: string,
+name: ?string,
+$fragmentType: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType,
|};
export type RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$key = {
+$data?: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$data,
+$fragmentSpreads: RefetchableClientEdgeQuery_relayResolver_Query_me__best_friends$fragmentType,
...
};
-------------------------------------------------------------------------------
import type { FragmentType } from "relay-runtime";
declare export opaque type relayResolver_BestFriendResolverFragment_name$fragmentType: FragmentType;
export type relayResolver_BestFriendResolverFragment_name$data = {|
+name: ?string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ fragment relayResolver_BestFriendResolverFragment_name on User {

query relayResolver_Query {
me {
best_friends @waterfall {
best_friends {
name
}
}
}

# %extensions%

type ClientUser {
name: String
}

extend type User {
best_friends: [User!]! @relay_resolver(fragment_name: "relayResolver_BestFriendResolverFragment_name", import_path: "./foo/bar/baz/BestFriendResolver.js")
best_friends: [ClientUser!]!
@relay_resolver(
fragment_name: "relayResolver_BestFriendResolverFragment_name"
import_path: "./foo/bar/baz/BestFriendResolver.js"
)
}
Loading

0 comments on commit 687e996

Please sign in to comment.