Skip to content

Commit 448b649

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 2652fe7 commit 448b649

File tree

8 files changed

+109
-80
lines changed

8 files changed

+109
-80
lines changed

graph/src/data/schema.rs

Lines changed: 81 additions & 12 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>,
@@ -394,10 +396,6 @@ impl ApiSchema {
394396
})
395397
}
396398

397-
pub fn document(&self) -> &s::Document {
398-
&self.schema.document
399-
}
400-
401399
pub fn id(&self) -> &DeploymentHash {
402400
&self.schema.id
403401
}
@@ -425,16 +423,87 @@ impl ApiSchema {
425423
.expect("ApiSchema.object_type is only used with existing types")
426424
.cheap_clone()
427425
}
428-
}
429426

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-
};
427+
pub fn get_named_type(&self, name: &str) -> Option<&TypeDefinition> {
428+
self.schema.document.get_named_type(name)
429+
}
430+
431+
/// Returns true if the given type is an input type.
432+
///
433+
/// Uses the algorithm outlined on
434+
/// https://facebook.github.io/graphql/draft/#IsInputType().
435+
pub fn is_input_type(&self, t: &s::Type) -> bool {
436+
match t {
437+
s::Type::NamedType(name) => {
438+
let named_type = self.get_named_type(name);
439+
named_type.map_or(false, |type_def| match type_def {
440+
s::TypeDefinition::Scalar(_)
441+
| s::TypeDefinition::Enum(_)
442+
| s::TypeDefinition::InputObject(_) => true,
443+
_ => false,
444+
})
445+
}
446+
s::Type::ListType(inner) => self.is_input_type(inner),
447+
s::Type::NonNullType(inner) => self.is_input_type(inner),
448+
}
436449
}
437450

451+
pub fn get_root_query_type_def(&self) -> Option<&s::TypeDefinition> {
452+
self.schema
453+
.document
454+
.definitions
455+
.iter()
456+
.find_map(|d| match d {
457+
s::Definition::TypeDefinition(def @ s::TypeDefinition::Object(_)) => match def {
458+
s::TypeDefinition::Object(t) if t.name == "Query" => Some(def),
459+
_ => None,
460+
},
461+
_ => None,
462+
})
463+
}
464+
465+
pub fn object_or_interface(&self, name: &str) -> Option<ObjectOrInterface<'_>> {
466+
if name.starts_with("__") {
467+
INTROSPECTION_SCHEMA.object_or_interface(name)
468+
} else {
469+
self.schema.document.object_or_interface(name)
470+
}
471+
}
472+
473+
/// Returns the type definition that a field type corresponds to.
474+
pub fn get_type_definition_from_field<'a>(
475+
&'a self,
476+
field: &s::Field,
477+
) -> Option<&'a s::TypeDefinition> {
478+
self.get_type_definition_from_type(&field.field_type)
479+
}
480+
481+
/// Returns the type definition for a type.
482+
pub fn get_type_definition_from_type<'a>(
483+
&'a self,
484+
t: &s::Type,
485+
) -> Option<&'a s::TypeDefinition> {
486+
match t {
487+
s::Type::NamedType(name) => self.get_named_type(name),
488+
s::Type::ListType(inner) => self.get_type_definition_from_type(inner),
489+
s::Type::NonNullType(inner) => self.get_type_definition_from_type(inner),
490+
}
491+
}
492+
493+
#[cfg(debug_assertions)]
494+
pub fn definitions(&self) -> impl Iterator<Item = &s::Definition<'static, String>> {
495+
self.schema.document.definitions.iter()
496+
}
497+
}
498+
499+
lazy_static! {
500+
static ref INTROSPECTION_SCHEMA: Document = {
501+
let schema = include_str!("introspection.graphql");
502+
parse_schema(schema).expect("the schema `introspection.graphql` is invalid")
503+
};
504+
}
505+
506+
fn add_introspection_schema(schema: &mut Document) {
438507
fn introspection_fields() -> Vec<Field> {
439508
// Generate fields for the root query fields in an introspection schema,
440509
// 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
@@ -7,10 +7,7 @@ use std::sync::Arc;
77
use std::time::Instant;
88
use std::{collections::hash_map::DefaultHasher, convert::TryFrom};
99

10-
use graph::data::graphql::{
11-
ext::{DocumentExt, TypeExt},
12-
ObjectOrInterface,
13-
};
10+
use graph::data::graphql::{ext::TypeExt, ObjectOrInterface};
1411
use graph::data::query::{Query as GraphDataQuery, QueryVariables};
1512
use graph::data::schema::ApiSchema;
1613
use graph::prelude::{
@@ -21,10 +18,7 @@ use crate::execution::ast as a;
2118
use crate::query::{ast as qast, ext::BlockConstraint};
2219
use crate::schema::ast as sast;
2320
use crate::values::coercion;
24-
use crate::{
25-
execution::{get_field, get_named_type, object_or_interface},
26-
schema::api::ErrorPolicy,
27-
};
21+
use crate::{execution::get_field, schema::api::ErrorPolicy};
2822

2923
#[derive(Clone, Debug)]
3024
pub enum ComplexityError {
@@ -157,8 +151,8 @@ impl Query {
157151

158152
let start = Instant::now();
159153
let root_type = match kind {
160-
Kind::Query => schema.document().get_root_query_type().unwrap(),
161-
Kind::Subscription => schema.document().get_root_subscription_type().unwrap(),
154+
Kind::Query => schema.query_type.as_ref(),
155+
Kind::Subscription => schema.subscription_type.as_ref().unwrap(),
162156
};
163157
// Use an intermediate struct so we can modify the query before
164158
// enclosing it in an Arc
@@ -316,7 +310,7 @@ pub fn coerce_variables(
316310
.flatten()
317311
{
318312
// Skip variable if it has an invalid type
319-
if !sast::is_input_type(schema.document(), &variable_def.var_type) {
313+
if !schema.is_input_type(&variable_def.var_type) {
320314
errors.push(QueryExecutionError::InvalidVariableTypeError(
321315
variable_def.position,
322316
variable_def.name.to_owned(),
@@ -371,7 +365,7 @@ fn coerce_variable(
371365
) -> Result<r::Value, Vec<QueryExecutionError>> {
372366
use crate::values::coercion::coerce_value;
373367

374-
let resolver = |name: &str| schema.document().get_named_type(name);
368+
let resolver = |name: &str| schema.get_named_type(name);
375369

376370
coerce_value(value, &variable_def.var_type, &resolver).map_err(|value| {
377371
vec![QueryExecutionError::InvalidArgumentError(
@@ -430,7 +424,6 @@ impl<'s> RawQuery<'s> {
430424
.items
431425
.iter()
432426
.try_fold(0, |total_complexity, selection| {
433-
let schema = self.schema.document();
434427
match selection {
435428
q::Selection::Field(field) => {
436429
// Empty selection sets are the base case.
@@ -454,7 +447,8 @@ impl<'s> RawQuery<'s> {
454447
.ok_or(Invalid)?;
455448

456449
let field_complexity = self.complexity_inner(
457-
&get_named_type(schema, s_field.field_type.get_base_type())
450+
self.schema
451+
.get_named_type(s_field.field_type.get_base_type())
458452
.ok_or(Invalid)?,
459453
&field.selection_set,
460454
max_depth,
@@ -483,7 +477,7 @@ impl<'s> RawQuery<'s> {
483477
q::Selection::FragmentSpread(fragment) => {
484478
let def = self.fragments.get(&fragment.fragment_name).unwrap();
485479
let q::TypeCondition::On(type_name) = &def.type_condition;
486-
let ty = get_named_type(schema, &type_name).ok_or(Invalid)?;
480+
let ty = self.schema.get_named_type(&type_name).ok_or(Invalid)?;
487481

488482
// Copy `visited_fragments` on write.
489483
let mut visited_fragments = visited_fragments.clone();
@@ -501,12 +495,12 @@ impl<'s> RawQuery<'s> {
501495
q::Selection::InlineFragment(fragment) => {
502496
let ty = match &fragment.type_condition {
503497
Some(q::TypeCondition::On(type_name)) => {
504-
get_named_type(schema, &type_name).ok_or(Invalid)?
498+
self.schema.get_named_type(type_name).ok_or(Invalid)?
505499
}
506-
_ => ty.clone(),
500+
_ => ty,
507501
};
508502
self.complexity_inner(
509-
&ty,
503+
ty,
510504
&fragment.selection_set,
511505
max_depth,
512506
depth + 1,
@@ -523,7 +517,7 @@ impl<'s> RawQuery<'s> {
523517
/// If the query is invalid, returns `Ok(0)` so that execution proceeds and
524518
/// gives a proper error.
525519
fn complexity(&self, max_depth: u8) -> Result<u64, QueryExecutionError> {
526-
let root_type = sast::get_root_query_type_def(self.schema.document()).unwrap();
520+
let root_type = self.schema.get_root_query_type_def().unwrap();
527521

528522
match self.complexity_inner(
529523
root_type,
@@ -545,7 +539,7 @@ impl<'s> RawQuery<'s> {
545539
}
546540

547541
fn validate_fields(&self) -> Result<(), Vec<QueryExecutionError>> {
548-
let root_type = self.schema.document().get_root_query_type().unwrap();
542+
let root_type = self.schema.query_type.as_ref();
549543

550544
let errors =
551545
self.validate_fields_inner(&"Query".to_owned(), root_type.into(), &self.selection_set);
@@ -563,8 +557,6 @@ impl<'s> RawQuery<'s> {
563557
ty: ObjectOrInterface<'_>,
564558
selection_set: &q::SelectionSet,
565559
) -> Vec<QueryExecutionError> {
566-
let schema = self.schema.document();
567-
568560
selection_set
569561
.items
570562
.iter()
@@ -573,9 +565,9 @@ impl<'s> RawQuery<'s> {
573565
q::Selection::Field(field) => match get_field(ty, &field.name) {
574566
Some(s_field) => {
575567
let base_type = s_field.field_type.get_base_type();
576-
if get_named_type(schema, base_type).is_none() {
568+
if self.schema.get_named_type(base_type).is_none() {
577569
errors.push(QueryExecutionError::NamedTypeError(base_type.into()));
578-
} else if let Some(ty) = object_or_interface(schema, base_type) {
570+
} else if let Some(ty) = self.schema.object_or_interface(base_type) {
579571
errors.extend(self.validate_fields_inner(
580572
base_type,
581573
ty,
@@ -593,7 +585,7 @@ impl<'s> RawQuery<'s> {
593585
match self.fragments.get(&fragment.fragment_name) {
594586
Some(frag) => {
595587
let q::TypeCondition::On(type_name) = &frag.type_condition;
596-
match object_or_interface(schema, type_name) {
588+
match self.schema.object_or_interface(type_name) {
597589
Some(ty) => errors.extend(self.validate_fields_inner(
598590
type_name,
599591
ty,
@@ -611,7 +603,7 @@ impl<'s> RawQuery<'s> {
611603
}
612604
q::Selection::InlineFragment(fragment) => match &fragment.type_condition {
613605
Some(q::TypeCondition::On(type_name)) => {
614-
match object_or_interface(schema, type_name) {
606+
match self.schema.object_or_interface(type_name) {
615607
Some(ty) => errors.extend(self.validate_fields_inner(
616608
type_name,
617609
ty,
@@ -745,7 +737,7 @@ impl Transform {
745737
) -> Result<(), Vec<QueryExecutionError>> {
746738
let mut errors = vec![];
747739

748-
let resolver = |name: &str| self.schema.document().get_named_type(name);
740+
let resolver = |name: &str| self.schema.get_named_type(name);
749741

750742
for argument_def in sast::get_argument_definitions(ty, field_name)
751743
.into_iter()
@@ -812,7 +804,7 @@ impl Transform {
812804
let field_type = parent_type.field(&name).expect("field names are valid");
813805
let ty = field_type.field_type.get_base_type();
814806
let type_set = a::ObjectTypeSet::from_name(&self.schema, ty)?;
815-
let ty = self.schema.document().object_or_interface(ty).unwrap();
807+
let ty = self.schema.object_or_interface(ty).unwrap();
816808
self.expand_selection_set(selection_set, &type_set, ty)?
817809
};
818810

@@ -914,7 +906,6 @@ impl Transform {
914906
let ty = match frag_cond {
915907
Some(q::TypeCondition::On(name)) => self
916908
.schema
917-
.document()
918909
.object_or_interface(name)
919910
.expect("type names on fragment spreads are valid"),
920911
None => ty,

0 commit comments

Comments
 (0)