Skip to content

Commit

Permalink
Add support for refetchable interfaces
Browse files Browse the repository at this point in the history
Summary:
In https://fb.workplace.com/groups/relay.support/permalink/10086229424758915/, it was revealed that fetchable interfaces are not currently supported by relay, even though they could be.

* Add support for (re) fetchable interfaces

Reviewed By: davidmccabe

Differential Revision: D41747533

fbshipit-source-id: 1cf1ae88645c9b9dcbff7e97a2060232a9a6550d
  • Loading branch information
Robert Balicki authored and facebook-github-bot committed Dec 6, 2022
1 parent ee4874a commit d00df58
Show file tree
Hide file tree
Showing 12 changed files with 388 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -125,23 +125,33 @@ fn get_fetchable_field_name(
fragment: &FragmentDefinition,
schema: &SDLSchema,
) -> DiagnosticsResult<Option<StringKey>> {
if let Type::Object(id) = fragment.type_condition {
let object = schema.object(id);
if let Some(fetchable) = object.directives.named(CONSTANTS.fetchable) {
let field_name_arg = fetchable.arguments.named(CONSTANTS.field_name);
if let Some(field_name_arg) = field_name_arg {
if let Some(value) = field_name_arg.value.get_string_literal() {
return Ok(Some(value));
}
let fetchable_directive = match fragment.type_condition {
Type::Interface(interface_id) => {
let interface = schema.interface(interface_id);
interface.directives.named(CONSTANTS.fetchable)
}
Type::Object(object_id) => {
let object = schema.object(object_id);
object.directives.named(CONSTANTS.fetchable)
}
_ => None,
};

if let Some(fetchable) = fetchable_directive {
let field_name_arg = fetchable.arguments.named(CONSTANTS.field_name);
if let Some(field_name_arg) = field_name_arg {
if let Some(value) = field_name_arg.value.get_string_literal() {
return Ok(Some(value));
}
return Err(vec![Diagnostic::error(
ValidationMessage::InvalidRefetchDirectiveDefinition {
fragment_name: fragment.name.item.0,
},
fragment.name.location,
)]);
}
return Err(vec![Diagnostic::error(
ValidationMessage::InvalidRefetchDirectiveDefinition {
fragment_name: fragment.name.item,
},
fragment.name.location,
)]);
}

Ok(None)
}

Expand Down Expand Up @@ -231,6 +241,6 @@ fn enforce_selections_with_id_field(

pub const FETCHABLE_QUERY_GENERATOR: QueryGenerator = QueryGenerator {
// T138625502 we should support interfaces and maybe unions
description: "server objects with the @fetchable directive",
description: "server objects and interfaces with the @fetchable directive",
build_refetch_operation,
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

use graphql_ir::FragmentDefinitionName;
use graphql_ir::VariableName;
use intern::string_key::StringKey;
use thiserror::Error;
Expand Down Expand Up @@ -76,10 +77,13 @@ pub(super) enum ValidationMessage {
)]
InvalidViewerSchemaForRefetchableFragmentOnViewer { fragment_name: StringKey },

// T139416294 this error message doesn't appear to be accurate
#[error(
"Invalid use of @refetchable with @connection in fragment '{fragment_name}', check that your schema defines a `directive @fetchable(field_name: String!) on OBJECT`."
"Invalid use of @refetchable with @connection in fragment '{fragment_name}', check that your schema defines a `directive @fetchable(field_name: String!) on OBJECT` or on `INTERFACE`."
)]
InvalidRefetchDirectiveDefinition { fragment_name: StringKey },
InvalidRefetchDirectiveDefinition {
fragment_name: FragmentDefinitionName,
},

#[error(
"Invalid use of @refetchable on fragment '{fragment_name}', the type '{type_name}' is @fetchable but the identifying field '{identifier_field_name}' does not have type 'ID'."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fragment UserName on UserNameRenderable
- the Viewer type
- the Query type
- the Node interface, object types that implement the Node interface, interfaces whose implementing objects all implement Node, and unions whose members all implement Node
- server objects with the @fetchable directive
- server objects and interfaces with the @fetchable directive

fragment-on-interface-which-implementations-not-implement-node.invalid.graphql:2:10
1 │ # expected-to-throw
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
==================================== INPUT ====================================
fragment RefetchableFragment on RefetchableInterface
@refetchable(queryName: "RefetchableFragmentQuery") {
id
}

# %extensions%

interface RefetchableInterface @fetchable(field_name: "id") {
id: ID!
}

extend type Query {
fetch__RefetchableInterface(id: ID!): RefetchableInterface
}

type ConcreteTypeImplementingRefetchableInterface implements RefetchableInterface & Node {
id: ID!
}
==================================== OUTPUT ===================================
query RefetchableFragmentQuery(
$id: ID!
) @__RefetchableDerivedFromMetadata
# RefetchableDerivedFromMetadata(
# FragmentDefinitionName(
# "RefetchableFragment",
# ),
# )
{
node(id: $id) {
...RefetchableFragment
}
}

fragment RefetchableFragment on RefetchableInterface @refetchable(queryName: "RefetchableFragmentQuery") @__RefetchableMetadata
# RefetchableMetadata {
# operation_name: "RefetchableFragmentQuery",
# path: [
# "node",
# ],
# identifier_field: Some(
# "id",
# ),
# }
{
id
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
fragment RefetchableFragment on RefetchableInterface
@refetchable(queryName: "RefetchableFragmentQuery") {
id
}

# %extensions%

interface RefetchableInterface @fetchable(field_name: "id") {
id: ID!
}

extend type Query {
fetch__RefetchableInterface(id: ID!): RefetchableInterface
}

type ConcreteTypeImplementingRefetchableInterface implements RefetchableInterface & Node {
id: ID!
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
==================================== INPUT ====================================
# Because there are no implementing types, "all implementing types impl Node"
# is true, so we generated a Node refetch query.
fragment RefetchableFragmentFoo on RefetchableInterfaceFoo
@refetchable(queryName: "RefetchableFragmentFooQuery") {
id
}


# %extensions%

interface RefetchableInterfaceFoo @fetchable(field_name: "id") {
id: ID!
}

extend type Query {
fetch__RefetchableInterfaceFoo(id: ID!): RefetchableInterfaceFoo
}
==================================== OUTPUT ===================================
query RefetchableFragmentFooQuery(
$id: ID!
) @__RefetchableDerivedFromMetadata
# RefetchableDerivedFromMetadata(
# FragmentDefinitionName(
# "RefetchableFragmentFoo",
# ),
# )
{
node(id: $id) {
...RefetchableFragmentFoo
}
}

fragment RefetchableFragmentFoo on RefetchableInterfaceFoo @refetchable(queryName: "RefetchableFragmentFooQuery") @__RefetchableMetadata
# RefetchableMetadata {
# operation_name: "RefetchableFragmentFooQuery",
# path: [
# "node",
# ],
# identifier_field: Some(
# "id",
# ),
# }
{
id
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Because there are no implementing types, "all implementing types impl Node"
# is true, so we generated a Node refetch query.
fragment RefetchableFragmentFoo on RefetchableInterfaceFoo
@refetchable(queryName: "RefetchableFragmentFooQuery") {
id
}


# %extensions%

interface RefetchableInterfaceFoo @fetchable(field_name: "id") {
id: ID!
}

extend type Query {
fetch__RefetchableInterfaceFoo(id: ID!): RefetchableInterfaceFoo
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
==================================== INPUT ====================================
fragment RefetchableFragment on RefetchableInterface
@refetchable(queryName: "RefetchableFragmentQuery") {
id
}

# %extensions%

interface RefetchableInterface @fetchable(field_name: "id") {
id: ID!
}

extend type Query {
fetch__RefetchableInterface(id: ID!): RefetchableInterface
}

type ConcreteTypeImplementingRefetchableInterface implements RefetchableInterface & Node {
id: ID!
}

type ConcreteType2ImplementingRefetchableInterface implements RefetchableInterface {
id: ID!
}
==================================== OUTPUT ===================================
query RefetchableFragmentQuery(
$id: ID!
) @__RefetchableDerivedFromMetadata
# RefetchableDerivedFromMetadata(
# FragmentDefinitionName(
# "RefetchableFragment",
# ),
# )
{
fetch__RefetchableInterface(id: $id) {
...RefetchableFragment
}
}

fragment RefetchableFragment on RefetchableInterface @refetchable(queryName: "RefetchableFragmentQuery") @__RefetchableMetadata
# RefetchableMetadata {
# operation_name: "RefetchableFragmentQuery",
# path: [
# "fetch__RefetchableInterface",
# ],
# identifier_field: Some(
# "id",
# ),
# }
{
id
__token
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
fragment RefetchableFragment on RefetchableInterface
@refetchable(queryName: "RefetchableFragmentQuery") {
id
}

# %extensions%

interface RefetchableInterface @fetchable(field_name: "id") {
id: ID!
}

extend type Query {
fetch__RefetchableInterface(id: ID!): RefetchableInterface
}

type ConcreteTypeImplementingRefetchableInterface implements RefetchableInterface & Node {
id: ID!
}

type ConcreteType2ImplementingRefetchableInterface implements RefetchableInterface {
id: ID!
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
==================================== INPUT ====================================
fragment RefetchableFragment on RefetchableInterface
@refetchable(queryName: "RefetchableFragmentQuery") {
id
}

fragment RefetchableFragment2 on RefetchableInterface2
@refetchable(queryName: "RefetchableFragmentQuery2") {
__typename
}

# %extensions%

interface RefetchableInterface @fetchable(field_name: "id") {
id: ID!
}

interface RefetchableInterface2 @fetchable(field_name: "not_id") {
not_id: ID!
}

extend type Query {
fetch__RefetchableInterface(id: ID!): RefetchableInterface
fetch__RefetchableInterface2(id: ID!): RefetchableInterface2
}

type ConcreteTypeImplementingRefetchableInterface implements RefetchableInterface {
id: ID!
}

type ConcreteTypeImplementingRefetchableInterface2 implements RefetchableInterface2 {
not_id: ID!
}
==================================== OUTPUT ===================================
query RefetchableFragmentQuery(
$id: ID!
) @__RefetchableDerivedFromMetadata
# RefetchableDerivedFromMetadata(
# FragmentDefinitionName(
# "RefetchableFragment",
# ),
# )
{
fetch__RefetchableInterface(id: $id) {
...RefetchableFragment
}
}

query RefetchableFragmentQuery2(
$id: ID!
) @__RefetchableDerivedFromMetadata
# RefetchableDerivedFromMetadata(
# FragmentDefinitionName(
# "RefetchableFragment2",
# ),
# )
{
fetch__RefetchableInterface2(id: $id) {
...RefetchableFragment2
}
}

fragment RefetchableFragment on RefetchableInterface @refetchable(queryName: "RefetchableFragmentQuery") @__RefetchableMetadata
# RefetchableMetadata {
# operation_name: "RefetchableFragmentQuery",
# path: [
# "fetch__RefetchableInterface",
# ],
# identifier_field: Some(
# "id",
# ),
# }
{
id
__token
}

fragment RefetchableFragment2 on RefetchableInterface2 @refetchable(queryName: "RefetchableFragmentQuery2") @__RefetchableMetadata
# RefetchableMetadata {
# operation_name: "RefetchableFragmentQuery2",
# path: [
# "fetch__RefetchableInterface2",
# ],
# identifier_field: Some(
# "not_id",
# ),
# }
{
__typename
not_id
__token
}
Loading

0 comments on commit d00df58

Please sign in to comment.