Skip to content

Commit ba5e48f

Browse files
monicatangfacebook-github-bot
authored andcommitted
add model resolvers for interfaces
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
1 parent 568d47d commit ba5e48f

10 files changed

+193
-87
lines changed

compiler/crates/relay-codegen/src/build_ast.rs

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
use std::path::PathBuf;
99

10-
use common::Location;
1110
use common::NamedItem;
11+
use common::ObjectName;
1212
use common::WithLocation;
1313
use docblock_shared::RELAY_RESOLVER_MODEL_INSTANCE_FIELD;
1414
use graphql_ir::Argument;
@@ -1276,20 +1276,18 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> {
12761276

12771277
fn build_client_edge_model_resolvers(
12781278
&mut self,
1279-
location: &Location,
12801279
model_resolvers: &[ClientEdgeModelResolver],
12811280
relay_resolver_metadata: &RelayResolverMetadata,
12821281
) -> Vec<ObjectEntry> {
12831282
model_resolvers
12841283
.iter()
12851284
.filter_map(|model_resolver| {
12861285
if model_resolver.has_model_instance_field {
1287-
let type_name = model_resolver.type_name.0;
1286+
let type_name = model_resolver.type_name.item.0;
12881287
Some(ObjectEntry {
12891288
key: type_name,
12901289
value: self.build_client_edge_model_resolver(
1291-
location,
1292-
type_name,
1290+
model_resolver.type_name,
12931291
model_resolver.is_live,
12941292
relay_resolver_metadata,
12951293
),
@@ -1303,32 +1301,31 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> {
13031301

13041302
fn build_client_edge_model_resolver(
13051303
&mut self,
1306-
location: &Location,
1307-
type_name: StringKey,
1304+
type_name: WithLocation<ObjectName>,
13081305
is_live: bool,
13091306
relay_resolver_metadata: &RelayResolverMetadata,
13101307
) -> Primitive {
13111308
let id_fragment_artifact_name = self
13121309
.project_config
13131310
.name
1314-
.generate_name_for_object_and_field(type_name, CODEGEN_CONSTANTS.id);
1311+
.generate_name_for_object_and_field(type_name.item.0, CODEGEN_CONSTANTS.id);
13151312
let path = format!(
13161313
"{}.{}",
13171314
relay_resolver_metadata.field_path, *RELAY_RESOLVER_MODEL_INSTANCE_FIELD
13181315
)
13191316
.intern();
13201317
let model_resolver_metadata = RelayResolverMetadata {
13211318
field_id: relay_resolver_metadata.field_id,
1322-
import_path: location.source_location().path().intern(),
1323-
import_name: Some(type_name),
1319+
import_path: type_name.location.source_location().path().intern(),
1320+
import_name: Some(type_name.item.0),
13241321
field_alias: None,
13251322
field_path: path,
13261323
field_arguments: vec![], // The model resolver field does not take GraphQL arguments.
13271324
live: is_live,
13281325
output_type_info: relay_resolver_metadata.output_type_info.clone(),
13291326
fragment_data_injection_mode: Some((
13301327
WithLocation::new(
1331-
*location,
1328+
type_name.location,
13321329
FragmentDefinitionName(id_fragment_artifact_name.clone().intern()),
13331330
),
13341331
FragmentDataInjectionMode::Field {
@@ -1777,14 +1774,11 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> {
17771774
}))
17781775
}
17791776

1780-
ClientEdgeMetadataDirective::ClientObject { type_name, location, model_resolvers, .. } => {
1777+
ClientEdgeMetadataDirective::ClientObject { type_name, model_resolvers, .. } => {
17811778
if self.project_config.feature_flags.disable_resolver_reader_ast {
17821779
selections_item
17831780
} else {
1784-
let concrete_type = match type_name {
1785-
Some(type_name) => Primitive::String(type_name.0),
1786-
None => Primitive::Null,
1787-
};
1781+
let concrete_type = type_name.map_or(Primitive::Null, |type_name| Primitive::String(type_name.0));
17881782
let field_directives = match &client_edge_metadata.backing_field {
17891783
Selection::ScalarField(field) => Some(&field.directives),
17901784
Selection::FragmentSpread(frag_spread) => Some(&frag_spread.directives),
@@ -1793,12 +1787,11 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> {
17931787
client_edge_metadata.backing_field
17941788
),
17951789
};
1796-
let model_resolver_field = if let Some(field_directives) = field_directives {
1790+
let model_resolver_field = field_directives.and_then(|field_directives| {
17971791
let resolver_metadata = RelayResolverMetadata::find(field_directives).unwrap();
17981792
let is_weak_resolver = matches!(resolver_metadata.output_type_info, ResolverOutputTypeInfo::Composite(_));
17991793
if !is_weak_resolver {
18001794
let model_resolver_primitives = self.build_client_edge_model_resolvers(
1801-
location,
18021795
model_resolvers,
18031796
resolver_metadata,
18041797
);
@@ -1810,9 +1803,7 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> {
18101803
} else {
18111804
None
18121805
}
1813-
} else {
1814-
None
1815-
};
1806+
});
18161807
let client_edge_model_resolvers = if let Some(model_resolver_field) = model_resolver_field {
18171808
Primitive::Key(model_resolver_field)
18181809
} else {

compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/resolver_returns_interface_of_all_live_model_type.expected

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ module.exports = ((node/*: any*/)/*: Fragment<
177177

178178
//- __generated__/QueryComponentQuery.graphql.js
179179
/**
180-
* <auto-generated> SignedSource<<5bd133f83631fcd35ea5b988e16d17c0>>
180+
* <auto-generated> SignedSource<<750533be4ec90cc3f3f7cccb2bf849f4>>
181181
* @flow
182182
* @lightSyntaxTransform
183183
* @nogrep
@@ -221,7 +221,34 @@ var node/*: ClientRequest*/ = {
221221
{
222222
"kind": "ClientEdgeToClientObject",
223223
"concreteType": null,
224-
"modelResolvers": null,
224+
"modelResolvers": {
225+
"Admin": {
226+
"alias": null,
227+
"args": null,
228+
"fragment": {
229+
"args": null,
230+
"kind": "FragmentSpread",
231+
"name": "Admin__id"
232+
},
233+
"kind": "RelayLiveResolver",
234+
"name": "person",
235+
"resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('Admin__id.graphql'), require('AdminTypeResolvers').Admin, 'id', true),
236+
"path": "person.__relay_model_instance"
237+
},
238+
"User": {
239+
"alias": null,
240+
"args": null,
241+
"fragment": {
242+
"args": null,
243+
"kind": "FragmentSpread",
244+
"name": "User__id"
245+
},
246+
"kind": "RelayLiveResolver",
247+
"name": "person",
248+
"resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('User__id.graphql'), require('UserTypeResolvers').User, 'id', true),
249+
"path": "person.__relay_model_instance"
250+
}
251+
},
225252
"backingField": {
226253
"alias": null,
227254
"args": null,

compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/resolver_returns_interface_of_all_strong_model_type.expected

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ module.exports = ((node/*: any*/)/*: Fragment<
175175

176176
//- __generated__/PersonComponentQuery.graphql.js
177177
/**
178-
* <auto-generated> SignedSource<<0f57207d3c3dd07322cf3394e3f46bf3>>
178+
* <auto-generated> SignedSource<<fd9dcd2a9e1c9c657b0bf475775f3870>>
179179
* @flow
180180
* @lightSyntaxTransform
181181
* @nogrep
@@ -219,7 +219,34 @@ var node/*: ClientRequest*/ = {
219219
{
220220
"kind": "ClientEdgeToClientObject",
221221
"concreteType": null,
222-
"modelResolvers": null,
222+
"modelResolvers": {
223+
"Admin": {
224+
"alias": null,
225+
"args": null,
226+
"fragment": {
227+
"args": null,
228+
"kind": "FragmentSpread",
229+
"name": "Admin__id"
230+
},
231+
"kind": "RelayResolver",
232+
"name": "person",
233+
"resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('Admin__id.graphql'), require('AdminTypeResolvers').Admin, 'id', true),
234+
"path": "person.__relay_model_instance"
235+
},
236+
"User": {
237+
"alias": null,
238+
"args": null,
239+
"fragment": {
240+
"args": null,
241+
"kind": "FragmentSpread",
242+
"name": "User__id"
243+
},
244+
"kind": "RelayResolver",
245+
"name": "person",
246+
"resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('User__id.graphql'), require('UserTypeResolvers').User, 'id', true),
247+
"path": "person.__relay_model_instance"
248+
}
249+
},
223250
"backingField": {
224251
"alias": null,
225252
"args": null,

compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/resolver_returns_interface_of_live_and_non_live_strong_model_type.expected

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ module.exports = ((node/*: any*/)/*: Fragment<
176176

177177
//- __generated__/QueryComponentQuery.graphql.js
178178
/**
179-
* <auto-generated> SignedSource<<5bd133f83631fcd35ea5b988e16d17c0>>
179+
* <auto-generated> SignedSource<<ec9f637bbc3eebc7dfd8c1c1744b875f>>
180180
* @flow
181181
* @lightSyntaxTransform
182182
* @nogrep
@@ -220,7 +220,34 @@ var node/*: ClientRequest*/ = {
220220
{
221221
"kind": "ClientEdgeToClientObject",
222222
"concreteType": null,
223-
"modelResolvers": null,
223+
"modelResolvers": {
224+
"Admin": {
225+
"alias": null,
226+
"args": null,
227+
"fragment": {
228+
"args": null,
229+
"kind": "FragmentSpread",
230+
"name": "Admin__id"
231+
},
232+
"kind": "RelayResolver",
233+
"name": "person",
234+
"resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('Admin__id.graphql'), require('AdminTypeResolvers').Admin, 'id', true),
235+
"path": "person.__relay_model_instance"
236+
},
237+
"User": {
238+
"alias": null,
239+
"args": null,
240+
"fragment": {
241+
"args": null,
242+
"kind": "FragmentSpread",
243+
"name": "User__id"
244+
},
245+
"kind": "RelayLiveResolver",
246+
"name": "person",
247+
"resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('User__id.graphql'), require('UserTypeResolvers').User, 'id', true),
248+
"path": "person.__relay_model_instance"
249+
}
250+
},
224251
"backingField": {
225252
"alias": null,
226253
"args": null,

compiler/crates/relay-transforms/src/client_edges.rs

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ use intern::Lookup;
4545
use lazy_static::lazy_static;
4646
use relay_config::ProjectConfig;
4747
use schema::DirectiveValue;
48+
use schema::ObjectID;
4849
use schema::Schema;
4950
use schema::Type;
5051

@@ -80,16 +81,15 @@ pub enum ClientEdgeMetadataDirective {
8081
},
8182
ClientObject {
8283
type_name: Option<ObjectName>,
83-
location: Location,
8484
unique_id: u32,
8585
model_resolvers: Vec<ClientEdgeModelResolver>,
8686
},
8787
}
8888
associated_data_impl!(ClientEdgeMetadataDirective);
8989

90-
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
90+
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
9191
pub struct ClientEdgeModelResolver {
92-
pub type_name: ObjectName,
92+
pub type_name: WithLocation<ObjectName>,
9393
pub has_model_instance_field: bool,
9494
pub is_live: bool,
9595
}
@@ -381,10 +381,14 @@ impl<'program, 'pc> ClientEdgesTransform<'program, 'pc> {
381381
field.alias_or_name_location(),
382382
));
383383
}
384+
let mut model_resolvers: Vec<ClientEdgeModelResolver> = implementing_objects
385+
.iter()
386+
.map(|object_id| self.get_client_edge_model_resolver_for_object(*object_id))
387+
.collect();
388+
model_resolvers.sort();
384389
Some(ClientEdgeMetadataDirective::ClientObject {
385390
type_name: None,
386-
location: field.alias_or_name_location(),
387-
model_resolvers: vec![],
391+
model_resolvers,
388392
unique_id: self.get_key(),
389393
})
390394
}
@@ -393,39 +397,15 @@ impl<'program, 'pc> ClientEdgesTransform<'program, 'pc> {
393397
ValidationMessage::ClientEdgeToClientUnion,
394398
field.alias_or_name_location(),
395399
));
400+
// TODO model resolvers for ClientEdgeToClientUnion
396401
None
397402
}
398403
Type::Object(object_id) => {
399-
let type_name = self.program.schema.object(object_id).name;
400-
let parent_type = self.program.schema.get_type(type_name.item.0).unwrap();
401-
let model_field_id = self
402-
.program
403-
.schema
404-
.named_field(parent_type, *RELAY_RESOLVER_MODEL_INSTANCE_FIELD);
405-
// Note: is_live is only true if the __relay_model_instance field exists on the model field
406-
let is_live = if let Some(id) = model_field_id {
407-
let model_field = self.program.schema.field(id);
408-
let resolver_directive =
409-
model_field.directives.named(*RELAY_RESOLVER_DIRECTIVE_NAME);
410-
if let Some(resolver_directive) = resolver_directive {
411-
resolver_directive
412-
.arguments
413-
.iter()
414-
.any(|arg| arg.name.0 == LIVE_ARGUMENT_NAME.0)
415-
} else {
416-
false
417-
}
418-
} else {
419-
false
420-
};
404+
let type_name = self.program.schema.object(object_id).name.item;
405+
let model_resolver = self.get_client_edge_model_resolver_for_object(object_id);
421406
Some(ClientEdgeMetadataDirective::ClientObject {
422-
type_name: Some(type_name.item),
423-
model_resolvers: vec![ClientEdgeModelResolver {
424-
type_name: type_name.item,
425-
has_model_instance_field: model_field_id.is_some(),
426-
is_live,
427-
}],
428-
location: type_name.location,
407+
type_name: Some(type_name),
408+
model_resolvers: vec![model_resolver],
429409
unique_id: self.get_key(),
430410
})
431411
}
@@ -435,6 +415,38 @@ impl<'program, 'pc> ClientEdgesTransform<'program, 'pc> {
435415
}
436416
}
437417

418+
fn get_client_edge_model_resolver_for_object(
419+
&mut self,
420+
object_id: ObjectID,
421+
) -> ClientEdgeModelResolver {
422+
let type_name = self.program.schema.object(object_id).name;
423+
let parent_type = self.program.schema.get_type(type_name.item.0).unwrap();
424+
let model_field_id = self
425+
.program
426+
.schema
427+
.named_field(parent_type, *RELAY_RESOLVER_MODEL_INSTANCE_FIELD);
428+
// Note: is_live is only true if the __relay_model_instance field exists on the model field
429+
let is_live = if let Some(id) = model_field_id {
430+
let model_field = self.program.schema.field(id);
431+
let resolver_directive = model_field.directives.named(*RELAY_RESOLVER_DIRECTIVE_NAME);
432+
if let Some(resolver_directive) = resolver_directive {
433+
resolver_directive
434+
.arguments
435+
.iter()
436+
.any(|arg| arg.name.0 == LIVE_ARGUMENT_NAME.0)
437+
} else {
438+
false
439+
}
440+
} else {
441+
false
442+
};
443+
ClientEdgeModelResolver {
444+
type_name,
445+
has_model_instance_field: model_field_id.is_some(),
446+
is_live,
447+
}
448+
}
449+
438450
fn get_edge_to_server_object_metadata_directive(
439451
&mut self,
440452
field_type: &schema::Field,

compiler/crates/relay-transforms/tests/client_edges/fixtures/client-edge-to-client-interface.expected

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,19 @@ fragment Foo_user on User {
3434
... @__ClientEdgeMetadataDirective
3535
# ClientObject {
3636
# type_name: None,
37-
# location: client-edge-to-client-interface.graphql:77:88,
3837
# unique_id: 0,
39-
# model_resolvers: [],
38+
# model_resolvers: [
39+
# ClientEdgeModelResolver {
40+
# type_name: WithLocation {
41+
# location: <generated>:144:154,
42+
# item: ObjectName(
43+
# "BestFriend",
44+
# ),
45+
# },
46+
# has_model_instance_field: false,
47+
# is_live: false,
48+
# },
49+
# ],
4050
# }
4151
{
4252
...BestFriendResolverFragment_name @__RelayResolverMetadata

0 commit comments

Comments
 (0)