Skip to content

Commit

Permalink
add model resolvers for interfaces
Browse files Browse the repository at this point in the history
Summary:
## Context

In M2 we want to support defining a resolver that returns an interface, implemented by strong concrete types. The `modelResolver` field on a `ClientEdgeToClientObject` type is used in `RelayReader` when reading a client edge to verify that the object exists before reading its child fields.

Previously, client edge nodes were structured as:
```
{
   ...
   'modelResolver': {..}
   'backingField': {..}
   'linkedField': {..}
   ...
}
```

This diff refactors them to:
```
{
   ...
   'modelResolvers': {
      'ConcreteTypeA': {..}
      'ConcreteTypeB': {..}
   }
   'backingField': {..}
   'linkedField': {..}
   ...
}
```

where the type of the `modelResolvers` field in runtime code is `{[string]: RelayResolver | RelayLiveResolver}`.

There will only be one concrete type if the client edge points to a concrete object, i.e.
```
{
   ...
   'modelResolvers': {
      'ConcreteObjectType': {..}
   }
   'backingField': {..}
   'linkedField': {..}
   ...
}
```

## This diff
Add model resolver nodes for client edges pointing to an interface (previously hardcoded to an empty object).

Note: model resolvers for `ClientEdgeToClientUnion` nodes are not yet generated here.

Reviewed By: captbaritone

Differential Revision: D54137384

fbshipit-source-id: 8f52634109eec3c9f71acb15c0e1228972ce145a
  • Loading branch information
monicatang authored and facebook-github-bot committed Mar 11, 2024
1 parent 568d47d commit ba5e48f
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 87 deletions.
33 changes: 12 additions & 21 deletions compiler/crates/relay-codegen/src/build_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

use std::path::PathBuf;

use common::Location;
use common::NamedItem;
use common::ObjectName;
use common::WithLocation;
use docblock_shared::RELAY_RESOLVER_MODEL_INSTANCE_FIELD;
use graphql_ir::Argument;
Expand Down Expand Up @@ -1276,20 +1276,18 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> {

fn build_client_edge_model_resolvers(
&mut self,
location: &Location,
model_resolvers: &[ClientEdgeModelResolver],
relay_resolver_metadata: &RelayResolverMetadata,
) -> Vec<ObjectEntry> {
model_resolvers
.iter()
.filter_map(|model_resolver| {
if model_resolver.has_model_instance_field {
let type_name = model_resolver.type_name.0;
let type_name = model_resolver.type_name.item.0;
Some(ObjectEntry {
key: type_name,
value: self.build_client_edge_model_resolver(
location,
type_name,
model_resolver.type_name,
model_resolver.is_live,
relay_resolver_metadata,
),
Expand All @@ -1303,32 +1301,31 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> {

fn build_client_edge_model_resolver(
&mut self,
location: &Location,
type_name: StringKey,
type_name: WithLocation<ObjectName>,
is_live: bool,
relay_resolver_metadata: &RelayResolverMetadata,
) -> Primitive {
let id_fragment_artifact_name = self
.project_config
.name
.generate_name_for_object_and_field(type_name, CODEGEN_CONSTANTS.id);
.generate_name_for_object_and_field(type_name.item.0, CODEGEN_CONSTANTS.id);
let path = format!(
"{}.{}",
relay_resolver_metadata.field_path, *RELAY_RESOLVER_MODEL_INSTANCE_FIELD
)
.intern();
let model_resolver_metadata = RelayResolverMetadata {
field_id: relay_resolver_metadata.field_id,
import_path: location.source_location().path().intern(),
import_name: Some(type_name),
import_path: type_name.location.source_location().path().intern(),
import_name: Some(type_name.item.0),
field_alias: None,
field_path: path,
field_arguments: vec![], // The model resolver field does not take GraphQL arguments.
live: is_live,
output_type_info: relay_resolver_metadata.output_type_info.clone(),
fragment_data_injection_mode: Some((
WithLocation::new(
*location,
type_name.location,
FragmentDefinitionName(id_fragment_artifact_name.clone().intern()),
),
FragmentDataInjectionMode::Field {
Expand Down Expand Up @@ -1777,14 +1774,11 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> {
}))
}

ClientEdgeMetadataDirective::ClientObject { type_name, location, model_resolvers, .. } => {
ClientEdgeMetadataDirective::ClientObject { type_name, model_resolvers, .. } => {
if self.project_config.feature_flags.disable_resolver_reader_ast {
selections_item
} else {
let concrete_type = match type_name {
Some(type_name) => Primitive::String(type_name.0),
None => Primitive::Null,
};
let concrete_type = type_name.map_or(Primitive::Null, |type_name| Primitive::String(type_name.0));
let field_directives = match &client_edge_metadata.backing_field {
Selection::ScalarField(field) => Some(&field.directives),
Selection::FragmentSpread(frag_spread) => Some(&frag_spread.directives),
Expand All @@ -1793,12 +1787,11 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> {
client_edge_metadata.backing_field
),
};
let model_resolver_field = if let Some(field_directives) = field_directives {
let model_resolver_field = field_directives.and_then(|field_directives| {
let resolver_metadata = RelayResolverMetadata::find(field_directives).unwrap();
let is_weak_resolver = matches!(resolver_metadata.output_type_info, ResolverOutputTypeInfo::Composite(_));
if !is_weak_resolver {
let model_resolver_primitives = self.build_client_edge_model_resolvers(
location,
model_resolvers,
resolver_metadata,
);
Expand All @@ -1810,9 +1803,7 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> {
} else {
None
}
} else {
None
};
});
let client_edge_model_resolvers = if let Some(model_resolver_field) = model_resolver_field {
Primitive::Key(model_resolver_field)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ module.exports = ((node/*: any*/)/*: Fragment<

//- __generated__/QueryComponentQuery.graphql.js
/**
* <auto-generated> SignedSource<<5bd133f83631fcd35ea5b988e16d17c0>>
* <auto-generated> SignedSource<<750533be4ec90cc3f3f7cccb2bf849f4>>
* @flow
* @lightSyntaxTransform
* @nogrep
Expand Down Expand Up @@ -221,7 +221,34 @@ var node/*: ClientRequest*/ = {
{
"kind": "ClientEdgeToClientObject",
"concreteType": null,
"modelResolvers": null,
"modelResolvers": {
"Admin": {
"alias": null,
"args": null,
"fragment": {
"args": null,
"kind": "FragmentSpread",
"name": "Admin__id"
},
"kind": "RelayLiveResolver",
"name": "person",
"resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('Admin__id.graphql'), require('AdminTypeResolvers').Admin, 'id', true),
"path": "person.__relay_model_instance"
},
"User": {
"alias": null,
"args": null,
"fragment": {
"args": null,
"kind": "FragmentSpread",
"name": "User__id"
},
"kind": "RelayLiveResolver",
"name": "person",
"resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('User__id.graphql'), require('UserTypeResolvers').User, 'id', true),
"path": "person.__relay_model_instance"
}
},
"backingField": {
"alias": null,
"args": null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ module.exports = ((node/*: any*/)/*: Fragment<

//- __generated__/PersonComponentQuery.graphql.js
/**
* <auto-generated> SignedSource<<0f57207d3c3dd07322cf3394e3f46bf3>>
* <auto-generated> SignedSource<<fd9dcd2a9e1c9c657b0bf475775f3870>>
* @flow
* @lightSyntaxTransform
* @nogrep
Expand Down Expand Up @@ -219,7 +219,34 @@ var node/*: ClientRequest*/ = {
{
"kind": "ClientEdgeToClientObject",
"concreteType": null,
"modelResolvers": null,
"modelResolvers": {
"Admin": {
"alias": null,
"args": null,
"fragment": {
"args": null,
"kind": "FragmentSpread",
"name": "Admin__id"
},
"kind": "RelayResolver",
"name": "person",
"resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('Admin__id.graphql'), require('AdminTypeResolvers').Admin, 'id', true),
"path": "person.__relay_model_instance"
},
"User": {
"alias": null,
"args": null,
"fragment": {
"args": null,
"kind": "FragmentSpread",
"name": "User__id"
},
"kind": "RelayResolver",
"name": "person",
"resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('User__id.graphql'), require('UserTypeResolvers').User, 'id', true),
"path": "person.__relay_model_instance"
}
},
"backingField": {
"alias": null,
"args": null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ module.exports = ((node/*: any*/)/*: Fragment<

//- __generated__/QueryComponentQuery.graphql.js
/**
* <auto-generated> SignedSource<<5bd133f83631fcd35ea5b988e16d17c0>>
* <auto-generated> SignedSource<<ec9f637bbc3eebc7dfd8c1c1744b875f>>
* @flow
* @lightSyntaxTransform
* @nogrep
Expand Down Expand Up @@ -220,7 +220,34 @@ var node/*: ClientRequest*/ = {
{
"kind": "ClientEdgeToClientObject",
"concreteType": null,
"modelResolvers": null,
"modelResolvers": {
"Admin": {
"alias": null,
"args": null,
"fragment": {
"args": null,
"kind": "FragmentSpread",
"name": "Admin__id"
},
"kind": "RelayResolver",
"name": "person",
"resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('Admin__id.graphql'), require('AdminTypeResolvers').Admin, 'id', true),
"path": "person.__relay_model_instance"
},
"User": {
"alias": null,
"args": null,
"fragment": {
"args": null,
"kind": "FragmentSpread",
"name": "User__id"
},
"kind": "RelayLiveResolver",
"name": "person",
"resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('User__id.graphql'), require('UserTypeResolvers').User, 'id', true),
"path": "person.__relay_model_instance"
}
},
"backingField": {
"alias": null,
"args": null,
Expand Down
80 changes: 46 additions & 34 deletions compiler/crates/relay-transforms/src/client_edges.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ use intern::Lookup;
use lazy_static::lazy_static;
use relay_config::ProjectConfig;
use schema::DirectiveValue;
use schema::ObjectID;
use schema::Schema;
use schema::Type;

Expand Down Expand Up @@ -80,16 +81,15 @@ pub enum ClientEdgeMetadataDirective {
},
ClientObject {
type_name: Option<ObjectName>,
location: Location,
unique_id: u32,
model_resolvers: Vec<ClientEdgeModelResolver>,
},
}
associated_data_impl!(ClientEdgeMetadataDirective);

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ClientEdgeModelResolver {
pub type_name: ObjectName,
pub type_name: WithLocation<ObjectName>,
pub has_model_instance_field: bool,
pub is_live: bool,
}
Expand Down Expand Up @@ -381,10 +381,14 @@ impl<'program, 'pc> ClientEdgesTransform<'program, 'pc> {
field.alias_or_name_location(),
));
}
let mut model_resolvers: Vec<ClientEdgeModelResolver> = implementing_objects
.iter()
.map(|object_id| self.get_client_edge_model_resolver_for_object(*object_id))
.collect();
model_resolvers.sort();
Some(ClientEdgeMetadataDirective::ClientObject {
type_name: None,
location: field.alias_or_name_location(),
model_resolvers: vec![],
model_resolvers,
unique_id: self.get_key(),
})
}
Expand All @@ -393,39 +397,15 @@ impl<'program, 'pc> ClientEdgesTransform<'program, 'pc> {
ValidationMessage::ClientEdgeToClientUnion,
field.alias_or_name_location(),
));
// TODO model resolvers for ClientEdgeToClientUnion
None
}
Type::Object(object_id) => {
let type_name = self.program.schema.object(object_id).name;
let parent_type = self.program.schema.get_type(type_name.item.0).unwrap();
let model_field_id = self
.program
.schema
.named_field(parent_type, *RELAY_RESOLVER_MODEL_INSTANCE_FIELD);
// Note: is_live is only true if the __relay_model_instance field exists on the model field
let is_live = if let Some(id) = model_field_id {
let model_field = self.program.schema.field(id);
let resolver_directive =
model_field.directives.named(*RELAY_RESOLVER_DIRECTIVE_NAME);
if let Some(resolver_directive) = resolver_directive {
resolver_directive
.arguments
.iter()
.any(|arg| arg.name.0 == LIVE_ARGUMENT_NAME.0)
} else {
false
}
} else {
false
};
let type_name = self.program.schema.object(object_id).name.item;
let model_resolver = self.get_client_edge_model_resolver_for_object(object_id);
Some(ClientEdgeMetadataDirective::ClientObject {
type_name: Some(type_name.item),
model_resolvers: vec![ClientEdgeModelResolver {
type_name: type_name.item,
has_model_instance_field: model_field_id.is_some(),
is_live,
}],
location: type_name.location,
type_name: Some(type_name),
model_resolvers: vec![model_resolver],
unique_id: self.get_key(),
})
}
Expand All @@ -435,6 +415,38 @@ impl<'program, 'pc> ClientEdgesTransform<'program, 'pc> {
}
}

fn get_client_edge_model_resolver_for_object(
&mut self,
object_id: ObjectID,
) -> ClientEdgeModelResolver {
let type_name = self.program.schema.object(object_id).name;
let parent_type = self.program.schema.get_type(type_name.item.0).unwrap();
let model_field_id = self
.program
.schema
.named_field(parent_type, *RELAY_RESOLVER_MODEL_INSTANCE_FIELD);
// Note: is_live is only true if the __relay_model_instance field exists on the model field
let is_live = if let Some(id) = model_field_id {
let model_field = self.program.schema.field(id);
let resolver_directive = model_field.directives.named(*RELAY_RESOLVER_DIRECTIVE_NAME);
if let Some(resolver_directive) = resolver_directive {
resolver_directive
.arguments
.iter()
.any(|arg| arg.name.0 == LIVE_ARGUMENT_NAME.0)
} else {
false
}
} else {
false
};
ClientEdgeModelResolver {
type_name,
has_model_instance_field: model_field_id.is_some(),
is_live,
}
}

fn get_edge_to_server_object_metadata_directive(
&mut self,
field_type: &schema::Field,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,19 @@ fragment Foo_user on User {
... @__ClientEdgeMetadataDirective
# ClientObject {
# type_name: None,
# location: client-edge-to-client-interface.graphql:77:88,
# unique_id: 0,
# model_resolvers: [],
# model_resolvers: [
# ClientEdgeModelResolver {
# type_name: WithLocation {
# location: <generated>:144:154,
# item: ObjectName(
# "BestFriend",
# ),
# },
# has_model_instance_field: false,
# is_live: false,
# },
# ],
# }
{
...BestFriendResolverFragment_name @__RelayResolverMetadata
Expand Down
Loading

0 comments on commit ba5e48f

Please sign in to comment.