Skip to content

Commit ab280d2

Browse files
committed
graph, graphql: Encapsulate access to the underlying schema in ApiSchema
This just shuffles some code around, but doesn't change anything else, in preparation for representing the schema in a way that's more useful to us.
1 parent deaa15f commit ab280d2

File tree

8 files changed

+109
-76
lines changed

8 files changed

+109
-76
lines changed

graph/src/data/schema.rs

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ use std::iter::FromIterator;
2323
use std::str::FromStr;
2424
use std::sync::Arc;
2525

26+
use super::graphql::ObjectOrInterface;
27+
2628
pub const SCHEMA_TYPE_NAME: &str = "_Schema_";
2729

2830
pub const META_FIELD_TYPE: &str = "_Meta_";
@@ -349,7 +351,7 @@ impl SchemaReference {
349351

350352
#[derive(Debug)]
351353
pub struct ApiSchema {
352-
pub schema: Schema,
354+
schema: Schema,
353355

354356
// Root types for the api schema.
355357
pub query_type: Arc<ObjectType>,
@@ -425,16 +427,87 @@ impl ApiSchema {
425427
.expect("ApiSchema.object_type is only used with existing types")
426428
.cheap_clone()
427429
}
428-
}
429430

430-
fn add_introspection_schema(schema: &mut Document) {
431-
lazy_static! {
432-
static ref INTROSPECTION_SCHEMA: Document = {
433-
let schema = include_str!("introspection.graphql");
434-
parse_schema(schema).expect("the schema `introspection.graphql` is invalid")
435-
};
431+
pub fn get_named_type(&self, name: &str) -> Option<&TypeDefinition> {
432+
self.schema.document.get_named_type(name)
436433
}
437434

435+
/// Returns true if the given type is an input type.
436+
///
437+
/// Uses the algorithm outlined on
438+
/// https://facebook.github.io/graphql/draft/#IsInputType().
439+
pub fn is_input_type(&self, t: &s::Type) -> bool {
440+
match t {
441+
s::Type::NamedType(name) => {
442+
let named_type = self.get_named_type(name);
443+
named_type.map_or(false, |type_def| match type_def {
444+
s::TypeDefinition::Scalar(_)
445+
| s::TypeDefinition::Enum(_)
446+
| s::TypeDefinition::InputObject(_) => true,
447+
_ => false,
448+
})
449+
}
450+
s::Type::ListType(inner) => self.is_input_type(inner),
451+
s::Type::NonNullType(inner) => self.is_input_type(inner),
452+
}
453+
}
454+
455+
pub fn get_root_query_type_def(&self) -> Option<&s::TypeDefinition> {
456+
self.schema
457+
.document
458+
.definitions
459+
.iter()
460+
.find_map(|d| match d {
461+
s::Definition::TypeDefinition(def @ s::TypeDefinition::Object(_)) => match def {
462+
s::TypeDefinition::Object(t) if t.name == "Query" => Some(def),
463+
_ => None,
464+
},
465+
_ => None,
466+
})
467+
}
468+
469+
pub fn object_or_interface(&self, name: &str) -> Option<ObjectOrInterface<'_>> {
470+
if name.starts_with("__") {
471+
INTROSPECTION_SCHEMA.object_or_interface(name)
472+
} else {
473+
self.schema.document.object_or_interface(name)
474+
}
475+
}
476+
477+
/// Returns the type definition that a field type corresponds to.
478+
pub fn get_type_definition_from_field<'a>(
479+
&'a self,
480+
field: &s::Field,
481+
) -> Option<&'a s::TypeDefinition> {
482+
self.get_type_definition_from_type(&field.field_type)
483+
}
484+
485+
/// Returns the type definition for a type.
486+
pub fn get_type_definition_from_type<'a>(
487+
&'a self,
488+
t: &s::Type,
489+
) -> Option<&'a s::TypeDefinition> {
490+
match t {
491+
s::Type::NamedType(name) => self.get_named_type(name),
492+
s::Type::ListType(inner) => self.get_type_definition_from_type(inner),
493+
s::Type::NonNullType(inner) => self.get_type_definition_from_type(inner),
494+
}
495+
}
496+
497+
#[cfg(debug_assertions)]
498+
pub fn definitions(&self) -> impl Iterator<Item = &s::Definition<'static, String>> {
499+
self.schema.document.definitions.iter()
500+
}
501+
}
502+
503+
lazy_static! {
504+
static ref INTROSPECTION_SCHEMA: Document = {
505+
let schema = include_str!("introspection.graphql");
506+
parse_schema(schema).expect("the schema `introspection.graphql` is invalid")
507+
};
508+
}
509+
510+
fn add_introspection_schema(schema: &mut Document) {
438511
fn introspection_fields() -> Vec<Field> {
439512
// Generate fields for the root query fields in an introspection schema,
440513
// the equivalent of the fields of the `Query` type:

graphql/src/execution/ast.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{collections::HashSet, ops::Deref};
22

33
use graph::{
44
components::store::EntityType,
5-
data::graphql::{DocumentExt, ObjectOrInterface},
5+
data::graphql::ObjectOrInterface,
66
prelude::{anyhow, q, r, s, ApiSchema, QueryExecutionError, ValueMap},
77
};
88
use graphql_parser::Pos;
@@ -318,8 +318,6 @@ pub(crate) fn resolve_object_types(
318318
) -> Result<HashSet<ObjectType>, QueryExecutionError> {
319319
let mut set = HashSet::new();
320320
match schema
321-
.schema
322-
.document
323321
.get_named_type(name)
324322
.ok_or_else(|| QueryExecutionError::AbstractTypeError(name.to_string()))?
325323
{

graphql/src/execution/execution.rs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ use graph::util::lfu_cache::LfuCache;
1919

2020
use super::QueryHash;
2121
use crate::execution::ast as a;
22-
use crate::introspection::{
23-
is_introspection_field, INTROSPECTION_DOCUMENT, INTROSPECTION_QUERY_TYPE,
24-
};
22+
use crate::introspection::{is_introspection_field, INTROSPECTION_QUERY_TYPE};
2523
use crate::prelude::*;
2624
use crate::schema::ast as sast;
2725

@@ -208,15 +206,6 @@ where
208206
pub(crate) cache_status: AtomicCell<CacheStatus>,
209207
}
210208

211-
// Helpers to look for types and fields on both the introspection and regular schemas.
212-
pub(crate) fn get_named_type(schema: &s::Document, name: &str) -> Option<s::TypeDefinition> {
213-
if name.starts_with("__") {
214-
INTROSPECTION_DOCUMENT.get_named_type(name).cloned()
215-
} else {
216-
schema.get_named_type(name).cloned()
217-
}
218-
}
219-
220209
pub(crate) fn get_field<'a>(
221210
object_type: impl Into<ObjectOrInterface<'a>>,
222211
name: &str,
@@ -229,17 +218,6 @@ pub(crate) fn get_field<'a>(
229218
}
230219
}
231220

232-
pub(crate) fn object_or_interface<'a>(
233-
schema: &'a s::Document,
234-
name: &str,
235-
) -> Option<ObjectOrInterface<'a>> {
236-
if name.starts_with("__") {
237-
INTROSPECTION_DOCUMENT.object_or_interface(name)
238-
} else {
239-
schema.object_or_interface(name)
240-
}
241-
}
242-
243221
impl<R> ExecutionContext<R>
244222
where
245223
R: Resolver,
@@ -615,7 +593,6 @@ fn resolve_field_value_for_named_type(
615593
let named_type = ctx
616594
.query
617595
.schema
618-
.document()
619596
.get_named_type(type_name)
620597
.ok_or_else(|| QueryExecutionError::NamedTypeError(type_name.to_string()))?;
621598
match named_type {
@@ -671,7 +648,6 @@ fn resolve_field_value_for_list_type(
671648
let named_type = ctx
672649
.query
673650
.schema
674-
.document()
675651
.get_named_type(type_name)
676652
.ok_or_else(|| QueryExecutionError::NamedTypeError(type_name.to_string()))?;
677653

@@ -773,7 +749,7 @@ fn complete_value(
773749
}
774750

775751
s::Type::NamedType(name) => {
776-
let named_type = ctx.query.schema.document().get_named_type(name).unwrap();
752+
let named_type = ctx.query.schema.get_named_type(name).unwrap();
777753

778754
match named_type {
779755
// Complete scalar values
@@ -858,7 +834,7 @@ fn resolve_abstract_type<'a>(
858834
// yields nothing
859835
let obj_type = ctx
860836
.resolver
861-
.resolve_abstract_type(ctx.query.schema.document(), abstract_type, object_value)
837+
.resolve_abstract_type(&ctx.query.schema, abstract_type, object_value)
862838
.ok_or_else(|| {
863839
vec![QueryExecutionError::AbstractTypeError(
864840
sast::get_type_name(abstract_type).to_string(),

graphql/src/execution/query.rs

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ use std::sync::Arc;
1010
use std::time::Instant;
1111
use std::{collections::hash_map::DefaultHasher, convert::TryFrom};
1212

13-
use graph::data::graphql::{
14-
ext::{DocumentExt, TypeExt},
15-
ObjectOrInterface,
16-
};
13+
use graph::data::graphql::{ext::TypeExt, ObjectOrInterface};
1714
use graph::data::query::QueryExecutionError;
1815
use graph::data::query::{Query as GraphDataQuery, QueryVariables};
1916
use graph::data::schema::ApiSchema;
@@ -23,10 +20,7 @@ use crate::execution::ast as a;
2320
use crate::query::{ast as qast, ext::BlockConstraint};
2421
use crate::schema::ast as sast;
2522
use crate::values::coercion;
26-
use crate::{
27-
execution::{get_field, get_named_type, object_or_interface},
28-
schema::api::ErrorPolicy,
29-
};
23+
use crate::{execution::get_field, schema::api::ErrorPolicy};
3024

3125
lazy_static! {
3226
static ref GRAPHQL_VALIDATION_PLAN: ValidationPlan = ValidationPlan::from(
@@ -209,8 +203,8 @@ impl Query {
209203

210204
let start = Instant::now();
211205
let root_type = match kind {
212-
Kind::Query => schema.document().get_root_query_type().unwrap(),
213-
Kind::Subscription => schema.document().get_root_subscription_type().unwrap(),
206+
Kind::Query => schema.query_type.as_ref(),
207+
Kind::Subscription => schema.subscription_type.as_ref().unwrap(),
214208
};
215209
// Use an intermediate struct so we can modify the query before
216210
// enclosing it in an Arc
@@ -368,7 +362,7 @@ pub fn coerce_variables(
368362
.flatten()
369363
{
370364
// Skip variable if it has an invalid type
371-
if !sast::is_input_type(schema.document(), &variable_def.var_type) {
365+
if !schema.is_input_type(&variable_def.var_type) {
372366
errors.push(QueryExecutionError::InvalidVariableTypeError(
373367
variable_def.position,
374368
variable_def.name.to_owned(),
@@ -423,7 +417,7 @@ fn coerce_variable(
423417
) -> Result<r::Value, Vec<QueryExecutionError>> {
424418
use crate::values::coercion::coerce_value;
425419

426-
let resolver = |name: &str| schema.document().get_named_type(name);
420+
let resolver = |name: &str| schema.get_named_type(name);
427421

428422
coerce_value(value, &variable_def.var_type, &resolver).map_err(|value| {
429423
vec![QueryExecutionError::InvalidArgumentError(
@@ -482,7 +476,6 @@ impl<'s> RawQuery<'s> {
482476
.items
483477
.iter()
484478
.try_fold(0, |total_complexity, selection| {
485-
let schema = self.schema.document();
486479
match selection {
487480
q::Selection::Field(field) => {
488481
// Empty selection sets are the base case.
@@ -506,7 +499,8 @@ impl<'s> RawQuery<'s> {
506499
.ok_or(Invalid)?;
507500

508501
let field_complexity = self.complexity_inner(
509-
&get_named_type(schema, s_field.field_type.get_base_type())
502+
self.schema
503+
.get_named_type(s_field.field_type.get_base_type())
510504
.ok_or(Invalid)?,
511505
&field.selection_set,
512506
max_depth,
@@ -535,7 +529,7 @@ impl<'s> RawQuery<'s> {
535529
q::Selection::FragmentSpread(fragment) => {
536530
let def = self.fragments.get(&fragment.fragment_name).unwrap();
537531
let q::TypeCondition::On(type_name) = &def.type_condition;
538-
let ty = get_named_type(schema, &type_name).ok_or(Invalid)?;
532+
let ty = self.schema.get_named_type(&type_name).ok_or(Invalid)?;
539533

540534
// Copy `visited_fragments` on write.
541535
let mut visited_fragments = visited_fragments.clone();
@@ -553,12 +547,12 @@ impl<'s> RawQuery<'s> {
553547
q::Selection::InlineFragment(fragment) => {
554548
let ty = match &fragment.type_condition {
555549
Some(q::TypeCondition::On(type_name)) => {
556-
get_named_type(schema, &type_name).ok_or(Invalid)?
550+
self.schema.get_named_type(type_name).ok_or(Invalid)?
557551
}
558-
_ => ty.clone(),
552+
_ => ty,
559553
};
560554
self.complexity_inner(
561-
&ty,
555+
ty,
562556
&fragment.selection_set,
563557
max_depth,
564558
depth + 1,
@@ -575,7 +569,7 @@ impl<'s> RawQuery<'s> {
575569
/// If the query is invalid, returns `Ok(0)` so that execution proceeds and
576570
/// gives a proper error.
577571
fn complexity(&self, max_depth: u8) -> Result<u64, QueryExecutionError> {
578-
let root_type = sast::get_root_query_type_def(self.schema.document()).unwrap();
572+
let root_type = self.schema.get_root_query_type_def().unwrap();
579573

580574
match self.complexity_inner(
581575
root_type,
@@ -597,7 +591,7 @@ impl<'s> RawQuery<'s> {
597591
}
598592

599593
fn validate_fields(&self) -> Result<(), Vec<QueryExecutionError>> {
600-
let root_type = self.schema.document().get_root_query_type().unwrap();
594+
let root_type = self.schema.query_type.as_ref();
601595

602596
let errors =
603597
self.validate_fields_inner(&"Query".to_owned(), root_type.into(), &self.selection_set);
@@ -615,8 +609,6 @@ impl<'s> RawQuery<'s> {
615609
ty: ObjectOrInterface<'_>,
616610
selection_set: &q::SelectionSet,
617611
) -> Vec<QueryExecutionError> {
618-
let schema = self.schema.document();
619-
620612
selection_set
621613
.items
622614
.iter()
@@ -625,9 +617,9 @@ impl<'s> RawQuery<'s> {
625617
q::Selection::Field(field) => match get_field(ty, &field.name) {
626618
Some(s_field) => {
627619
let base_type = s_field.field_type.get_base_type();
628-
if get_named_type(schema, base_type).is_none() {
620+
if self.schema.get_named_type(base_type).is_none() {
629621
errors.push(QueryExecutionError::NamedTypeError(base_type.into()));
630-
} else if let Some(ty) = object_or_interface(schema, base_type) {
622+
} else if let Some(ty) = self.schema.object_or_interface(base_type) {
631623
errors.extend(self.validate_fields_inner(
632624
base_type,
633625
ty,
@@ -645,7 +637,7 @@ impl<'s> RawQuery<'s> {
645637
match self.fragments.get(&fragment.fragment_name) {
646638
Some(frag) => {
647639
let q::TypeCondition::On(type_name) = &frag.type_condition;
648-
match object_or_interface(schema, type_name) {
640+
match self.schema.object_or_interface(type_name) {
649641
Some(ty) => errors.extend(self.validate_fields_inner(
650642
type_name,
651643
ty,
@@ -663,7 +655,7 @@ impl<'s> RawQuery<'s> {
663655
}
664656
q::Selection::InlineFragment(fragment) => match &fragment.type_condition {
665657
Some(q::TypeCondition::On(type_name)) => {
666-
match object_or_interface(schema, type_name) {
658+
match self.schema.object_or_interface(type_name) {
667659
Some(ty) => errors.extend(self.validate_fields_inner(
668660
type_name,
669661
ty,
@@ -797,7 +789,7 @@ impl Transform {
797789
) -> Result<(), Vec<QueryExecutionError>> {
798790
let mut errors = vec![];
799791

800-
let resolver = |name: &str| self.schema.document().get_named_type(name);
792+
let resolver = |name: &str| self.schema.get_named_type(name);
801793

802794
for argument_def in sast::get_argument_definitions(ty, field_name)
803795
.into_iter()
@@ -864,7 +856,7 @@ impl Transform {
864856
let field_type = parent_type.field(&name).expect("field names are valid");
865857
let ty = field_type.field_type.get_base_type();
866858
let type_set = a::ObjectTypeSet::from_name(&self.schema, ty)?;
867-
let ty = self.schema.document().object_or_interface(ty).unwrap();
859+
let ty = self.schema.object_or_interface(ty).unwrap();
868860
self.expand_selection_set(selection_set, &type_set, ty)?
869861
};
870862

@@ -966,7 +958,6 @@ impl Transform {
966958
let ty = match frag_cond {
967959
Some(q::TypeCondition::On(name)) => self
968960
.schema
969-
.document()
970961
.object_or_interface(name)
971962
.expect("type names on fragment spreads are valid"),
972963
None => ty,

0 commit comments

Comments
 (0)