compiler/crates/schema/src/in_memory/mod.rs (1,387 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use crate::definitions::{Argument, Directive, *};
use crate::errors::SchemaError;
use crate::graphql_schema::Schema;
use common::{Diagnostic, DiagnosticsResult, Location, SourceLocationKey, WithLocation};
use graphql_syntax::*;
use intern::string_key::{Intern, StringKey};
use std::collections::{BTreeMap, HashMap};
fn todo_add_location<T>(error: SchemaError) -> DiagnosticsResult<T> {
Err(vec![Diagnostic::error(error, Location::generated())])
}
#[derive(Debug)]
pub struct InMemorySchema {
query_type: Option<ObjectID>,
mutation_type: Option<ObjectID>,
subscription_type: Option<ObjectID>,
type_map: TypeMap,
clientid_field: FieldID,
strongid_field: FieldID,
typename_field: FieldID,
fetch_token_field: FieldID,
is_fulfilled_field: FieldID,
clientid_field_name: StringKey,
strongid_field_name: StringKey,
typename_field_name: StringKey,
fetch_token_field_name: StringKey,
is_fulfilled_field_name: StringKey,
string_type: Option<Type>,
id_type: Option<Type>,
unchecked_argument_type_sentinel: Option<TypeReference>,
directives: HashMap<StringKey, Directive>,
enums: Vec<Enum>,
fields: Vec<Field>,
input_objects: Vec<InputObject>,
interfaces: Vec<Interface>,
objects: Vec<Object>,
scalars: Vec<Scalar>,
unions: Vec<Union>,
}
impl Schema for InMemorySchema {
fn query_type(&self) -> Option<Type> {
self.query_type.map(Type::Object)
}
fn mutation_type(&self) -> Option<Type> {
self.mutation_type.map(Type::Object)
}
fn subscription_type(&self) -> Option<Type> {
self.subscription_type.map(Type::Object)
}
fn clientid_field(&self) -> FieldID {
self.clientid_field
}
fn strongid_field(&self) -> FieldID {
self.strongid_field
}
fn typename_field(&self) -> FieldID {
self.typename_field
}
fn fetch_token_field(&self) -> FieldID {
self.fetch_token_field
}
fn is_fulfilled_field(&self) -> FieldID {
self.is_fulfilled_field
}
fn get_type(&self, type_name: StringKey) -> Option<Type> {
self.type_map.get(&type_name).copied()
}
fn get_directive(&self, name: StringKey) -> Option<&Directive> {
self.directives.get(&name)
}
fn input_object(&self, id: InputObjectID) -> &InputObject {
&self.input_objects[id.as_usize()]
}
fn enum_(&self, id: EnumID) -> &Enum {
&self.enums[id.as_usize()]
}
fn scalar(&self, id: ScalarID) -> &Scalar {
&self.scalars[id.as_usize()]
}
fn field(&self, id: FieldID) -> &Field {
&self.fields[id.as_usize()]
}
fn object(&self, id: ObjectID) -> &Object {
&self.objects[id.as_usize()]
}
fn union(&self, id: UnionID) -> &Union {
&self.unions[id.as_usize()]
}
fn interface(&self, id: InterfaceID) -> &Interface {
&self.interfaces[id.as_usize()]
}
fn get_type_name(&self, type_: Type) -> StringKey {
match type_ {
Type::Enum(id) => self.enums[id.as_usize()].name,
Type::InputObject(id) => self.input_objects[id.as_usize()].name,
Type::Interface(id) => self.interfaces[id.as_usize()].name,
Type::Object(id) => self.objects[id.as_usize()].name.item,
Type::Scalar(id) => self.scalars[id.as_usize()].name,
Type::Union(id) => self.unions[id.as_usize()].name,
}
}
fn is_extension_type(&self, type_: Type) -> bool {
match type_ {
Type::Enum(id) => self.enums[id.as_usize()].is_extension,
Type::Interface(id) => self.interfaces[id.as_usize()].is_extension,
Type::Object(id) => self.objects[id.as_usize()].is_extension,
Type::Scalar(id) => self.scalars[id.as_usize()].is_extension,
Type::Union(id) => self.unions[id.as_usize()].is_extension,
Type::InputObject(_) => false,
}
}
fn is_string(&self, type_: Type) -> bool {
type_ == self.string_type.unwrap()
}
fn is_id(&self, type_: Type) -> bool {
type_ == self.id_type.unwrap()
}
fn named_field(&self, parent_type: Type, name: StringKey) -> Option<FieldID> {
// Special case for __typename and __id fields, which should not be in the list of type fields
// but should be fine to select.
let can_have_typename = matches!(
parent_type,
Type::Object(_) | Type::Interface(_) | Type::Union(_)
);
if can_have_typename {
if name == self.typename_field_name {
return Some(self.typename_field);
}
// TODO(inanc): Also check if the parent type is fetchable?
if name == self.fetch_token_field_name {
return Some(self.fetch_token_field);
}
if name == self.clientid_field_name {
return Some(self.clientid_field);
}
if name == self.strongid_field_name {
return Some(self.strongid_field);
}
}
let fields = match parent_type {
Type::Object(id) => {
let object = &self.objects[id.as_usize()];
&object.fields
}
Type::Interface(id) => {
let interface = &self.interfaces[id.as_usize()];
&interface.fields
}
// Unions don't have any fields, but can have selections like __typename
// or a field with @fixme_fat_interface
Type::Union(_) => return None,
_ => panic!(
"Cannot get field {} on type '{:?}', this type does not have fields",
name,
self.get_type_name(parent_type)
),
};
fields
.iter()
.find(|field_id| {
let field = &self.fields[field_id.as_usize()];
field.name.item == name
})
.cloned()
}
/// A value that represents a type of unchecked arguments where we don't
/// have a type to instantiate the argument.
///
/// TODO: we probably want to replace this with a proper `Unknown` type.
fn unchecked_argument_type_sentinel(&self) -> &TypeReference {
self.unchecked_argument_type_sentinel.as_ref().unwrap()
}
fn snapshot_print(&self) -> String {
let Self {
query_type,
mutation_type,
subscription_type,
directives,
clientid_field: _clientid_field,
strongid_field: _strongid_field,
typename_field: _typename_field,
fetch_token_field: _fetch_token_field,
is_fulfilled_field: _is_fulfilled_field,
clientid_field_name: _clientid_field_name,
strongid_field_name: _strongid_field_name,
typename_field_name: _typename_field_name,
fetch_token_field_name: _fetch_token_field_name,
is_fulfilled_field_name: _is_fulfilled_field_name,
string_type: _string_type,
id_type: _id_type,
unchecked_argument_type_sentinel: _unchecked_argument_type_sentinel,
type_map,
enums,
fields,
input_objects,
interfaces,
objects,
scalars,
unions,
} = self;
let ordered_type_map: BTreeMap<_, _> = type_map.iter().collect();
let mut ordered_directives = directives.values().collect::<Vec<&Directive>>();
ordered_directives.sort_by_key(|dir| dir.name.lookup());
format!(
r#"Schema {{
query_type: {:#?}
mutation_type: {:#?}
subscription_type: {:#?}
directives: {:#?}
type_map: {:#?}
enums: {:#?}
fields: {:#?}
input_objects: {:#?}
interfaces: {:#?}
objects: {:#?}
scalars: {:#?}
unions: {:#?}
}}"#,
query_type,
mutation_type,
subscription_type,
ordered_directives,
ordered_type_map,
enums,
fields,
input_objects,
interfaces,
objects,
scalars,
unions,
)
}
fn input_objects<'a>(&'a self) -> Box<dyn Iterator<Item = &'a InputObject> + 'a> {
Box::new(self.input_objects.iter())
}
fn enums<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Enum> + 'a> {
Box::new(self.enums.iter())
}
fn scalars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Scalar> + 'a> {
Box::new(self.scalars.iter())
}
fn fields<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Field> + 'a> {
Box::new(self.fields.iter())
}
fn objects<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Object> + 'a> {
Box::new(self.objects.iter())
}
fn unions<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Union> + 'a> {
Box::new(self.unions.iter())
}
fn interfaces<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Interface> + 'a> {
Box::new(self.interfaces.iter())
}
}
impl InMemorySchema {
pub fn get_directive_mut(&mut self, name: StringKey) -> Option<&mut Directive> {
self.directives.get_mut(&name)
}
pub fn get_type_map(&self) -> impl Iterator<Item = (&StringKey, &Type)> {
self.type_map.iter()
}
pub fn get_directives(&self) -> impl Iterator<Item = &Directive> {
self.directives.values()
}
/// Returns all directives applicable for a given location(Query, Field, etc).
pub fn directives_for_location(&self, location: DirectiveLocation) -> Vec<&Directive> {
self.directives
.values()
.filter(|directive| directive.locations.contains(&location))
.collect()
}
pub fn get_fields(&self) -> impl Iterator<Item = &Field> {
self.fields.iter()
}
pub fn get_interfaces(&self) -> impl Iterator<Item = &Interface> {
self.interfaces.iter()
}
pub fn get_enums(&self) -> impl Iterator<Item = &Enum> {
self.enums.iter()
}
pub fn get_objects(&self) -> impl Iterator<Item = &Object> {
self.objects.iter()
}
pub fn has_directive(&self, directive_name: StringKey) -> bool {
self.directives.contains_key(&directive_name)
}
pub fn has_type(&self, type_name: StringKey) -> bool {
self.type_map.contains_key(&type_name)
}
pub fn add_directive(&mut self, directive: Directive) -> DiagnosticsResult<()> {
if self.directives.contains_key(&directive.name) {
return todo_add_location(SchemaError::DuplicateDirectiveDefinition(directive.name));
}
self.directives.insert(directive.name, directive);
Ok(())
}
pub fn add_field(&mut self, field: Field) -> DiagnosticsResult<FieldID> {
Ok(self.build_field(field))
}
pub fn add_enum(&mut self, enum_: Enum) -> DiagnosticsResult<EnumID> {
if self.type_map.contains_key(&enum_.name) {
return todo_add_location(SchemaError::DuplicateType(enum_.name));
}
let index: u32 = self.enums.len().try_into().unwrap();
let name = enum_.name;
self.enums.push(enum_);
self.type_map.insert(name, Type::Enum(EnumID(index)));
Ok(EnumID(index))
}
pub fn add_input_object(
&mut self,
input_object: InputObject,
) -> DiagnosticsResult<InputObjectID> {
if self.type_map.contains_key(&input_object.name) {
return todo_add_location(SchemaError::DuplicateType(input_object.name));
}
let index: u32 = self.input_objects.len().try_into().unwrap();
let name = input_object.name;
self.input_objects.push(input_object);
self.type_map
.insert(name, Type::InputObject(InputObjectID(index)));
Ok(InputObjectID(index))
}
pub fn add_interface(&mut self, interface: Interface) -> DiagnosticsResult<InterfaceID> {
if self.type_map.contains_key(&interface.name) {
return todo_add_location(SchemaError::DuplicateType(interface.name));
}
let index: u32 = self.interfaces.len().try_into().unwrap();
let name = interface.name;
self.interfaces.push(interface);
self.type_map
.insert(name, Type::Interface(InterfaceID(index)));
Ok(InterfaceID(index))
}
pub fn add_object(&mut self, object: Object) -> DiagnosticsResult<ObjectID> {
if self.type_map.contains_key(&object.name.item) {
return Err(vec![Diagnostic::error(
SchemaError::DuplicateType(object.name.item),
object.name.location,
)]);
}
let index: u32 = self.objects.len().try_into().unwrap();
let name = object.name;
self.objects.push(object);
self.type_map
.insert(name.item, Type::Object(ObjectID(index)));
Ok(ObjectID(index))
}
pub fn add_scalar(&mut self, scalar: Scalar) -> DiagnosticsResult<ScalarID> {
if self.type_map.contains_key(&scalar.name) {
return todo_add_location(SchemaError::DuplicateType(scalar.name));
}
let index: u32 = self.scalars.len().try_into().unwrap();
let name = scalar.name;
self.scalars.push(scalar);
self.type_map.insert(name, Type::Scalar(ScalarID(index)));
Ok(ScalarID(index))
}
pub fn add_union(&mut self, union: Union) -> DiagnosticsResult<UnionID> {
if self.type_map.contains_key(&union.name) {
return todo_add_location(SchemaError::DuplicateType(union.name));
}
let index: u32 = self.unions.len().try_into().unwrap();
let name = union.name;
self.unions.push(union);
self.type_map.insert(name, Type::Union(UnionID(index)));
Ok(UnionID(index))
}
pub fn add_field_to_interface(
&mut self,
interface_id: InterfaceID,
field_id: FieldID,
) -> DiagnosticsResult<InterfaceID> {
let interface = self.interfaces.get_mut(interface_id.as_usize()).unwrap();
interface.fields.push(field_id);
Ok(interface_id)
}
pub fn add_field_to_object(
&mut self,
obj_id: ObjectID,
field_id: FieldID,
) -> DiagnosticsResult<ObjectID> {
let object = self.objects.get_mut(obj_id.as_usize()).unwrap();
object.fields.push(field_id);
Ok(obj_id)
}
pub fn add_interface_to_object(
&mut self,
obj_id: ObjectID,
interface_id: InterfaceID,
) -> DiagnosticsResult<ObjectID> {
let object = self.objects.get_mut(obj_id.as_usize()).unwrap();
object.interfaces.push(interface_id);
Ok(obj_id)
}
pub fn add_parent_interface_to_interface(
&mut self,
interface_id: InterfaceID,
parent_interface_id: InterfaceID,
) -> DiagnosticsResult<InterfaceID> {
let interface = self.interfaces.get_mut(interface_id.as_usize()).unwrap();
interface.interfaces.push(parent_interface_id);
Ok(interface_id)
}
pub fn add_implementing_object_to_interface(
&mut self,
interface_id: InterfaceID,
object_id: ObjectID,
) -> DiagnosticsResult<InterfaceID> {
let interface = self.interfaces.get_mut(interface_id.as_usize()).unwrap();
interface.implementing_objects.push(object_id);
Ok(interface_id)
}
pub fn add_member_to_union(
&mut self,
union_id: UnionID,
object_id: ObjectID,
) -> DiagnosticsResult<UnionID> {
let union = self.unions.get_mut(union_id.as_usize()).unwrap();
union.members.push(object_id);
Ok(union_id)
}
/// Sets argument definitions for a given input object.
/// Any existing argument definitions will be erased.
pub fn set_input_object_args(
&mut self,
input_object_id: InputObjectID,
fields: ArgumentDefinitions,
) -> DiagnosticsResult<InputObjectID> {
let input_object = self
.input_objects
.get_mut(input_object_id.as_usize())
.unwrap();
input_object.fields = fields;
Ok(input_object_id)
}
/// Sets argument definitions for a given field.
/// Any existing argument definitions on the field will be erased.
pub fn set_field_args(
&mut self,
field_id: FieldID,
args: ArgumentDefinitions,
) -> DiagnosticsResult<FieldID> {
let field = self.fields.get_mut(field_id.as_usize()).unwrap();
field.arguments = args;
Ok(field_id)
}
/// Replaces the definition of interface type, but keeps the same id.
/// Existing references to the old type now reference the replacement.
pub fn replace_interface(
&mut self,
id: InterfaceID,
interface: Interface,
) -> DiagnosticsResult<()> {
if id.as_usize() >= self.interfaces.len() {
return todo_add_location(SchemaError::UnknownTypeID(
id.as_usize(),
String::from("Interface"),
));
}
self.type_map
.remove(&self.get_type_name(Type::Interface(id)));
self.type_map.insert(interface.name, Type::Interface(id));
self.interfaces[id.as_usize()] = interface;
Ok(())
}
/// Replaces the definition of object type, but keeps the same id.
/// Existing references to the old type now reference the replacement.
pub fn replace_object(&mut self, id: ObjectID, object: Object) -> DiagnosticsResult<()> {
if id.as_usize() >= self.objects.len() {
return todo_add_location(SchemaError::UnknownTypeID(
id.as_usize(),
String::from("Object"),
));
}
self.type_map.remove(&self.get_type_name(Type::Object(id)));
self.type_map.insert(object.name.item, Type::Object(id));
self.objects[id.as_usize()] = object;
Ok(())
}
/// Replaces the definition of enum type, but keeps the same id.
/// Existing references to the old type now reference the replacement.
pub fn replace_enum(&mut self, id: EnumID, enum_: Enum) -> DiagnosticsResult<()> {
if id.as_usize() >= self.enums.len() {
return todo_add_location(SchemaError::UnknownTypeID(
id.as_usize(),
String::from("Enum"),
));
}
self.type_map.remove(&self.get_type_name(Type::Enum(id)));
self.type_map.insert(enum_.name, Type::Enum(id));
self.enums[id.as_usize()] = enum_;
Ok(())
}
/// Replaces the definition of input object type, but keeps the same id.
/// Existing references to the old type now reference the replacement.
pub fn replace_input_object(
&mut self,
id: InputObjectID,
input_object: InputObject,
) -> DiagnosticsResult<()> {
if id.as_usize() >= self.enums.len() {
return todo_add_location(SchemaError::UnknownTypeID(
id.as_usize(),
String::from("Input Object"),
));
}
self.type_map
.remove(&self.get_type_name(Type::InputObject(id)));
self.type_map
.insert(input_object.name, Type::InputObject(id));
self.input_objects[id.as_usize()] = input_object;
Ok(())
}
/// Replaces the definition of union type, but keeps the same id.
/// Existing references to the old type now reference the replacement.
pub fn replace_union(&mut self, id: UnionID, union: Union) -> DiagnosticsResult<()> {
if id.as_usize() >= self.enums.len() {
return todo_add_location(SchemaError::UnknownTypeID(
id.as_usize(),
String::from("Union"),
));
}
self.type_map.remove(&self.get_type_name(Type::Union(id)));
self.type_map.insert(union.name, Type::Union(id));
self.unions[id.as_usize()] = union;
Ok(())
}
/// Replaces the definition of field, but keeps the same id.
/// Existing references to the old field now reference the replacement.
pub fn replace_field(&mut self, id: FieldID, field: Field) -> DiagnosticsResult<()> {
let id = id.as_usize();
if id >= self.fields.len() {
return Err(vec![Diagnostic::error(
SchemaError::UnknownTypeID(id, String::from("Field")),
field.name.location,
)]);
}
self.fields[id] = field;
Ok(())
}
/// Creates an uninitialized, invalid schema which can then be added to using the add_*
/// methods. Note that we still bake in some assumptions about the clientid and typename
/// fields, but in practice this is not an issue.
pub fn create_uninitialized() -> InMemorySchema {
InMemorySchema {
query_type: None,
mutation_type: None,
subscription_type: None,
type_map: HashMap::new(),
clientid_field: FieldID(0),
strongid_field: FieldID(0),
typename_field: FieldID(0),
fetch_token_field: FieldID(0),
is_fulfilled_field: FieldID(0),
clientid_field_name: "__id".intern(),
strongid_field_name: "strong_id__".intern(),
typename_field_name: "__typename".intern(),
fetch_token_field_name: "__token".intern(),
is_fulfilled_field_name: "is_fulfilled__".intern(),
string_type: None,
id_type: None,
unchecked_argument_type_sentinel: None,
directives: HashMap::new(),
enums: Vec::new(),
fields: Vec::new(),
input_objects: Vec::new(),
interfaces: Vec::new(),
objects: Vec::new(),
scalars: Vec::new(),
unions: Vec::new(),
}
}
pub fn build(
schema_documents: &[SchemaDocument],
client_schema_documents: &[SchemaDocument],
) -> DiagnosticsResult<Self> {
let schema_definitions: Vec<&TypeSystemDefinition> = schema_documents
.iter()
.flat_map(|document| &document.definitions)
.collect();
let client_definitions: Vec<&TypeSystemDefinition> = client_schema_documents
.iter()
.flat_map(|document| &document.definitions)
.collect();
// Step 1: build the type_map from type names to type keys
let mut type_map =
HashMap::with_capacity(schema_definitions.len() + client_definitions.len());
let mut next_object_id = 0;
let mut next_interface_id = 0;
let mut next_union_id = 0;
let mut next_input_object_id = 0;
let mut next_enum_id = 0;
let mut next_scalar_id = 0;
let mut field_count = 0;
let mut directive_count = 0;
for definition in schema_definitions.iter().chain(&client_definitions) {
match definition {
TypeSystemDefinition::SchemaDefinition { .. } => {}
TypeSystemDefinition::DirectiveDefinition { .. } => {
directive_count += 1;
}
TypeSystemDefinition::ObjectTypeDefinition(ObjectTypeDefinition {
name,
fields,
..
}) => {
type_map.insert(name.value, Type::Object(ObjectID(next_object_id)));
field_count += len_of_option_list(fields);
next_object_id += 1;
}
TypeSystemDefinition::InterfaceTypeDefinition(InterfaceTypeDefinition {
name,
fields,
..
}) => {
type_map.insert(name.value, Type::Interface(InterfaceID(next_interface_id)));
field_count += len_of_option_list(fields);
next_interface_id += 1;
}
TypeSystemDefinition::UnionTypeDefinition(UnionTypeDefinition { name, .. }) => {
type_map.insert(name.value, Type::Union(UnionID(next_union_id)));
next_union_id += 1;
}
TypeSystemDefinition::InputObjectTypeDefinition(InputObjectTypeDefinition {
name,
..
}) => {
type_map.insert(
name.value,
Type::InputObject(InputObjectID(next_input_object_id)),
);
next_input_object_id += 1;
}
TypeSystemDefinition::EnumTypeDefinition(EnumTypeDefinition { name, .. }) => {
type_map.insert(name.value, Type::Enum(EnumID(next_enum_id)));
next_enum_id += 1;
}
TypeSystemDefinition::ScalarTypeDefinition(ScalarTypeDefinition {
name, ..
}) => {
type_map.insert(name.value, Type::Scalar(ScalarID(next_scalar_id)));
next_scalar_id += 1;
}
TypeSystemDefinition::ObjectTypeExtension { .. } => {}
TypeSystemDefinition::InterfaceTypeExtension { .. } => {}
TypeSystemDefinition::SchemaExtension { .. } => todo!("SchemaExtension"),
TypeSystemDefinition::EnumTypeExtension { .. } => todo!("EnumTypeExtension"),
TypeSystemDefinition::UnionTypeExtension { .. } => todo!("UnionTypeExtension"),
TypeSystemDefinition::InputObjectTypeExtension { .. } => {
todo!("InputObjectTypeExtension")
}
TypeSystemDefinition::ScalarTypeExtension { .. } => todo!("ScalarTypeExtension"),
}
}
// Step 2: define operation types, directives, and types
let string_type = *type_map
.get(&"String".intern())
.expect("Missing String type");
let id_type = *type_map.get(&"ID".intern()).expect("Missing ID type");
let unchecked_argument_type_sentinel = Some(TypeReference::Named(
*type_map
.get(&"Boolean".intern())
.expect("Missing Boolean type"),
));
let mut schema = InMemorySchema {
query_type: None,
mutation_type: None,
subscription_type: None,
type_map,
clientid_field: FieldID(0), // dummy value, overwritten later
strongid_field: FieldID(0), // dummy value, overwritten later
typename_field: FieldID(0), // dummy value, overwritten later
fetch_token_field: FieldID(0), // dummy value, overwritten later
is_fulfilled_field: FieldID(0), // dummy value, overwritten later
clientid_field_name: "__id".intern(),
strongid_field_name: "strong_id__".intern(),
typename_field_name: "__typename".intern(),
fetch_token_field_name: "__token".intern(),
is_fulfilled_field_name: "is_fulfilled__".intern(),
string_type: Some(string_type),
id_type: Some(id_type),
unchecked_argument_type_sentinel,
directives: HashMap::with_capacity(directive_count),
enums: Vec::with_capacity(next_enum_id.try_into().unwrap()),
fields: Vec::with_capacity(field_count),
input_objects: Vec::with_capacity(next_input_object_id.try_into().unwrap()),
interfaces: Vec::with_capacity(next_interface_id.try_into().unwrap()),
objects: Vec::with_capacity(next_object_id.try_into().unwrap()),
scalars: Vec::with_capacity(next_scalar_id.try_into().unwrap()),
unions: Vec::with_capacity(next_union_id.try_into().unwrap()),
};
for document in schema_documents {
for definition in &document.definitions {
schema.add_definition(definition, &document.location.source_location(), false)?;
}
}
for document in client_schema_documents {
for definition in &document.definitions {
schema.add_definition(definition, &document.location.source_location(), true)?;
}
}
for document in schema_documents.iter().chain(client_schema_documents) {
for definition in &document.definitions {
if let TypeSystemDefinition::ObjectTypeDefinition(ObjectTypeDefinition {
name,
interfaces,
..
}) = definition
{
let object_id = match schema.type_map.get(&name.value) {
Some(Type::Object(id)) => id,
_ => unreachable!("Must be an Object type"),
};
for interface in interfaces {
let type_ = schema.type_map.get(&interface.value).unwrap();
match type_ {
Type::Interface(id) => {
let interface = schema.interfaces.get_mut(id.as_usize()).unwrap();
interface.implementing_objects.push(*object_id)
}
_ => unreachable!("Must be an interface"),
}
}
}
}
}
schema.load_defaults();
Ok(schema)
}
fn load_defaults(&mut self) {
self.load_default_root_types();
self.load_default_typename_field();
self.load_default_fetch_token_field();
self.load_default_clientid_field();
self.load_default_strongid_field();
self.load_default_is_fulfilled_field();
}
// In case the schema doesn't define a query, mutation or subscription
// type, but there is a Query, Mutation, or Subscription object type
// defined, default to those.
// This is not standard GraphQL behavior, and we might want to remove
// this at some point.
fn load_default_root_types(&mut self) {
if self.query_type.is_none() {
if let Some(Type::Object(id)) = self.type_map.get(&"Query".intern()) {
self.query_type = Some(*id);
}
}
if self.mutation_type.is_none() {
if let Some(Type::Object(id)) = self.type_map.get(&"Mutation".intern()) {
self.mutation_type = Some(*id);
}
}
if self.subscription_type.is_none() {
if let Some(Type::Object(id)) = self.type_map.get(&"Subscription".intern()) {
self.subscription_type = Some(*id);
}
}
}
fn load_default_typename_field(&mut self) {
let string_type = *self
.type_map
.get(&"String".intern())
.expect("Missing String type");
let typename_field_id = self.fields.len();
self.typename_field = FieldID(typename_field_id.try_into().unwrap());
self.fields.push(Field {
name: WithLocation::generated(self.typename_field_name),
is_extension: false,
arguments: ArgumentDefinitions::new(Default::default()),
type_: TypeReference::NonNull(Box::new(TypeReference::Named(string_type))),
directives: Vec::new(),
parent_type: None,
description: None,
});
}
fn load_default_fetch_token_field(&mut self) {
let id_type = *self.type_map.get(&"ID".intern()).expect("Missing ID type");
let fetch_token_field_id = self.fields.len();
self.fetch_token_field = FieldID(fetch_token_field_id.try_into().unwrap());
self.fields.push(Field {
name: WithLocation::generated(self.fetch_token_field_name),
is_extension: false,
arguments: ArgumentDefinitions::new(Default::default()),
type_: TypeReference::NonNull(Box::new(TypeReference::Named(id_type))),
directives: Vec::new(),
parent_type: None,
description: None,
});
}
fn load_default_clientid_field(&mut self) {
let id_type = *self.type_map.get(&"ID".intern()).expect("Missing ID type");
let clientid_field_id = self.fields.len();
self.clientid_field = FieldID(clientid_field_id.try_into().unwrap());
self.fields.push(Field {
name: WithLocation::generated(self.clientid_field_name),
is_extension: true,
arguments: ArgumentDefinitions::new(Default::default()),
type_: TypeReference::NonNull(Box::new(TypeReference::Named(id_type))),
directives: Vec::new(),
parent_type: None,
description: None,
});
}
fn load_default_strongid_field(&mut self) {
let id_type = *self.type_map.get(&"ID".intern()).expect("Missing ID type");
let strongid_field_id = self.fields.len();
self.strongid_field = FieldID(strongid_field_id.try_into().unwrap());
self.fields.push(Field {
name: WithLocation::generated(self.strongid_field_name),
is_extension: true,
arguments: ArgumentDefinitions::new(Default::default()),
type_: TypeReference::Named(id_type),
directives: Vec::new(),
parent_type: None,
description: None,
});
}
fn load_default_is_fulfilled_field(&mut self) {
let string_type = *self
.type_map
.get(&"String".intern())
.expect("Missing String type");
let is_fulfilled_field_id = self.fields.len();
self.is_fulfilled_field = FieldID(is_fulfilled_field_id.try_into().unwrap());
self.fields.push(Field {
name: WithLocation::generated(self.is_fulfilled_field_name),
is_extension: true,
arguments: ArgumentDefinitions::new(vec![Argument {
name: "name".intern(),
type_: TypeReference::NonNull(Box::new(TypeReference::Named(string_type))),
default_value: None,
description: None,
}]),
type_: TypeReference::NonNull(Box::new(TypeReference::Named(string_type))),
directives: Vec::new(),
parent_type: None,
description: None,
});
}
/// Add additional object extensions to the schema after its initial
/// creation.
pub fn add_object_type_extension(
&mut self,
object_extension: ObjectTypeExtension,
location_key: SourceLocationKey,
is_extension: bool,
) -> DiagnosticsResult<()> {
self.add_definition(
&TypeSystemDefinition::ObjectTypeExtension(object_extension),
&location_key,
is_extension,
)
}
/// Add additional interface extensions to the schema after its initial
/// creation.
pub fn add_interface_type_extension(
&mut self,
interface_extension: InterfaceTypeExtension,
location_key: SourceLocationKey,
is_extension: bool,
) -> DiagnosticsResult<()> {
self.add_definition(
&TypeSystemDefinition::InterfaceTypeExtension(interface_extension),
&location_key,
is_extension,
)
}
fn add_definition(
&mut self,
definition: &TypeSystemDefinition,
location_key: &SourceLocationKey,
is_extension: bool,
) -> DiagnosticsResult<()> {
match definition {
TypeSystemDefinition::SchemaDefinition(SchemaDefinition {
operation_types,
directives: _directives,
}) => {
for OperationTypeDefinition { operation, type_ } in &operation_types.items {
let operation_id = self.build_object_id(type_.value)?;
match operation {
OperationType::Query => {
if let Some(prev_query_type) = self.query_type {
return Err(vec![Diagnostic::error(
SchemaError::DuplicateOperationDefinition(
*operation,
type_.value,
expect_object_type_name(&self.type_map, prev_query_type),
),
Location::new(*location_key, type_.span),
)]);
} else {
self.query_type = Some(operation_id);
}
}
OperationType::Mutation => {
if let Some(prev_mutation_type) = self.mutation_type {
return Err(vec![Diagnostic::error(
SchemaError::DuplicateOperationDefinition(
*operation,
type_.value,
expect_object_type_name(&self.type_map, prev_mutation_type),
),
Location::new(*location_key, type_.span),
)]);
} else {
self.mutation_type = Some(operation_id);
}
}
OperationType::Subscription => {
if let Some(prev_subscription_type) = self.subscription_type {
return Err(vec![Diagnostic::error(
SchemaError::DuplicateOperationDefinition(
*operation,
type_.value,
expect_object_type_name(
&self.type_map,
prev_subscription_type,
),
),
Location::new(*location_key, type_.span),
)]);
} else {
self.subscription_type = Some(operation_id);
}
}
}
}
}
TypeSystemDefinition::DirectiveDefinition(DirectiveDefinition {
name,
arguments,
repeatable,
locations,
description,
}) => {
if self.directives.contains_key(&name.value) {
let str_name = name.value.lookup();
if str_name != "skip" && str_name != "include" {
// TODO(T63941319) @skip and @include directives are duplicated in our schema
return Err(vec![Diagnostic::error(
SchemaError::DuplicateDirectiveDefinition(name.value),
Location::new(*location_key, name.span),
)]);
}
}
let arguments = self.build_arguments(arguments)?;
self.directives.insert(
name.value,
Directive {
name: name.value,
arguments,
locations: locations.clone(),
repeatable: *repeatable,
is_extension,
description: description.as_ref().map(|node| node.value),
},
);
}
TypeSystemDefinition::ObjectTypeDefinition(ObjectTypeDefinition {
name,
interfaces,
fields,
directives,
}) => {
let parent_id = Type::Object(ObjectID(self.objects.len() as u32));
let fields = if is_extension {
self.build_extend_fields(
fields,
&mut HashMap::with_capacity(len_of_option_list(fields)),
*location_key,
Some(parent_id),
)?
} else {
self.build_fields(fields, *location_key, Some(parent_id))?
};
let interfaces = interfaces
.iter()
.map(|name| self.build_interface_id(name, location_key))
.collect::<DiagnosticsResult<Vec<_>>>()?;
let directives = self.build_directive_values(directives);
self.objects.push(Object {
name: WithLocation::new(Location::new(*location_key, name.span), name.value),
fields,
is_extension,
interfaces,
directives,
description: None,
});
}
TypeSystemDefinition::InterfaceTypeDefinition(InterfaceTypeDefinition {
name,
interfaces,
directives,
fields,
}) => {
let parent_id = Type::Interface(InterfaceID(self.interfaces.len() as u32));
let fields = if is_extension {
self.build_extend_fields(
fields,
&mut HashMap::with_capacity(len_of_option_list(fields)),
*location_key,
Some(parent_id),
)?
} else {
self.build_fields(fields, *location_key, Some(parent_id))?
};
let interfaces = interfaces
.iter()
.map(|name| self.build_interface_id(name, location_key))
.collect::<DiagnosticsResult<Vec<_>>>()?;
let directives = self.build_directive_values(directives);
self.interfaces.push(Interface {
name: name.value,
implementing_objects: vec![],
is_extension,
fields,
directives,
interfaces,
description: None,
});
}
TypeSystemDefinition::UnionTypeDefinition(UnionTypeDefinition {
name,
directives,
members,
}) => {
let members = members
.iter()
.map(|name| self.build_object_id(name.value))
.collect::<DiagnosticsResult<Vec<_>>>()?;
let directives = self.build_directive_values(directives);
self.unions.push(Union {
name: name.value,
is_extension,
members,
directives,
description: None,
});
}
TypeSystemDefinition::InputObjectTypeDefinition(InputObjectTypeDefinition {
name,
fields,
directives,
}) => {
let fields = self.build_arguments(fields)?;
let directives = self.build_directive_values(directives);
self.input_objects.push(InputObject {
name: name.value,
fields,
directives,
description: None,
});
}
TypeSystemDefinition::EnumTypeDefinition(EnumTypeDefinition {
name,
directives,
values,
}) => {
let directives = self.build_directive_values(directives);
let values = if let Some(values) = values {
values
.items
.iter()
.map(|enum_def| EnumValue {
value: enum_def.name.value,
directives: self.build_directive_values(&enum_def.directives),
})
.collect()
} else {
Vec::new()
};
self.enums.push(Enum {
name: name.value,
is_extension,
values,
directives,
description: None,
});
}
TypeSystemDefinition::ScalarTypeDefinition(ScalarTypeDefinition {
name,
directives,
}) => {
let directives = self.build_directive_values(directives);
self.scalars.push(Scalar {
name: name.value,
is_extension,
directives,
description: None,
})
}
TypeSystemDefinition::ObjectTypeExtension(ObjectTypeExtension {
name,
interfaces,
fields,
directives,
}) => match self.type_map.get(&name.value).cloned() {
Some(Type::Object(id)) => {
let index = id.as_usize();
let obj = self.objects.get(index).ok_or_else(|| {
vec![Diagnostic::error(
SchemaError::ExtendUndefinedType(name.value),
Location::new(*location_key, name.span),
)]
})?;
let field_ids = &obj.fields;
let mut existing_fields =
HashMap::with_capacity(field_ids.len() + len_of_option_list(fields));
for field_id in field_ids {
let field_name = self.fields[field_id.as_usize()].name;
existing_fields.insert(field_name.item, field_name.location);
}
let client_fields = self.build_extend_fields(
fields,
&mut existing_fields,
*location_key,
Some(Type::Object(id)),
)?;
self.objects[index].fields.extend(client_fields);
let built_interfaces = interfaces
.iter()
.map(|name| self.build_interface_id(name, location_key))
.collect::<DiagnosticsResult<Vec<_>>>()?;
extend_without_duplicates(
&mut self.objects[index].interfaces,
built_interfaces,
);
let built_directives = self.build_directive_values(directives);
extend_without_duplicates(
&mut self.objects[index].directives,
built_directives,
);
}
_ => {
return Err(vec![Diagnostic::error(
SchemaError::ExtendUndefinedType(name.value),
Location::new(*location_key, name.span),
)]);
}
},
TypeSystemDefinition::InterfaceTypeExtension(InterfaceTypeExtension {
name,
fields,
directives,
..
}) => match self.type_map.get(&name.value).cloned() {
Some(Type::Interface(id)) => {
let index = id.as_usize();
let interface = self.interfaces.get(index).ok_or_else(|| {
vec![Diagnostic::error(
SchemaError::ExtendUndefinedType(name.value),
Location::new(*location_key, name.span),
)]
})?;
let field_ids = &interface.fields;
let mut existing_fields =
HashMap::with_capacity(field_ids.len() + len_of_option_list(fields));
for field_id in field_ids {
let field_name = self.fields[field_id.as_usize()].name;
existing_fields.insert(field_name.item, field_name.location);
}
let client_fields = self.build_extend_fields(
fields,
&mut existing_fields,
*location_key,
Some(Type::Interface(id)),
)?;
self.interfaces[index].fields.extend(client_fields);
let built_directives = self.build_directive_values(directives);
extend_without_duplicates(
&mut self.interfaces[index].directives,
built_directives,
);
}
_ => {
return Err(vec![Diagnostic::error(
SchemaError::ExtendUndefinedType(name.value),
Location::new(*location_key, name.span),
)]);
}
},
TypeSystemDefinition::SchemaExtension { .. } => todo!("SchemaExtension"),
TypeSystemDefinition::EnumTypeExtension { .. } => todo!("EnumTypeExtension"),
TypeSystemDefinition::UnionTypeExtension { .. } => todo!("UnionTypeExtension"),
TypeSystemDefinition::InputObjectTypeExtension { .. } => {
todo!("InputObjectTypeExtension")
}
TypeSystemDefinition::ScalarTypeExtension { .. } => todo!("ScalarTypeExtension"),
}
Ok(())
}
fn build_object_id(&mut self, name: StringKey) -> DiagnosticsResult<ObjectID> {
match self.type_map.get(&name) {
Some(Type::Object(id)) => Ok(*id),
Some(non_object_type) => {
todo_add_location(SchemaError::ExpectedObjectReference(name, *non_object_type))
}
None => todo_add_location(SchemaError::UndefinedType(name)),
}
}
fn build_interface_id(
&mut self,
name: &Identifier,
location_key: &SourceLocationKey,
) -> DiagnosticsResult<InterfaceID> {
match self.type_map.get(&name.value) {
Some(Type::Interface(id)) => Ok(*id),
Some(non_interface_type) => Err(vec![Diagnostic::error(
SchemaError::ExpectedInterfaceReference(name.value, *non_interface_type),
Location::new(*location_key, name.span),
)]),
None => Err(vec![Diagnostic::error(
SchemaError::UndefinedType(name.value),
Location::new(*location_key, name.span),
)]),
}
}
fn build_field(&mut self, field: Field) -> FieldID {
let field_index = self.fields.len().try_into().unwrap();
self.fields.push(field);
FieldID(field_index)
}
fn build_fields(
&mut self,
field_defs: &Option<List<FieldDefinition>>,
field_location_key: SourceLocationKey,
parent_type: Option<Type>,
) -> DiagnosticsResult<Vec<FieldID>> {
if let Some(field_defs) = field_defs {
field_defs
.items
.iter()
.map(|field_def| {
let arguments = self.build_arguments(&field_def.arguments)?;
let type_ = self.build_type_reference(&field_def.type_)?;
let directives = self.build_directive_values(&field_def.directives);
let description = field_def.description.as_ref().map(|desc| desc.value);
Ok(self.build_field(Field {
name: WithLocation::new(
Location::new(field_location_key, field_def.name.span),
field_def.name.value,
),
is_extension: false,
arguments,
type_,
directives,
parent_type,
description,
}))
})
.collect()
} else {
Ok(Vec::new())
}
}
fn build_extend_fields(
&mut self,
field_defs: &Option<List<FieldDefinition>>,
existing_fields: &mut HashMap<StringKey, Location>,
source_location_key: SourceLocationKey,
parent_type: Option<Type>,
) -> DiagnosticsResult<Vec<FieldID>> {
if let Some(field_defs) = field_defs {
let mut field_ids: Vec<FieldID> = Vec::with_capacity(field_defs.items.len());
for field_def in &field_defs.items {
let field_name = field_def.name.value;
let field_location = Location::new(source_location_key, field_def.name.span);
if let Some(prev_location) = existing_fields.insert(field_name, field_location) {
return Err(vec![
Diagnostic::error(SchemaError::DuplicateField(field_name), field_location)
.annotate("previously defined here", prev_location),
]);
}
let arguments = self.build_arguments(&field_def.arguments)?;
let directives = self.build_directive_values(&field_def.directives);
let type_ = self.build_type_reference(&field_def.type_)?;
let description = field_def.description.as_ref().map(|desc| desc.value);
field_ids.push(self.build_field(Field {
name: WithLocation::new(field_location, field_name),
is_extension: true,
arguments,
type_,
directives,
parent_type,
description,
}));
}
Ok(field_ids)
} else {
Ok(Vec::new())
}
}
fn build_arguments(
&mut self,
arg_defs: &Option<List<InputValueDefinition>>,
) -> DiagnosticsResult<ArgumentDefinitions> {
if let Some(arg_defs) = arg_defs {
let arg_defs: DiagnosticsResult<Vec<Argument>> = arg_defs
.items
.iter()
.map(|arg_def| {
Ok(Argument {
name: arg_def.name.value,
type_: self.build_input_object_reference(&arg_def.type_)?,
default_value: arg_def.default_value.clone(),
description: None,
})
})
.collect();
Ok(ArgumentDefinitions(arg_defs?))
} else {
Ok(ArgumentDefinitions(Vec::new()))
}
}
fn build_input_object_reference(
&mut self,
ast_type: &TypeAnnotation,
) -> DiagnosticsResult<TypeReference> {
Ok(match ast_type {
TypeAnnotation::Named(named_type) => {
let type_ = self.type_map.get(&named_type.name.value).ok_or_else(|| {
vec![Diagnostic::error(
SchemaError::UndefinedType(named_type.name.value),
Location::new(SourceLocationKey::generated(), named_type.name.span),
)]
})?;
if !(type_.is_enum() || type_.is_scalar() || type_.is_input_object()) {
return Err(vec![Diagnostic::error(
SchemaError::ExpectedInputType(named_type.name.value),
Location::new(SourceLocationKey::generated(), named_type.name.span),
)]);
}
TypeReference::Named(*type_)
}
TypeAnnotation::NonNull(of_type) => {
TypeReference::NonNull(Box::new(self.build_input_object_reference(&of_type.type_)?))
}
TypeAnnotation::List(of_type) => {
TypeReference::List(Box::new(self.build_input_object_reference(&of_type.type_)?))
}
})
}
fn build_type_reference(
&mut self,
ast_type: &TypeAnnotation,
) -> DiagnosticsResult<TypeReference> {
Ok(match ast_type {
TypeAnnotation::Named(named_type) => TypeReference::Named(
*self.type_map.get(&named_type.name.value).ok_or_else(|| {
vec![Diagnostic::error(
SchemaError::UndefinedType(named_type.name.value),
Location::generated(),
)]
})?,
),
TypeAnnotation::NonNull(of_type) => {
TypeReference::NonNull(Box::new(self.build_type_reference(&of_type.type_)?))
}
TypeAnnotation::List(of_type) => {
TypeReference::List(Box::new(self.build_type_reference(&of_type.type_)?))
}
})
}
fn build_directive_values(&mut self, directives: &[ConstantDirective]) -> Vec<DirectiveValue> {
directives
.iter()
.map(|directive| {
let arguments = if let Some(arguments) = &directive.arguments {
arguments
.items
.iter()
.map(|argument| ArgumentValue {
name: argument.name.value,
value: argument.value.clone(),
})
.collect()
} else {
Vec::new()
};
DirectiveValue {
name: directive.name.value,
arguments,
}
})
.collect()
}
}
/// Extends the `target` with `extensions` ignoring items that are already in
/// `target`.
fn extend_without_duplicates<T: PartialEq>(
target: &mut Vec<T>,
extensions: impl IntoIterator<Item = T>,
) {
for extension in extensions {
if !target.contains(&extension) {
target.push(extension);
}
}
}
fn len_of_option_list<T>(option_list: &Option<List<T>>) -> usize {
option_list.as_ref().map_or(0, |list| list.items.len())
}
fn expect_object_type_name(type_map: &TypeMap, object_id: ObjectID) -> StringKey {
*type_map
.iter()
.find(|(_, type_)| match type_ {
Type::Object(id_) => id_ == &object_id,
_ => false,
})
.expect("Missing object in type_map")
.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extend_without_duplicates() {
let mut target = vec![10, 11];
extend_without_duplicates(&mut target, vec![1, 10, 100]);
assert_eq!(target, vec![10, 11, 1, 100]);
}
}