use super::*;
use indexmap::map::Entry;
pub struct SchemaBuilder {
schema: Schema,
orphan_schema_extensions: Vec<Node<ast::SchemaExtension>>,
orphan_type_extensions: IndexMap<Name, Vec<ast::Definition>>,
}
impl Default for SchemaBuilder {
fn default() -> Self {
Self::new()
}
}
impl SchemaBuilder {
pub fn new() -> Self {
let mut builder = SchemaBuilder {
schema: Schema {
sources: IndexMap::new(),
build_errors: Vec::new(),
schema_definition: None,
directive_definitions: IndexMap::new(),
types: IndexMap::new(),
},
orphan_schema_extensions: Vec::new(),
orphan_type_extensions: IndexMap::new(),
};
static BUILT_IN_TYPES: std::sync::OnceLock<ast::Document> = std::sync::OnceLock::new();
let built_in = BUILT_IN_TYPES.get_or_init(|| {
let input = include_str!("../built_in_types.graphql").to_owned();
let path = "built_in.graphql".into();
let id = FileId::BUILT_IN;
let document = ast::Document::parser().parse_with_file_id(input, path, id);
debug_assert!(document.check_parse_errors().is_ok());
document
});
let executable_definitions_are_errors = true;
builder.add_ast_document(built_in, executable_definitions_are_errors);
debug_assert!(
builder.schema.build_errors.is_empty()
&& builder.orphan_type_extensions.is_empty()
&& builder.orphan_schema_extensions.is_empty()
&& builder.schema.schema_definition.is_none(),
);
builder
}
pub fn parse(mut self, source_text: impl Into<String>, path: impl AsRef<Path>) -> Self {
Parser::new().parse_into_schema_builder(source_text, path, &mut self);
self
}
pub(crate) fn add_ast_document(
&mut self,
document: &ast::Document,
executable_definitions_are_errors: bool,
) {
if let Some((file_id, source_file)) = document.source.clone() {
self.schema.sources.insert(file_id, source_file);
}
for definition in &document.definitions {
match definition {
ast::Definition::SchemaDefinition(def) => match &self.schema.schema_definition {
None => {
self.schema.schema_definition = Some(SchemaDefinition::from_ast(
&mut self.schema.build_errors,
def,
&self.orphan_schema_extensions,
));
self.orphan_schema_extensions = Vec::new();
}
Some(previous) => {
self.schema
.build_errors
.push(BuildError::SchemaDefinitionCollision {
location: def.location(),
previous_location: previous.location(),
})
}
},
ast::Definition::DirectiveDefinition(def) => {
if let Err((_prev_name, previous)) =
insert_sticky(&mut self.schema.directive_definitions, &def.name, || {
def.clone()
})
{
self.schema
.build_errors
.push(BuildError::DirectiveDefinitionCollision {
location: def.name.location(),
previous_location: previous.name.location(),
name: def.name.clone(),
})
}
}
ast::Definition::ScalarTypeDefinition(def) => {
if let Err((prev_name, previous)) =
insert_sticky(&mut self.schema.types, &def.name, || {
ExtendedType::Scalar(ScalarType::from_ast(
def,
self.orphan_type_extensions
.remove(&def.name)
.unwrap_or_default(),
))
})
{
self.schema.build_errors.push(if previous.is_built_in() {
BuildError::BuiltInScalarTypeRedefinition {
location: def.location(),
}
} else {
BuildError::TypeDefinitionCollision {
location: def.name.location(),
previous_location: prev_name.location(),
name: def.name.clone(),
}
})
}
}
ast::Definition::ObjectTypeDefinition(def) => {
if let Err((prev_name, _previous)) =
insert_sticky(&mut self.schema.types, &def.name, || {
ExtendedType::Object(ObjectType::from_ast(
&mut self.schema.build_errors,
def,
self.orphan_type_extensions
.remove(&def.name)
.unwrap_or_default(),
))
})
{
self.schema
.build_errors
.push(BuildError::TypeDefinitionCollision {
location: def.name.location(),
previous_location: prev_name.location(),
name: def.name.clone(),
})
}
}
ast::Definition::InterfaceTypeDefinition(def) => {
if let Err((prev_name, _previous)) =
insert_sticky(&mut self.schema.types, &def.name, || {
ExtendedType::Interface(InterfaceType::from_ast(
&mut self.schema.build_errors,
def,
self.orphan_type_extensions
.remove(&def.name)
.unwrap_or_default(),
))
})
{
self.schema
.build_errors
.push(BuildError::TypeDefinitionCollision {
location: def.name.location(),
previous_location: prev_name.location(),
name: def.name.clone(),
})
}
}
ast::Definition::UnionTypeDefinition(def) => {
if let Err((prev_name, _)) =
insert_sticky(&mut self.schema.types, &def.name, || {
ExtendedType::Union(UnionType::from_ast(
&mut self.schema.build_errors,
def,
self.orphan_type_extensions
.remove(&def.name)
.unwrap_or_default(),
))
})
{
self.schema
.build_errors
.push(BuildError::TypeDefinitionCollision {
location: def.name.location(),
previous_location: prev_name.location(),
name: def.name.clone(),
})
}
}
ast::Definition::EnumTypeDefinition(def) => {
if let Err((prev_name, _previous)) =
insert_sticky(&mut self.schema.types, &def.name, || {
ExtendedType::Enum(EnumType::from_ast(
&mut self.schema.build_errors,
def,
self.orphan_type_extensions
.remove(&def.name)
.unwrap_or_default(),
))
})
{
self.schema
.build_errors
.push(BuildError::TypeDefinitionCollision {
location: def.name.location(),
previous_location: prev_name.location(),
name: def.name.clone(),
})
}
}
ast::Definition::InputObjectTypeDefinition(def) => {
if let Err((prev_name, _previous)) =
insert_sticky(&mut self.schema.types, &def.name, || {
ExtendedType::InputObject(InputObjectType::from_ast(
&mut self.schema.build_errors,
def,
self.orphan_type_extensions
.remove(&def.name)
.unwrap_or_default(),
))
})
{
self.schema
.build_errors
.push(BuildError::TypeDefinitionCollision {
location: def.name.location(),
previous_location: prev_name.location(),
name: def.name.clone(),
})
}
}
ast::Definition::SchemaExtension(ext) => {
if let Some(root) = &mut self.schema.schema_definition {
root.make_mut()
.extend_ast(&mut self.schema.build_errors, ext)
} else {
self.orphan_schema_extensions.push(ext.clone())
}
}
ast::Definition::ScalarTypeExtension(ext) => {
if let Some((_, ty_name, ty)) = self.schema.types.get_full_mut(&ext.name) {
if let ExtendedType::Scalar(ty) = ty {
ty.make_mut().extend_ast(ext)
} else {
self.schema
.build_errors
.push(BuildError::TypeExtensionKindMismatch {
location: ext.name.location(),
name: ext.name.clone(),
describe_ext: definition.describe(),
def_location: ty_name.location(),
describe_def: ty.describe(),
})
}
} else {
self.orphan_type_extensions
.entry(ext.name.clone())
.or_default()
.push(definition.clone())
}
}
ast::Definition::ObjectTypeExtension(ext) => {
if let Some((_, ty_name, ty)) = self.schema.types.get_full_mut(&ext.name) {
if let ExtendedType::Object(ty) = ty {
ty.make_mut().extend_ast(&mut self.schema.build_errors, ext)
} else {
self.schema
.build_errors
.push(BuildError::TypeExtensionKindMismatch {
location: ext.name.location(),
name: ext.name.clone(),
describe_ext: definition.describe(),
def_location: ty_name.location(),
describe_def: ty.describe(),
})
}
} else {
self.orphan_type_extensions
.entry(ext.name.clone())
.or_default()
.push(definition.clone())
}
}
ast::Definition::InterfaceTypeExtension(ext) => {
if let Some((_, ty_name, ty)) = self.schema.types.get_full_mut(&ext.name) {
if let ExtendedType::Interface(ty) = ty {
ty.make_mut().extend_ast(&mut self.schema.build_errors, ext)
} else {
self.schema
.build_errors
.push(BuildError::TypeExtensionKindMismatch {
location: ext.name.location(),
name: ext.name.clone(),
describe_ext: definition.describe(),
def_location: ty_name.location(),
describe_def: ty.describe(),
})
}
} else {
self.orphan_type_extensions
.entry(ext.name.clone())
.or_default()
.push(definition.clone())
}
}
ast::Definition::UnionTypeExtension(ext) => {
if let Some((_, ty_name, ty)) = self.schema.types.get_full_mut(&ext.name) {
if let ExtendedType::Union(ty) = ty {
ty.make_mut().extend_ast(&mut self.schema.build_errors, ext)
} else {
self.schema
.build_errors
.push(BuildError::TypeExtensionKindMismatch {
location: ext.name.location(),
name: ext.name.clone(),
describe_ext: definition.describe(),
def_location: ty_name.location(),
describe_def: ty.describe(),
})
}
} else {
self.orphan_type_extensions
.entry(ext.name.clone())
.or_default()
.push(definition.clone())
}
}
ast::Definition::EnumTypeExtension(ext) => {
if let Some((_, ty_name, ty)) = self.schema.types.get_full_mut(&ext.name) {
if let ExtendedType::Enum(ty) = ty {
ty.make_mut().extend_ast(&mut self.schema.build_errors, ext)
} else {
self.schema
.build_errors
.push(BuildError::TypeExtensionKindMismatch {
location: ext.name.location(),
name: ext.name.clone(),
describe_ext: definition.describe(),
def_location: ty_name.location(),
describe_def: ty.describe(),
})
}
} else {
self.orphan_type_extensions
.entry(ext.name.clone())
.or_default()
.push(definition.clone())
}
}
ast::Definition::InputObjectTypeExtension(ext) => {
if let Some((_, ty_name, ty)) = self.schema.types.get_full_mut(&ext.name) {
if let ExtendedType::InputObject(ty) = ty {
ty.make_mut().extend_ast(&mut self.schema.build_errors, ext)
} else {
self.schema
.build_errors
.push(BuildError::TypeExtensionKindMismatch {
location: ext.name.location(),
name: ext.name.clone(),
describe_ext: definition.describe(),
def_location: ty_name.location(),
describe_def: ty.describe(),
})
}
} else {
self.orphan_type_extensions
.entry(ext.name.clone())
.or_default()
.push(definition.clone())
}
}
ast::Definition::OperationDefinition(_)
| ast::Definition::FragmentDefinition(_) => {
if executable_definitions_are_errors {
self.schema
.build_errors
.push(BuildError::ExecutableDefinition {
location: definition.location(),
describe: definition.describe(),
})
}
}
}
}
}
pub fn build(self) -> Schema {
let SchemaBuilder {
mut schema,
orphan_schema_extensions,
orphan_type_extensions,
} = self;
schema
.build_errors
.extend(orphan_schema_extensions.into_iter().map(|ext| {
BuildError::OrphanSchemaExtension {
location: ext.location(),
}
}));
schema
.build_errors
.extend(orphan_type_extensions.into_values().flatten().map(|ext| {
let name = ext.name().unwrap().clone();
BuildError::OrphanTypeExtension {
location: name.location(),
name,
}
}));
schema
}
}
impl SchemaDefinition {
fn from_ast(
errors: &mut Vec<BuildError>,
definition: &Node<ast::SchemaDefinition>,
extensions: &[Node<ast::SchemaExtension>],
) -> Node<Self> {
let mut root = Self {
description: definition.description.clone(),
directives: definition
.directives
.iter()
.map(|d| d.to_component(ComponentOrigin::Definition))
.collect(),
query: None,
mutation: None,
subscription: None,
};
root.add_root_operations(
errors,
ComponentOrigin::Definition,
&definition.root_operations,
);
for ext in extensions {
root.extend_ast(errors, ext)
}
definition.same_location(root)
}
fn extend_ast(&mut self, errors: &mut Vec<BuildError>, extension: &Node<ast::SchemaExtension>) {
let origin = ComponentOrigin::Extension(ExtensionId::new(extension));
self.directives.extend(
extension
.directives
.iter()
.map(|d| d.to_component(origin.clone())),
);
self.add_root_operations(errors, origin, &extension.root_operations)
}
fn add_root_operations(
&mut self,
errors: &mut Vec<BuildError>,
origin: ComponentOrigin,
root_operations: &[Node<(ast::OperationType, Name)>],
) {
for op in root_operations {
let (operation_type, object_type_name) = &**op;
let entry = match operation_type {
ast::OperationType::Query => &mut self.query,
ast::OperationType::Mutation => &mut self.mutation,
ast::OperationType::Subscription => &mut self.subscription,
};
match entry {
None => *entry = Some(object_type_name.to_component(origin.clone())),
Some(previous) => errors.push(BuildError::DuplicateRootOperation {
location: op.location(),
previous_location: previous.location(),
operation_type: operation_type.name(),
}),
}
}
}
}
impl ScalarType {
fn from_ast(
definition: &Node<ast::ScalarTypeDefinition>,
extensions: Vec<ast::Definition>,
) -> Node<Self> {
let mut ty = Self {
description: definition.description.clone(),
directives: definition
.directives
.iter()
.map(|d| d.to_component(ComponentOrigin::Definition))
.collect(),
};
for def in &extensions {
if let ast::Definition::ScalarTypeExtension(ext) = def {
ty.extend_ast(ext)
}
}
definition.same_location(ty)
}
fn extend_ast(&mut self, extension: &Node<ast::ScalarTypeExtension>) {
let origin = ComponentOrigin::Extension(ExtensionId::new(extension));
self.directives.extend(
extension
.directives
.iter()
.map(|d| d.to_component(origin.clone())),
);
}
}
impl ObjectType {
fn from_ast(
errors: &mut Vec<BuildError>,
definition: &Node<ast::ObjectTypeDefinition>,
extensions: Vec<ast::Definition>,
) -> Node<Self> {
let mut ty = Self {
description: definition.description.clone(),
implements_interfaces: collect_sticky_set(
definition
.implements_interfaces
.iter()
.map(|name| name.to_component(ComponentOrigin::Definition)),
|prev, dup| {
errors.push(BuildError::DuplicateImplementsInterfaceInObject {
location: dup.location(),
name_at_previous_location: prev.node.clone(),
type_name: definition.name.clone(),
})
},
),
directives: definition
.directives
.iter()
.map(|d| d.to_component(ComponentOrigin::Definition))
.collect(),
fields: collect_sticky(
definition
.fields
.iter()
.map(|field| (&field.name, field.to_component(ComponentOrigin::Definition))),
|prev_key, dup_value| {
errors.push(BuildError::ObjectFieldNameCollision {
location: dup_value.location(),
name_at_previous_location: prev_key.clone(),
type_name: definition.name.clone(),
})
},
),
};
for def in &extensions {
if let ast::Definition::ObjectTypeExtension(ext) = def {
ty.extend_ast(errors, ext)
}
}
definition.same_location(ty)
}
fn extend_ast(
&mut self,
errors: &mut Vec<BuildError>,
extension: &Node<ast::ObjectTypeExtension>,
) {
let origin = ComponentOrigin::Extension(ExtensionId::new(extension));
self.directives.extend(
extension
.directives
.iter()
.map(|d| d.to_component(origin.clone())),
);
extend_sticky_set(
&mut self.implements_interfaces,
extension
.implements_interfaces
.iter()
.map(|name| name.to_component(origin.clone())),
|prev, dup| {
errors.push(BuildError::DuplicateImplementsInterfaceInObject {
location: dup.location(),
name_at_previous_location: prev.node.clone(),
type_name: extension.name.clone(),
})
},
);
extend_sticky(
&mut self.fields,
extension
.fields
.iter()
.map(|field| (&field.name, field.to_component(origin.clone()))),
|prev_key, dup_value| {
errors.push(BuildError::ObjectFieldNameCollision {
location: dup_value.location(),
name_at_previous_location: prev_key.clone(),
type_name: extension.name.clone(),
})
},
);
}
}
impl InterfaceType {
fn from_ast(
errors: &mut Vec<BuildError>,
definition: &Node<ast::InterfaceTypeDefinition>,
extensions: Vec<ast::Definition>,
) -> Node<Self> {
let mut ty = Self {
description: definition.description.clone(),
implements_interfaces: collect_sticky_set(
definition
.implements_interfaces
.iter()
.map(|name| name.to_component(ComponentOrigin::Definition)),
|prev, dup| {
errors.push(BuildError::DuplicateImplementsInterfaceInInterface {
location: dup.location(),
name_at_previous_location: prev.node.clone(),
type_name: definition.name.clone(),
})
},
),
directives: definition
.directives
.iter()
.map(|d| d.to_component(ComponentOrigin::Definition))
.collect(),
fields: collect_sticky(
definition
.fields
.iter()
.map(|field| (&field.name, field.to_component(ComponentOrigin::Definition))),
|prev_key, dup_value| {
errors.push(BuildError::InterfaceFieldNameCollision {
location: dup_value.location(),
name_at_previous_location: prev_key.clone(),
type_name: definition.name.clone(),
})
},
),
};
for def in &extensions {
if let ast::Definition::InterfaceTypeExtension(ext) = def {
ty.extend_ast(errors, ext)
}
}
definition.same_location(ty)
}
fn extend_ast(
&mut self,
errors: &mut Vec<BuildError>,
extension: &Node<ast::InterfaceTypeExtension>,
) {
let origin = ComponentOrigin::Extension(ExtensionId::new(extension));
self.directives.extend(
extension
.directives
.iter()
.map(|d| d.to_component(origin.clone())),
);
extend_sticky_set(
&mut self.implements_interfaces,
extension
.implements_interfaces
.iter()
.map(|name| name.to_component(origin.clone())),
|prev, dup| {
errors.push(BuildError::DuplicateImplementsInterfaceInInterface {
location: dup.location(),
name_at_previous_location: prev.node.clone(),
type_name: extension.name.clone(),
})
},
);
extend_sticky(
&mut self.fields,
extension
.fields
.iter()
.map(|field| (&field.name, field.to_component(origin.clone()))),
|prev_key, dup_value| {
errors.push(BuildError::InterfaceFieldNameCollision {
location: dup_value.location(),
name_at_previous_location: prev_key.clone(),
type_name: extension.name.clone(),
})
},
);
}
}
impl UnionType {
fn from_ast(
errors: &mut Vec<BuildError>,
definition: &Node<ast::UnionTypeDefinition>,
extensions: Vec<ast::Definition>,
) -> Node<Self> {
let mut ty = Self {
description: definition.description.clone(),
directives: definition
.directives
.iter()
.map(|d| d.to_component(ComponentOrigin::Definition))
.collect(),
members: collect_sticky_set(
definition
.members
.iter()
.map(|name| name.to_component(ComponentOrigin::Definition)),
|prev, dup| {
errors.push(BuildError::UnionMemberNameCollision {
location: dup.location(),
name_at_previous_location: prev.node.clone(),
type_name: definition.name.clone(),
})
},
),
};
for def in &extensions {
if let ast::Definition::UnionTypeExtension(ext) = def {
ty.extend_ast(errors, ext)
}
}
definition.same_location(ty)
}
fn extend_ast(
&mut self,
errors: &mut Vec<BuildError>,
extension: &Node<ast::UnionTypeExtension>,
) {
let origin = ComponentOrigin::Extension(ExtensionId::new(extension));
self.directives.extend(
extension
.directives
.iter()
.map(|d| d.to_component(origin.clone())),
);
extend_sticky_set(
&mut self.members,
extension
.members
.iter()
.map(|name| name.to_component(origin.clone())),
|prev, dup| {
errors.push(BuildError::UnionMemberNameCollision {
location: dup.location(),
name_at_previous_location: prev.node.clone(),
type_name: extension.name.clone(),
})
},
);
}
}
impl EnumType {
fn from_ast(
errors: &mut Vec<BuildError>,
definition: &Node<ast::EnumTypeDefinition>,
extensions: Vec<ast::Definition>,
) -> Node<Self> {
let mut ty = Self {
description: definition.description.clone(),
directives: definition
.directives
.iter()
.map(|d| d.to_component(ComponentOrigin::Definition))
.collect(),
values: collect_sticky(
definition.values.iter().map(|value_def| {
(
&value_def.value,
value_def.to_component(ComponentOrigin::Definition),
)
}),
|prev_key, dup_value| {
errors.push(BuildError::EnumValueNameCollision {
location: dup_value.location(),
name_at_previous_location: prev_key.clone(),
type_name: definition.name.clone(),
})
},
),
};
for def in &extensions {
if let ast::Definition::EnumTypeExtension(ext) = def {
ty.extend_ast(errors, ext)
}
}
definition.same_location(ty)
}
fn extend_ast(
&mut self,
errors: &mut Vec<BuildError>,
extension: &Node<ast::EnumTypeExtension>,
) {
let origin = ComponentOrigin::Extension(ExtensionId::new(extension));
self.directives.extend(
extension
.directives
.iter()
.map(|d| d.to_component(origin.clone())),
);
extend_sticky(
&mut self.values,
extension
.values
.iter()
.map(|value_def| (&value_def.value, value_def.to_component(origin.clone()))),
|prev_key, dup_value| {
errors.push(BuildError::EnumValueNameCollision {
location: dup_value.location(),
name_at_previous_location: prev_key.clone(),
type_name: extension.name.clone(),
})
},
)
}
}
impl InputObjectType {
fn from_ast(
errors: &mut Vec<BuildError>,
definition: &Node<ast::InputObjectTypeDefinition>,
extensions: Vec<ast::Definition>,
) -> Node<Self> {
let mut ty = Self {
description: definition.description.clone(),
directives: definition
.directives
.iter()
.map(|d| d.to_component(ComponentOrigin::Definition))
.collect(),
fields: collect_sticky(
definition
.fields
.iter()
.map(|field| (&field.name, field.to_component(ComponentOrigin::Definition))),
|prev_key, dup_value| {
errors.push(BuildError::InputFieldNameCollision {
location: dup_value.location(),
name_at_previous_location: prev_key.clone(),
type_name: definition.name.clone(),
})
},
),
};
for def in &extensions {
if let ast::Definition::InputObjectTypeExtension(ext) = def {
ty.extend_ast(errors, ext)
}
}
definition.same_location(ty)
}
fn extend_ast(
&mut self,
errors: &mut Vec<BuildError>,
extension: &Node<ast::InputObjectTypeExtension>,
) {
let origin = ComponentOrigin::Extension(ExtensionId::new(extension));
self.directives.extend(
extension
.directives
.iter()
.map(|d| d.to_component(origin.clone())),
);
extend_sticky(
&mut self.fields,
extension
.fields
.iter()
.map(|field| (&field.name, field.to_component(origin.clone()))),
|prev_key, dup_value| {
errors.push(BuildError::InputFieldNameCollision {
location: dup_value.location(),
name_at_previous_location: prev_key.clone(),
type_name: extension.name.clone(),
})
},
)
}
}
fn insert_sticky<'map, V>(
map: &'map mut IndexMap<Name, V>,
key: &Name,
make_value: impl FnOnce() -> V,
) -> Result<(), (&'map Name, &'map V)> {
match map.entry(key.clone()) {
Entry::Vacant(entry) => {
entry.insert(make_value());
Ok(())
}
Entry::Occupied(_) => Err(map.get_key_value(key).unwrap()),
}
}
fn extend_sticky<'a, V>(
map: &mut IndexMap<Name, V>,
iter: impl IntoIterator<Item = (&'a Name, V)>,
mut duplicate: impl FnMut(&Name, V),
) {
for (key, value) in iter.into_iter() {
match map.get_key_value(key) {
None => {
map.insert(key.clone(), value);
}
Some((prev_key, _)) => duplicate(prev_key, value),
}
}
}
fn collect_sticky<'a, V>(
iter: impl IntoIterator<Item = (&'a Name, V)>,
duplicate: impl FnMut(&Name, V),
) -> IndexMap<Name, V> {
let mut map = IndexMap::new();
extend_sticky(&mut map, iter, duplicate);
map
}
fn extend_sticky_set(
set: &mut IndexSet<ComponentStr>,
iter: impl IntoIterator<Item = ComponentStr>,
mut duplicate: impl FnMut(&ComponentStr, ComponentStr),
) {
for value in iter.into_iter() {
match set.get(&value) {
None => {
set.insert(value);
}
Some(previous) => duplicate(previous, value),
}
}
}
fn collect_sticky_set(
iter: impl IntoIterator<Item = ComponentStr>,
duplicate: impl FnMut(&ComponentStr, ComponentStr),
) -> IndexSet<ComponentStr> {
let mut set = IndexSet::new();
extend_sticky_set(&mut set, iter, duplicate);
set
}