Skip to content

Commit

Permalink
transform to convert fragments and linked fields on abstract types to…
Browse files Browse the repository at this point in the history
… inline fragment

Summary:
## context

https://fburl.com/gdoc/3cv4msxz

## this diff

Converts selections on fragments and linked fields on abstract types to selections on inline fragments on the concrete type. This is just a starting point, i.e. fragment spreads are tbd.

Reviewed By: captbaritone

Differential Revision: D52861037

fbshipit-source-id: ff2ec2a3a44f2a6ec25246a52ce130993cb57e77
  • Loading branch information
monicatang authored and facebook-github-bot committed Jan 19, 2024
1 parent 396a1db commit 5718ca9
Show file tree
Hide file tree
Showing 8 changed files with 285 additions and 5 deletions.
Expand Up @@ -5,11 +5,22 @@
* LICENSE file in the root directory of this source tree.
*/

use std::sync::Arc;

use common::Diagnostic;
use common::DiagnosticsResult;
use common::FeatureFlags;
use common::Location;
use graphql_ir::FragmentDefinition;
use graphql_ir::InlineFragment;
use graphql_ir::LinkedField;
use graphql_ir::Program;
use graphql_ir::Selection;
use graphql_ir::Transformed;
use graphql_ir::Transformer;
use schema::SDLSchema;
use schema::Schema;
use schema::Type;

/// Transform selections on abstract types.
///
Expand Down Expand Up @@ -39,23 +50,86 @@ pub fn relay_resolvers_abstract_types(
}

struct RelayResolverAbstractTypesTransform<'program> {
_program: &'program Program,
program: &'program Program,
errors: Vec<Diagnostic>,
}

impl<'program> RelayResolverAbstractTypesTransform<'program> {
fn new(program: &'program Program) -> Self {
Self {
_program: program,
program,
errors: Default::default(),
}
}

// TODO T174693027 Implement transform
}

impl Transformer for RelayResolverAbstractTypesTransform<'_> {
const NAME: &'static str = "RelayResolverAbstractTypesTransform";
const VISIT_ARGUMENTS: bool = false;
const VISIT_DIRECTIVES: bool = false;

fn transform_fragment(
&mut self,
fragment: &FragmentDefinition,
) -> Transformed<FragmentDefinition> {
if !matches!(fragment.type_condition, Type::Interface(_)) {
return Transformed::Keep;
}
let inline_fragment_selections = create_inline_fragment_selections_for_abstract_type(
&self.program.schema,
fragment.type_condition,
&fragment.selections,
);
Transformed::Replace(FragmentDefinition {
selections: inline_fragment_selections,
..fragment.clone()
})
}

fn transform_linked_field(&mut self, field: &LinkedField) -> Transformed<Selection> {
let schema = &self.program.schema;
let field_type = schema.field(field.definition.item);
let edge_to_type = field_type.type_.inner();
if !matches!(edge_to_type, Type::Interface(_)) {
return Transformed::Keep;
}
let inline_fragment_selections = create_inline_fragment_selections_for_abstract_type(
&schema,
edge_to_type,
&field.selections,
);
Transformed::Replace(Selection::LinkedField(Arc::new(LinkedField {
selections: inline_fragment_selections,
..field.clone()
})))
}
}

fn create_inline_fragment_selections_for_abstract_type(
schema: &Arc<SDLSchema>,
abstract_type: Type,
selections: &[Selection],
) -> Vec<Selection> {
match abstract_type {
Type::Interface(interface_id) => {
let interface = schema.interface(interface_id);
let implementing_objects =
interface.recursively_implementing_objects(Arc::as_ref(&schema));
let mut sorted_implementing_objects =
implementing_objects.into_iter().collect::<Vec<_>>();
sorted_implementing_objects.sort();
sorted_implementing_objects
.iter()
.map(|object_id| {
Selection::InlineFragment(Arc::new(InlineFragment {
type_condition: Some(Type::Object(*object_id)),
directives: vec![], // TODO T174693027 do we need directives here?
selections: selections.to_vec(),
spread_location: Location::generated(),
}))
})
.collect()
}
_ => panic!("Expected abstract type to be an interface"),
}
}
@@ -0,0 +1,37 @@
==================================== INPUT ====================================
# relay-resolver-enable-interface-output-type

query edgeToAbstractTypeQuery {
cat {
description
}
}

# %extensions%

interface Cat {
description: String
}

type Tabby implements Cat {
description: String
}

type Persian implements Cat {
description: String
}

extend type Query {
cat: Cat
}
==================================== OUTPUT ===================================
query edgeToAbstractTypeQuery {
cat {
... on Tabby {
description
}
... on Persian {
description
}
}
}
@@ -0,0 +1,25 @@
# relay-resolver-enable-interface-output-type

query edgeToAbstractTypeQuery {
cat {
description
}
}

# %extensions%

interface Cat {
description: String
}

type Tabby implements Cat {
description: String
}

type Persian implements Cat {
description: String
}

extend type Query {
cat: Cat
}
@@ -0,0 +1,30 @@
==================================== INPUT ====================================
query edgeToAbstractTypeDisabledQuery {
cat {
description
}
}

# %extensions%

interface Cat {
description: String
}

type Tabby implements Cat {
description: String
}

type Persian implements Cat {
description: String
}

extend type Query {
cat: Cat
}
==================================== OUTPUT ===================================
query edgeToAbstractTypeDisabledQuery {
cat {
description
}
}
@@ -0,0 +1,23 @@
query edgeToAbstractTypeDisabledQuery {
cat {
description
}
}

# %extensions%

interface Cat {
description: String
}

type Tabby implements Cat {
description: String
}

type Persian implements Cat {
description: String
}

extend type Query {
cat: Cat
}
@@ -0,0 +1,43 @@
==================================== INPUT ====================================
# relay-resolver-enable-interface-output-type

fragment fragmentOnAbstractTypeDisabledFragment on Cat {
description
}

# %extensions%

interface Cat {
description: String
}

type Tabby implements Cat {
description: String
}

type Persian implements Cat {
description: String
}

type Siberian implements Cat {
description: String
}

type Aegean implements Cat {
description: String
}
==================================== OUTPUT ===================================
fragment fragmentOnAbstractTypeDisabledFragment on Cat {
... on Tabby {
description
}
... on Persian {
description
}
... on Siberian {
description
}
... on Aegean {
description
}
}
@@ -0,0 +1,27 @@
# relay-resolver-enable-interface-output-type

fragment fragmentOnAbstractTypeDisabledFragment on Cat {
description
}

# %extensions%

interface Cat {
description: String
}

type Tabby implements Cat {
description: String
}

type Persian implements Cat {
description: String
}

type Siberian implements Cat {
description: String
}

type Aegean implements Cat {
description: String
}
Expand Up @@ -4,17 +4,38 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<273ffd6866e55a9ed31d0fc960dcaac8>>
* @generated SignedSource<<d3d3fd84e6950808a9632e2da63de991>>
*/

mod relay_resolvers_abstract_types;

use relay_resolvers_abstract_types::transform_fixture;
use fixture_tests::test_fixture;

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

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

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

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

0 comments on commit 5718ca9

Please sign in to comment.