Skip to content

Commit

Permalink
ViewerQueryGenerator
Browse files Browse the repository at this point in the history
Reviewed By: kassens

Differential Revision: D21450535

fbshipit-source-id: 9ad6f4b15a31663719bbc60190c7b8adfd8bd16c
  • Loading branch information
tyao1 authored and facebook-github-bot committed May 7, 2020
1 parent 74b8420 commit f95b903
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 2 deletions.
5 changes: 5 additions & 0 deletions compiler/crates/graphql-ir/src/errors.rs
Expand Up @@ -418,4 +418,9 @@ pub enum ValidationMessage {
"Invalid use of @refetchable on fragment '{fragment_name}', check that your schema defines a `Node {{ id: ID }}` interface and has a `node(id: ID): Node` field on the query type (the id argument may also be non-null)."
)]
InvalidNodeSchemaForRefetchableFragmentOnNode { fragment_name: StringKey },

#[error(
"Invalid use of @refetchable on fragment '{fragment_name}', check that your schema defines a 'Viewer' object type and has a 'viewer: Viewer' field on the query type."
)]
InvalidViewerSchemaForRefetchableFragmentOnViewer { fragment_name: StringKey },
}
Expand Up @@ -8,6 +8,7 @@
mod node_query_generator;
mod query_query_generator;
mod utils;
mod viewer_query_generator;

use crate::root_variables::{InferVariablesVisitor, VariableMap};
use common::WithLocation;
Expand All @@ -25,6 +26,7 @@ use schema::Schema;
use std::fmt::Write;
use std::sync::Arc;
use utils::*;
use viewer_query_generator::VIEWER_QUERY_GENERATOR;

/// This transform synthesizes "refetch" queries for fragments that
/// are trivially refetchable. This is comprised of three main stages:
Expand Down Expand Up @@ -183,7 +185,11 @@ pub struct QueryGenerator {
pub build_refetch_operation: BuildRefetchOperationFn,
}

const GENERATORS: [QueryGenerator; 2] = [QUERY_QUERY_GENERATOR, NODE_QUERY_GENERATOR];
const GENERATORS: [QueryGenerator; 3] = [
VIEWER_QUERY_GENERATOR,
QUERY_QUERY_GENERATOR,
NODE_QUERY_GENERATOR,
];

#[allow(dead_code)]
pub struct RefetchRoot {
Expand Down
Expand Up @@ -18,6 +18,8 @@ pub struct Constants {
pub id_name: StringKey,
pub node_field_name: StringKey,
pub node_type_name: StringKey,
pub viewer_type_name: StringKey,
pub viewer_field_name: StringKey,
pub query_name_arg: StringKey,
pub refetchable_name: StringKey,
}
Expand All @@ -29,6 +31,8 @@ lazy_static! {
node_type_name: "Node".intern(),
query_name_arg: "queryName".intern(),
refetchable_name: "refetchable".intern(),
viewer_type_name: "Viewer".intern(),
viewer_field_name: "viewer".intern(),
};
}

Expand Down
@@ -0,0 +1,89 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

use super::{
build_fragment_spread, build_operation_variable_definitions, QueryGenerator, RefetchRoot,
CONSTANTS,
};
use crate::root_variables::VariableMap;
use common::WithLocation;
use graphql_ir::{
FragmentDefinition, LinkedField, OperationDefinition, Selection, ValidationError,
ValidationMessage, ValidationResult,
};
use graphql_syntax::OperationKind;
use interner::StringKey;
use schema::{FieldID, Schema, Type};
use std::sync::Arc;

fn build_refetch_operation(
schema: &Schema,
fragment: &Arc<FragmentDefinition>,
query_name: StringKey,
variables_map: &VariableMap,
) -> ValidationResult<Option<RefetchRoot>> {
if schema.get_type_name(fragment.type_condition) != CONSTANTS.viewer_type_name {
return Ok(None);
}
let query_type = schema.query_type().unwrap();
let viewer_field_id = get_viewer_field_id(schema, query_type, fragment)?;

Ok(Some(RefetchRoot {
identifier_field: None,
path: vec![CONSTANTS.viewer_field_name],
operation: Arc::new(OperationDefinition {
kind: OperationKind::Query,
name: WithLocation::new(fragment.name.location, query_name),
type_: query_type,
variable_definitions: build_operation_variable_definitions(
variables_map,
&fragment.variable_definitions,
),
directives: vec![],
selections: vec![Selection::LinkedField(Arc::new(LinkedField {
alias: None,
definition: WithLocation::new(fragment.name.location, viewer_field_id),
arguments: vec![],
directives: vec![],
selections: vec![build_fragment_spread(fragment)],
}))],
}),
fragment: Arc::clone(fragment),
}))
}

fn get_viewer_field_id(
schema: &Schema,
query_type: Type,
fragment: &FragmentDefinition,
) -> ValidationResult<FieldID> {
let viewer_type = schema.get_type(CONSTANTS.viewer_type_name);
let viewer_field_id = schema.named_field(query_type, CONSTANTS.viewer_field_name);
if let Some(viewer_type) = viewer_type {
if let Some(viewer_field_id) = viewer_field_id {
let viewer_field = schema.field(viewer_field_id);
if viewer_type.is_object()
&& viewer_type == viewer_field.type_.inner()
&& viewer_type == fragment.type_condition
&& viewer_field.arguments.is_empty()
{
return Ok(viewer_field_id);
}
}
}
Err(vec![ValidationError::new(
ValidationMessage::InvalidViewerSchemaForRefetchableFragmentOnViewer {
fragment_name: fragment.name.item,
},
vec![fragment.name.location],
)])
}

pub const VIEWER_QUERY_GENERATOR: QueryGenerator = QueryGenerator {
description: "the Viewer type",
build_refetch_operation,
};
Expand Up @@ -6,6 +6,7 @@ fragment UserName on UserNameRenderable
}
==================================== ERROR ====================================
Invalid use of @refetchable on fragment 'UserName', only supported are fragments on:
- the Viewer type
- the Query type
- the Node interface or types implementing the Node interface:
fragment-on-interface-which-implmentations-not-implement-node.invalid.graphql:1:10:
Expand Down
@@ -0,0 +1,37 @@
==================================== INPUT ====================================
fragment RefetchableFragment on Viewer
@refetchable(queryName: "RefetchableFragmentQuery") {
actor {
id
name
...ProfilePicture
}
}

fragment ProfilePicture on User {
profilePicture(size: $size) {
uri
}
}
==================================== OUTPUT ===================================
query RefetchableFragmentQuery(
$size: [Int]
) {
viewer {
...RefetchableFragment
}
}

fragment ProfilePicture on User {
profilePicture(size: $size) {
uri
}
}

fragment RefetchableFragment on Viewer @refetchable(queryName: "RefetchableFragmentQuery") {
actor {
id
name
...ProfilePicture
}
}
@@ -0,0 +1,49 @@
==================================== INPUT ====================================
fragment RefetchableFragment on Viewer
@refetchable(queryName: "RefetchableFragmentQuery")
@argumentDefinitions(size: {type: "[Int]"}) {
actor {
id
name
...ProfilePicture @arguments(size: $size)
}
}

fragment ProfilePicture on User @argumentDefinitions(size: {type: "[Int]"}) {
pic: profilePicture(size: $size) {
uri
}
profilePicture(size: $rootSize) {
uri
}
}
==================================== OUTPUT ===================================
query RefetchableFragmentQuery(
$rootSize: [Int]
$size: [Int]
) {
viewer {
...RefetchableFragment @arguments(size: $size)
}
}

fragment ProfilePicture on User @argumentDefinitions(
size: {type: "[Int]"}
) {
pic: profilePicture(size: $size) {
uri
}
profilePicture(size: $rootSize) {
uri
}
}

fragment RefetchableFragment on Viewer @argumentDefinitions(
size: {type: "[Int]"}
) @refetchable(queryName: "RefetchableFragmentQuery") {
actor {
id
name
...ProfilePicture @arguments(size: $size)
}
}
@@ -1,4 +1,4 @@
// @generated SignedSource<<c060406e062d3aa771e9914ad63f4f8c>>
// @generated SignedSource<<d7341dfcdb893900eeaa099faf47f0c4>>

mod refetchable_fragment;

Expand Down Expand Up @@ -68,6 +68,13 @@ fn fragment_on_query_without_query_name_invalid() {
test_fixture(transform_fixture, "fragment-on-query-without-query-name.invalid.graphql", "refetchable_fragment/fixtures/fragment-on-query-without-query-name.invalid.expected", input, expected);
}

#[test]
fn fragment_on_viewer() {
let input = include_str!("refetchable_fragment/fixtures/fragment-on-viewer.graphql");
let expected = include_str!("refetchable_fragment/fixtures/fragment-on-viewer.expected");
test_fixture(transform_fixture, "fragment-on-viewer.graphql", "refetchable_fragment/fixtures/fragment-on-viewer.expected", input, expected);
}

#[test]
fn fragment_with_args_on_object_implementing_node_interface() {
let input = include_str!("refetchable_fragment/fixtures/fragment-with-args-on-object-implementing-node-interface.graphql");
Expand All @@ -82,6 +89,13 @@ fn fragment_with_args_on_query() {
test_fixture(transform_fixture, "fragment-with-args-on-query.graphql", "refetchable_fragment/fixtures/fragment-with-args-on-query.expected", input, expected);
}

#[test]
fn fragment_with_args_on_viewer() {
let input = include_str!("refetchable_fragment/fixtures/fragment-with-args-on-viewer.graphql");
let expected = include_str!("refetchable_fragment/fixtures/fragment-with-args-on-viewer.expected");
test_fixture(transform_fixture, "fragment-with-args-on-viewer.graphql", "refetchable_fragment/fixtures/fragment-with-args-on-viewer.expected", input, expected);
}

#[test]
fn refetchable_fragment_with_connection() {
let input = include_str!("refetchable_fragment/fixtures/refetchable-fragment-with-connection.graphql");
Expand Down

0 comments on commit f95b903

Please sign in to comment.