Skip to content

Commit

Permalink
factor out @refetchable directive parsing
Browse files Browse the repository at this point in the history
Reviewed By: alunyov

Differential Revision: D27631808

fbshipit-source-id: 1dca51aae835fadeb6ec19333353201e80bba89b
  • Loading branch information
kassens authored and facebook-github-bot committed Apr 8, 2021
1 parent b8d2694 commit d6bb6b0
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 74 deletions.
3 changes: 0 additions & 3 deletions compiler/crates/graphql-ir/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,9 +448,6 @@ pub enum ValidationMessage {
variables_string: String,
},

#[error("Expected the 'queryName' argument of @refetchable to be provided")]
QueryNameRequired,

#[error(
"Expected the 'queryName' argument of @refetchable to be a string, got '{query_name_value}"
)]
Expand Down
121 changes: 54 additions & 67 deletions compiler/crates/relay-transforms/src/refetchable_fragment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
mod fetchable_query_generator;
mod node_query_generator;
mod query_query_generator;
mod refetchable_directive;
mod utils;
mod viewer_query_generator;

use crate::connections::{extract_connection_metadata_from_directive, ConnectionConstants};
use crate::root_variables::{InferVariablesVisitor, VariableMap};

use common::{Diagnostic, DiagnosticsResult, Location, NamedItem, WithLocation};
use common::{Diagnostic, DiagnosticsResult, WithLocation};
use errors::validate_map;
use fetchable_query_generator::FETCHABLE_QUERY_GENERATOR;
use fnv::{FnvHashMap, FnvHashSet};
Expand All @@ -23,7 +24,6 @@ use graphql_ir::{
VariableDefinition,
};
use graphql_syntax::OperationKind;
use graphql_text_printer::print_value;
use interner::StringKey;
use node_query_generator::NODE_QUERY_GENERATOR;
use query_query_generator::QUERY_QUERY_GENERATOR;
Expand All @@ -36,6 +36,8 @@ pub use utils::{
};
use viewer_query_generator::VIEWER_QUERY_GENERATOR;

use self::refetchable_directive::RefetchableDirective;

/// This transform synthesizes "refetch" queries for fragments that
/// are trivially refetchable. This is comprised of three main stages:
///
Expand Down Expand Up @@ -110,35 +112,23 @@ impl RefetchableFragment<'_> {
&mut self,
fragment: &Arc<FragmentDefinition>,
) -> DiagnosticsResult<Option<(StringKey, RefetchRoot)>> {
if let Some((refetch_name, refetch_name_location)) =
self.get_refetch_query_name(fragment)?
if let Some(refetchable_directive) =
RefetchableDirective::from_directives(&self.program.schema, &fragment.directives)?
{
if let Some(existing_query) = self.program.operation(refetch_name) {
return Err(vec![
Diagnostic::error(
ValidationMessage::RefetchableQueryConflictWithQuery {
query_name: refetch_name,
},
refetch_name_location,
)
.annotate(
"an operation with that name is already defined here",
existing_query.name.location,
),
]);
}
self.validate_refetch_name(fragment, &refetchable_directive)?;

let variables_map = self.visitor.infer_fragment_variables(fragment);
for generator in GENERATORS.iter() {
if let Some(refetch_root) = (generator.build_refetch_operation)(
&self.program.schema,
fragment,
refetch_name,
refetchable_directive.query_name.item,
&variables_map,
)? {
if !self.for_typegen {
self.validate_connection_metadata(refetch_root.fragment.as_ref())?;
}
return Ok(Some((refetch_name, refetch_root)));
return Ok(Some((refetchable_directive.query_name.item, refetch_root)));
}
}
let mut descriptions = String::new();
Expand All @@ -158,57 +148,54 @@ impl RefetchableFragment<'_> {
}
}

fn get_refetch_query_name(
fn validate_refetch_name(
&mut self,
fragment: &FragmentDefinition,
) -> DiagnosticsResult<Option<(StringKey, Location)>> {
if let Some(directive) = fragment.directives.named(CONSTANTS.refetchable_name) {
if let Some(query_name_arg) = directive.arguments.named(CONSTANTS.query_name_arg) {
if let Some(query_name) = query_name_arg.value.item.get_string_literal() {
if let Some(previous_fragment) = self
.existing_refetch_operations
.insert(query_name, fragment.name)
{
let (first_fragment, second_fragment) =
if fragment.name.item > previous_fragment.item {
(previous_fragment, fragment.name)
} else {
(fragment.name, previous_fragment)
};
Err(vec![
Diagnostic::error(
ValidationMessage::DuplicateRefetchableOperation {
query_name,
first_fragment_name: first_fragment.item,
second_fragment_name: second_fragment.item,
},
first_fragment.location,
)
.annotate("also defined here", second_fragment.location),
])
} else {
Ok(Some((query_name, query_name_arg.value.location)))
}
} else {
Err(vec![Diagnostic::error(
ValidationMessage::ExpectQueryNameToBeString {
query_name_value: print_value(
&self.program.schema,
&query_name_arg.value.item,
),
},
query_name_arg.name.location,
)])
}
refetchable_directive: &RefetchableDirective,
) -> DiagnosticsResult<()> {
// check for conflict with other @refetchable names
if let Some(previous_fragment) = self
.existing_refetch_operations
.insert(refetchable_directive.query_name.item, fragment.name)
{
let (first_fragment, second_fragment) = if fragment.name.item > previous_fragment.item {
(previous_fragment, fragment.name)
} else {
Err(vec![Diagnostic::error(
ValidationMessage::QueryNameRequired,
directive.name.location,
)])
}
} else {
Ok(None)
(fragment.name, previous_fragment)
};
return Err(vec![
Diagnostic::error(
ValidationMessage::DuplicateRefetchableOperation {
query_name: refetchable_directive.query_name.item,
first_fragment_name: first_fragment.item,
second_fragment_name: second_fragment.item,
},
first_fragment.location,
)
.annotate("also defined here", second_fragment.location),
]);
}

// check for conflict with operations
if let Some(existing_query) = self
.program
.operation(refetchable_directive.query_name.item)
{
return Err(vec![
Diagnostic::error(
ValidationMessage::RefetchableQueryConflictWithQuery {
query_name: refetchable_directive.query_name.item,
},
refetchable_directive.query_name.location,
)
.annotate(
"an operation with that name is already defined here",
existing_query.name.location,
),
]);
}

Ok(())
}

/// Validate that any @connection usage is valid for refetching:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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 common::{Diagnostic, DiagnosticsResult, NamedItem, WithLocation};
use graphql_ir::{Directive, ValidationMessage};
use graphql_text_printer::print_value;
use interner::{Intern, StringKey};
use lazy_static::lazy_static;
use schema::SDLSchema;

lazy_static! {
static ref REFETCHABLE_NAME: StringKey = "refetchable".intern();
static ref QUERY_NAME_ARG: StringKey = "queryName".intern();
}

/// Represents the @refetchable Relay directive:
///
/// ```graphql
/// directive @refetchable(
/// queryName: String!
/// ) on FRAGMENT_DEFINITION
/// ```
pub struct RefetchableDirective {
pub query_name: WithLocation<StringKey>,
}

impl RefetchableDirective {
pub fn from_directives(
schema: &SDLSchema,
directives: &[Directive],
) -> DiagnosticsResult<Option<Self>> {
if let Some(directive) = directives.named(*REFETCHABLE_NAME) {
Ok(Some(Self::from_directive(schema, directive)?))
} else {
Ok(None)
}
}

fn from_directive(schema: &SDLSchema, directive: &Directive) -> DiagnosticsResult<Self> {
let mut name = None;
for argument in &directive.arguments {
if argument.name.item == *QUERY_NAME_ARG {
if let Some(query_name) = argument.value.item.get_string_literal() {
name = Some(WithLocation::new(argument.value.location, query_name));
} else {
return Err(vec![Diagnostic::error(
ValidationMessage::ExpectQueryNameToBeString {
query_name_value: print_value(schema, &argument.value.item),
},
argument.name.location,
)]);
}
} else {
// should be validated by general directive validations
panic!(
"Unexpected name on @refetchable query: `{}`",
argument.name.item
)
}
}
Ok(Self {
query_name: name.unwrap(),
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ pub struct Constants {
pub identifier_field_arg: StringKey,
pub node_field_name: StringKey,
pub node_type_name: StringKey,
pub query_name_arg: StringKey,
pub refetchable_metadata_name: StringKey,
pub refetchable_name: StringKey,
pub refetchable_operation_metadata_name: StringKey,
pub viewer_field_name: StringKey,
pub viewer_type_name: StringKey,
Expand All @@ -44,9 +42,7 @@ lazy_static! {
identifier_field_arg: "fragmentPathInResult".intern(),
node_field_name: "node".intern(),
node_type_name: "Node".intern(),
query_name_arg: "queryName".intern(),
refetchable_metadata_name: "__refetchableMetadata".intern(),
refetchable_name: "refetchable".intern(),
refetchable_operation_metadata_name: "__refetchableQueryMetadata".intern(),
viewer_field_name: "viewer".intern(),
viewer_type_name: "Viewer".intern(),
Expand Down

0 comments on commit d6bb6b0

Please sign in to comment.