use crate::ast::{ASTContext, DefaultIn, NamedType, OperationKind, Type};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use hashbrown::hash_map::DefaultHashBuilder;
use hashbrown::{HashMap, HashSet};
#[derive(Debug, Clone, PartialEq)]
pub struct Schema<'a> {
pub(crate) query_type: Option<&'a SchemaObject<'a>>,
pub(crate) mutation_type: Option<&'a SchemaObject<'a>>,
pub(crate) subscription_type: Option<&'a SchemaObject<'a>>,
pub(crate) types:
hashbrown::HashMap<&'a str, &'a SchemaType<'a>, DefaultHashBuilder, &'a bumpalo::Bump>,
}
impl<'a> DefaultIn<'a> for Schema<'a> {
fn default_in(arena: &'a Bump) -> Self {
Schema {
query_type: None,
mutation_type: None,
subscription_type: None,
types: HashMap::new_in(arena),
}
}
}
impl<'a> Schema<'a> {
pub fn is_empty(&self) -> bool {
self.types.is_empty()
&& self.query_type.is_none()
&& self.mutation_type.is_none()
&& self.subscription_type.is_none()
}
#[inline]
pub fn query_type(&self) -> Option<&'a SchemaObject<'a>> {
self.query_type
}
#[inline]
pub fn mutation_type(&self) -> Option<&'a SchemaObject<'a>> {
self.mutation_type
}
#[inline]
pub fn subscription_type(&self) -> Option<&'a SchemaObject<'a>> {
self.subscription_type
}
#[inline]
pub fn get_root_type(&self, operation_kind: OperationKind) -> Option<&'a SchemaObject<'a>> {
match operation_kind {
OperationKind::Query => self.query_type,
OperationKind::Mutation => self.mutation_type,
OperationKind::Subscription => self.subscription_type,
}
}
#[inline]
pub fn get_type(&self, name: &'a str) -> Option<&'a SchemaType<'a>> {
self.types.get(name).copied()
}
pub fn is_sub_type(&self, abstract_type: SchemaType<'a>, sub_type: SchemaType<'a>) -> bool {
match abstract_type {
SchemaType::Union(schema_union) => schema_union.is_sub_type(sub_type),
SchemaType::Interface(schema_interface) => schema_interface.is_sub_type(sub_type),
SchemaType::Object(schema_object) => {
if let SchemaType::Object(sub_object_type) = sub_type {
sub_object_type == schema_object
} else {
false
}
}
_ => false,
}
}
}
pub trait SchemaFields<'a>: Sized {
fn add_field(&mut self, ctx: &'a ASTContext, field: SchemaField<'a>);
fn get_fields(
&self,
) -> HashMap<&'a str, &'a SchemaField<'a>, DefaultHashBuilder, &'a bumpalo::Bump>;
fn get_field(&self, name: &'a str) -> Option<&'a SchemaField<'a>> {
self.get_fields().get(name).copied()
}
}
pub trait SchemaInterfaces<'a>: Sized {
fn add_interface(&mut self, ctx: &'a ASTContext, interface: &'a str);
fn get_interfaces(&self) -> Vec<'a, &'a str>;
#[inline]
fn implements_interface(&self, schema_interface: &SchemaInterface<'a>) -> bool {
self.get_interfaces()
.into_iter()
.any(|interface| interface == schema_interface.name)
}
}
pub trait SchemaPossibleTypes<'a>: Sized {
fn add_possible_type(&mut self, ctx: &'a ASTContext, object: &'a str);
fn get_possible_types(&self) -> Vec<'a, &'a str>;
#[inline]
fn get_possible_type(&self, name: &'a str) -> Option<&'a str> {
self.get_possible_types()
.into_iter()
.find(|&possible_type| possible_type == name)
}
#[inline]
fn is_possible_type(&self, schema_object: &SchemaObject<'a>) -> bool {
self.get_possible_types()
.into_iter()
.any(|possible_type| possible_type == schema_object.name)
}
}
pub trait SchemaSuperType<'a>: Sized {
fn is_sub_type(&self, subtype: SchemaType<'a>) -> bool;
}
#[derive(Debug, Clone, PartialEq)]
pub struct SchemaObject<'a> {
pub name: &'a str,
pub(crate) fields: HashMap<&'a str, &'a SchemaField<'a>, DefaultHashBuilder, &'a bumpalo::Bump>,
pub(crate) interfaces: Vec<'a, &'a str>,
}
impl<'a> SchemaObject<'a> {
#[inline]
pub fn new(ctx: &'a ASTContext, name: &'a str) -> Self {
SchemaObject {
name,
fields: HashMap::new_in(&ctx.arena),
interfaces: Vec::new_in(&ctx.arena),
}
}
}
impl<'a> SchemaFields<'a> for SchemaObject<'a> {
fn add_field(&mut self, ctx: &'a ASTContext, field: SchemaField<'a>) {
self.fields.insert(field.name, ctx.alloc(field));
}
fn get_fields(
&self,
) -> HashMap<&'a str, &'a SchemaField<'a>, DefaultHashBuilder, &'a bumpalo::Bump> {
self.fields.clone()
}
}
impl<'a> SchemaInterfaces<'a> for SchemaObject<'a> {
fn add_interface(&mut self, _ctx: &'a ASTContext, interface: &'a str) {
self.interfaces.push(interface);
}
#[inline]
fn get_interfaces(&self) -> Vec<'a, &'a str> {
self.interfaces.clone()
}
}
#[derive(Clone, Debug)]
pub struct SchemaInterface<'a> {
pub name: &'a str,
pub(crate) fields: HashMap<&'a str, &'a SchemaField<'a>, DefaultHashBuilder, &'a bumpalo::Bump>,
pub(crate) interfaces: Vec<'a, &'a str>,
pub(crate) possible_interfaces: Vec<'a, &'a str>,
pub(crate) possible_types: Vec<'a, &'a str>,
}
impl<'a> PartialEq for SchemaInterface<'a> {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& maps_are_equal(self.fields.clone(), other.fields.clone())
&& self.interfaces == other.interfaces
&& self.possible_interfaces == other.possible_interfaces
&& self.possible_types == other.possible_types
}
}
impl<'a> SchemaInterface<'a> {
#[inline]
pub fn new(ctx: &'a ASTContext, name: &'a str) -> Self {
SchemaInterface {
name,
fields: HashMap::new_in(&ctx.arena),
interfaces: Vec::new_in(&ctx.arena),
possible_interfaces: Vec::new_in(&ctx.arena),
possible_types: Vec::new_in(&ctx.arena),
}
}
pub fn add_possible_interface(&mut self, _ctx: &'a ASTContext, interface: &'a str) {
self.possible_interfaces.push(interface);
}
#[inline]
pub fn get_possible_interfaces(&self) -> Vec<'a, &'a str> {
self.possible_interfaces.clone()
}
}
impl<'a> SchemaFields<'a> for SchemaInterface<'a> {
fn add_field(&mut self, ctx: &'a ASTContext, field: SchemaField<'a>) {
self.fields.insert(field.name, ctx.alloc(field));
}
fn get_fields(
&self,
) -> HashMap<&'a str, &'a SchemaField<'a>, DefaultHashBuilder, &'a bumpalo::Bump> {
self.fields.clone()
}
}
impl<'a> SchemaInterfaces<'a> for SchemaInterface<'a> {
fn add_interface(&mut self, _ctx: &'a ASTContext, interface: &'a str) {
self.interfaces.push(interface);
}
#[inline]
fn get_interfaces(&self) -> Vec<'a, &'a str> {
self.interfaces.clone()
}
}
impl<'a> SchemaPossibleTypes<'a> for SchemaInterface<'a> {
fn add_possible_type(&mut self, _ctx: &'a ASTContext, object: &'a str) {
self.possible_types.push(object);
}
#[inline]
fn get_possible_types(&self) -> Vec<'a, &'a str> {
self.possible_types.clone()
}
}
impl<'a> SchemaSuperType<'a> for SchemaInterface<'a> {
#[inline]
fn is_sub_type(&self, sub_type: SchemaType<'a>) -> bool {
match sub_type {
SchemaType::Object(schema_object) => schema_object.implements_interface(self),
SchemaType::Interface(schema_interface) => schema_interface.implements_interface(self),
_ => false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SchemaField<'a> {
pub name: &'a str,
pub arguments: HashMap<&'a str, SchemaInputField<'a>, DefaultHashBuilder, &'a bumpalo::Bump>,
pub output_type: &'a TypeRef<'a>,
}
impl<'a> SchemaField<'a> {
#[inline]
pub fn new(ctx: &'a ASTContext, name: &'a str, output_type: &'a TypeRef<'a>) -> Self {
SchemaField {
name,
arguments: HashMap::new_in(&ctx.arena),
output_type,
}
}
pub fn add_argument(&mut self, _ctx: &'a ASTContext, arg: SchemaInputField<'a>) {
self.arguments.insert(arg.name, arg);
}
#[inline]
pub fn get_argument(&self, name: &'a str) -> Option<&SchemaInputField<'a>> {
self.arguments.get(name)
}
}
#[derive(Debug, Clone)]
pub struct SchemaUnion<'a> {
pub name: &'a str,
possible_types: Vec<'a, &'a str>,
}
impl<'a> PartialEq for SchemaUnion<'a> {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.possible_types == other.possible_types
}
}
impl<'a> SchemaUnion<'a> {
#[inline]
pub fn new(ctx: &'a ASTContext, name: &'a str) -> Self {
SchemaUnion {
name,
possible_types: Vec::new_in(&ctx.arena),
}
}
#[inline]
pub fn is_sub_type(&self, sub_type: SchemaType<'a>) -> bool {
match sub_type {
SchemaType::Object(schema_object) => self
.possible_types
.iter()
.any(|possible| possible == &schema_object.name),
_ => false,
}
}
}
impl<'a> SchemaPossibleTypes<'a> for SchemaUnion<'a> {
fn add_possible_type(&mut self, _ctx: &'a ASTContext, object: &'a str) {
self.possible_types.push(object);
}
#[inline]
fn get_possible_types(&self) -> Vec<'a, &'a str> {
self.possible_types.clone()
}
}
impl<'a> SchemaSuperType<'a> for SchemaUnion<'a> {
#[inline]
fn is_sub_type(&self, sub_type: SchemaType<'a>) -> bool {
if let SchemaType::Object(schema_object) = sub_type {
self.is_possible_type(schema_object)
} else {
false
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SchemaScalar<'a> {
pub name: &'a str,
}
impl<'a> SchemaScalar<'a> {
#[inline]
pub fn new(name: &'a str) -> Self {
SchemaScalar { name }
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct SchemaEnum<'a> {
pub name: &'a str,
pub values: HashSet<&'a str, DefaultHashBuilder, &'a bumpalo::Bump>,
}
impl<'a> SchemaEnum<'a> {
#[inline]
pub fn new(ctx: &'a ASTContext, name: &'a str) -> Self {
SchemaEnum {
name,
values: HashSet::new_in(&ctx.arena),
}
}
pub fn add_value(&mut self, _ctx: &'a ASTContext, value: &'a str) {
self.values.insert(value);
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SchemaInputObject<'a> {
pub name: &'a str,
pub fields: HashMap<&'a str, SchemaInputField<'a>, DefaultHashBuilder, &'a bumpalo::Bump>,
}
impl<'a> SchemaInputObject<'a> {
#[inline]
pub fn new(ctx: &'a ASTContext, name: &'a str) -> Self {
SchemaInputObject {
name,
fields: HashMap::new_in(&ctx.arena),
}
}
pub fn add_field(&mut self, _ctx: &'a ASTContext, field: SchemaInputField<'a>) {
self.fields.insert(field.name, field);
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct SchemaInputField<'a> {
pub name: &'a str,
pub input_type: &'a TypeRef<'a>,
}
impl<'a> SchemaInputField<'a> {
#[inline]
pub fn new(name: &'a str, input_type: &'a TypeRef<'a>) -> Self {
SchemaInputField { name, input_type }
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum SchemaType<'a> {
InputObject(&'a SchemaInputObject<'a>),
Object(&'a SchemaObject<'a>),
Union(&'a SchemaUnion<'a>),
Interface(&'a SchemaInterface<'a>),
Scalar(&'a SchemaScalar<'a>),
Enum(&'a SchemaEnum<'a>),
}
impl<'a> SchemaType<'a> {
#[inline]
pub fn name(&self) -> &'a str {
match self {
SchemaType::InputObject(x) => x.name,
SchemaType::Object(x) => x.name,
SchemaType::Union(x) => x.name,
SchemaType::Interface(x) => x.name,
SchemaType::Scalar(x) => x.name,
SchemaType::Enum(x) => x.name,
}
}
pub fn object(&self) -> Option<&'a SchemaObject<'a>> {
match self {
SchemaType::Object(x) => Some(x),
_ => None,
}
}
pub fn input_object(&self) -> Option<&'a SchemaInputObject<'a>> {
match self {
SchemaType::InputObject(x) => Some(x),
_ => None,
}
}
pub fn interface(&self) -> Option<&'a SchemaInterface<'a>> {
match self {
SchemaType::Interface(x) => Some(x),
_ => None,
}
}
pub fn union_type(&self) -> Option<&'a SchemaUnion<'a>> {
match self {
SchemaType::Union(x) => Some(x),
_ => None,
}
}
pub fn input_type(&self) -> Option<InputType<'a>> {
match self {
SchemaType::Scalar(x) => Some(InputType::Scalar(x)),
SchemaType::Enum(x) => Some(InputType::Enum(x)),
SchemaType::InputObject(x) => Some(InputType::InputObject(x)),
_ => None,
}
}
pub fn output_type(&self) -> Option<OutputType<'a>> {
match self {
SchemaType::Object(x) => Some(OutputType::Object(x)),
SchemaType::Union(x) => Some(OutputType::Union(x)),
SchemaType::Interface(x) => Some(OutputType::Interface(x)),
SchemaType::Scalar(x) => Some(OutputType::Scalar(x)),
SchemaType::Enum(x) => Some(OutputType::Enum(x)),
_ => None,
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum OwnedSchemaType<'a> {
InputObject(SchemaInputObject<'a>),
Object(SchemaObject<'a>),
Union(SchemaUnion<'a>),
Interface(SchemaInterface<'a>),
Scalar(SchemaScalar<'a>),
Enum(SchemaEnum<'a>),
}
impl<'a> OwnedSchemaType<'a> {
#[inline]
pub fn name(&self) -> &'a str {
match self {
OwnedSchemaType::InputObject(x) => x.name,
OwnedSchemaType::Object(x) => x.name,
OwnedSchemaType::Union(x) => x.name,
OwnedSchemaType::Interface(x) => x.name,
OwnedSchemaType::Scalar(x) => x.name,
OwnedSchemaType::Enum(x) => x.name,
}
}
pub fn object(&'a self) -> Option<&'a SchemaObject<'a>> {
match self {
OwnedSchemaType::Object(x) => Some(x),
_ => None,
}
}
pub fn input_object(&'a self) -> Option<&'a SchemaInputObject<'a>> {
match self {
OwnedSchemaType::InputObject(x) => Some(x),
_ => None,
}
}
pub fn interface(&'a self) -> Option<&'a SchemaInterface<'a>> {
match self {
OwnedSchemaType::Interface(x) => Some(x),
_ => None,
}
}
pub fn union_type(&'a self) -> Option<&'a SchemaUnion<'a>> {
match self {
OwnedSchemaType::Union(x) => Some(x),
_ => None,
}
}
pub fn input_type(&'a self) -> Option<InputType<'a>> {
match self {
OwnedSchemaType::Scalar(x) => Some(InputType::Scalar(x)),
OwnedSchemaType::Enum(x) => Some(InputType::Enum(x)),
OwnedSchemaType::InputObject(x) => Some(InputType::InputObject(x)),
_ => None,
}
}
pub fn output_type(&'a self) -> Option<OutputType<'a>> {
match self {
OwnedSchemaType::Object(x) => Some(OutputType::Object(x)),
OwnedSchemaType::Union(x) => Some(OutputType::Union(x)),
OwnedSchemaType::Interface(x) => Some(OutputType::Interface(x)),
OwnedSchemaType::Scalar(x) => Some(OutputType::Scalar(x)),
OwnedSchemaType::Enum(x) => Some(OutputType::Enum(x)),
_ => None,
}
}
}
impl<'a> From<&'a SchemaObject<'a>> for SchemaType<'a> {
#[inline]
fn from(schema_object: &'a SchemaObject<'a>) -> Self {
SchemaType::Object(schema_object)
}
}
impl<'a> From<&'a SchemaUnion<'a>> for SchemaType<'a> {
#[inline]
fn from(schema_union: &'a SchemaUnion<'a>) -> Self {
SchemaType::Union(schema_union)
}
}
impl<'a> From<&'a SchemaInterface<'a>> for SchemaType<'a> {
#[inline]
fn from(schema_interface: &'a SchemaInterface<'a>) -> Self {
SchemaType::Interface(schema_interface)
}
}
impl<'a> From<OutputType<'a>> for SchemaType<'a> {
#[inline]
fn from(type_ref: OutputType<'a>) -> Self {
match type_ref {
OutputType::Object(x) => SchemaType::Object(x),
OutputType::Union(x) => SchemaType::Union(x),
OutputType::Interface(x) => SchemaType::Interface(x),
OutputType::Scalar(x) => SchemaType::Scalar(x),
OutputType::Enum(x) => SchemaType::Enum(x),
}
}
}
impl<'a> From<InputType<'a>> for SchemaType<'a> {
#[inline]
fn from(type_ref: InputType<'a>) -> Self {
match type_ref {
InputType::InputObject(x) => SchemaType::InputObject(x),
InputType::Scalar(x) => SchemaType::Scalar(x),
InputType::Enum(x) => SchemaType::Enum(x),
}
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum OutputType<'a> {
Object(&'a SchemaObject<'a>),
Union(&'a SchemaUnion<'a>),
Interface(&'a SchemaInterface<'a>),
Scalar(&'a SchemaScalar<'a>),
Enum(&'a SchemaEnum<'a>),
}
impl<'a> OutputType<'a> {
#[inline]
pub fn name(&self) -> &str {
match self {
OutputType::Object(x) => x.name,
OutputType::Union(x) => x.name,
OutputType::Interface(x) => x.name,
OutputType::Scalar(x) => x.name,
OutputType::Enum(x) => x.name,
}
}
#[inline]
pub fn into_schema_type(&self) -> SchemaType<'a> {
match self {
OutputType::Object(x) => SchemaType::Object(x),
OutputType::Union(x) => SchemaType::Union(x),
OutputType::Interface(x) => SchemaType::Interface(x),
OutputType::Scalar(x) => SchemaType::Scalar(x),
OutputType::Enum(x) => SchemaType::Enum(x),
}
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum InputType<'a> {
InputObject(&'a SchemaInputObject<'a>),
Scalar(&'a SchemaScalar<'a>),
Enum(&'a SchemaEnum<'a>),
}
impl<'a> InputType<'a> {
#[inline]
pub fn name(&self) -> &str {
match self {
InputType::InputObject(o) => o.name,
InputType::Scalar(s) => s.name,
InputType::Enum(e) => e.name,
}
}
#[inline]
pub fn named_type(&self) -> SchemaType<'a> {
match self {
InputType::InputObject(x) => SchemaType::InputObject(x),
InputType::Scalar(x) => SchemaType::Scalar(x),
InputType::Enum(x) => SchemaType::Enum(x),
}
}
}
#[derive(Clone, Copy)]
pub enum TypeRef<'a> {
Type(&'a str),
ListType(&'a TypeRef<'a>),
NonNullType(&'a TypeRef<'a>),
}
impl<'a> TypeRef<'a> {
#[inline]
pub fn of_type(&self, schema: &'a Schema<'a>) -> &'a SchemaType<'a> {
match self {
TypeRef::Type(of_type) => {
let schema_type = schema
.get_type(of_type)
.expect("Referenced type should exist in the schema.");
schema_type
}
TypeRef::ListType(of_type) => of_type.of_type(schema),
TypeRef::NonNullType(of_type) => of_type.of_type(schema),
}
}
}
impl<'a> std::fmt::Debug for TypeRef<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Type(arg0) => f.debug_tuple("OutputTypeRef").field(&arg0).finish(),
Self::ListType(arg0) => f.debug_tuple("ListType").field(arg0).finish(),
Self::NonNullType(arg0) => f.debug_tuple("NonNullType").field(arg0).finish(),
}
}
}
impl<'a> PartialEq for TypeRef<'a> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Type(left), Self::Type(right)) => left == right,
(Self::ListType(left), Self::ListType(right)) => left == right,
(Self::NonNullType(left), Self::NonNullType(right)) => left == right,
_ => false,
}
}
}
fn maps_are_equal<'a, V>(
left: HashMap<&'a str, V, DefaultHashBuilder, &'a bumpalo::Bump>,
right: HashMap<&'a str, V, DefaultHashBuilder, &'a bumpalo::Bump>,
) -> bool
where
V: PartialEq + Copy,
{
let length_matches = left.len() == right.len();
length_matches
&& left.iter().all(|(k, left_val)| {
right
.get(k)
.map(|right_val| left_val == right_val)
.unwrap_or(false)
})
}
pub trait Named {
fn name(&self) -> &str;
}
impl<'a> Named for &'a SchemaInterface<'a> {
fn name(&self) -> &str {
self.name
}
}
impl<'a> Named for &'a SchemaObject<'a> {
fn name(&self) -> &str {
self.name
}
}
impl<'a> Named for &'a SchemaUnion<'a> {
fn name(&self) -> &str {
self.name
}
}
pub fn schema_type_to_type<'arena>(
ctx: &'arena ASTContext,
schema: &'arena Schema,
schema_type: TypeRef<'arena>,
) -> Type<'arena> {
match schema_type {
t @ TypeRef::Type(_) => match t.of_type(schema).input_type().expect("input schema type") {
InputType::InputObject(SchemaInputObject { name, .. })
| InputType::Scalar(SchemaScalar { name, .. })
| InputType::Enum(SchemaEnum { name, .. }) => Type::NamedType(NamedType { name }),
},
TypeRef::ListType(t) => Type::ListType(ctx.alloc(schema_type_to_type(ctx, schema, *t))),
TypeRef::NonNullType(t) => {
Type::NonNullType(ctx.alloc(schema_type_to_type(ctx, schema, *t)))
}
}
}