Skip to content

Commit

Permalink
feat: parse schema from SDL (#3)
Browse files Browse the repository at this point in the history
* initial SDL implementation

* export functions

* fix input type definitions

* fix missing implementors

* remove need for ParseResult

* down to 1 failing test

* fix kitchen sink by adding possible_interface back, we should verify whether this is a bug as a possible interface is also a possible type

* bug found in build_client_schema

* validate

* more fixes

* run clippy

* Update src/schema/build_client_schema.rs

Co-authored-by: Dominic Petrick <[email protected]>

* Update src/schema/build_client_schema.rs

Co-authored-by: Dominic Petrick <[email protected]>

---------

Co-authored-by: Dominic Petrick <[email protected]>
  • Loading branch information
JoviDeCroock and dpetrick authored Jun 5, 2024
1 parent 9122b54 commit 79440ed
Show file tree
Hide file tree
Showing 15 changed files with 2,251 additions and 41 deletions.
6 changes: 3 additions & 3 deletions src/ast/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ impl<'a> Selection<'a> {
#[inline]
pub fn field(&'a self) -> Option<&'a Field<'a>> {
match self {
Selection::Field(field) => Some(&field),
Selection::Field(field) => Some(field),
Selection::FragmentSpread(_) => None,
Selection::InlineFragment(_) => None,
}
Expand All @@ -479,7 +479,7 @@ impl<'a> Selection<'a> {
#[inline]
pub fn fragment_spread(&'a self) -> Option<&'a FragmentSpread<'a>> {
match self {
Selection::FragmentSpread(spread) => Some(&spread),
Selection::FragmentSpread(spread) => Some(spread),
Selection::Field(_) => None,
Selection::InlineFragment(_) => None,
}
Expand All @@ -489,7 +489,7 @@ impl<'a> Selection<'a> {
#[inline]
pub fn inline_fragment(&'a self) -> Option<&'a InlineFragment<'a>> {
match self {
Selection::InlineFragment(fragment) => Some(&fragment),
Selection::InlineFragment(fragment) => Some(fragment),
Selection::FragmentSpread(_) => None,
Selection::Field(_) => None,
}
Expand Down
14 changes: 7 additions & 7 deletions src/ast/ast_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ where
impl<'a> DefaultIn<'a> for Document<'a> {
fn default_in(arena: &'a bumpalo::Bump) -> Self {
Document {
definitions: Vec::new_in(&arena),
definitions: Vec::new_in(arena),
size_hint: 0,
}
}
Expand All @@ -83,47 +83,47 @@ impl<'a> DefaultIn<'a> for Document<'a> {
impl<'a> DefaultIn<'a> for VariableDefinitions<'a> {
fn default_in(arena: &'a bumpalo::Bump) -> Self {
VariableDefinitions {
children: Vec::new_in(&arena),
children: Vec::new_in(arena),
}
}
}

impl<'a> DefaultIn<'a> for ObjectValue<'a> {
fn default_in(arena: &'a bumpalo::Bump) -> Self {
ObjectValue {
children: Vec::new_in(&arena),
children: Vec::new_in(arena),
}
}
}

impl<'a> DefaultIn<'a> for ListValue<'a> {
fn default_in(arena: &'a bumpalo::Bump) -> Self {
ListValue {
children: Vec::new_in(&arena),
children: Vec::new_in(arena),
}
}
}

impl<'a> DefaultIn<'a> for Arguments<'a> {
fn default_in(arena: &'a bumpalo::Bump) -> Self {
Arguments {
children: Vec::new_in(&arena),
children: Vec::new_in(arena),
}
}
}

impl<'a> DefaultIn<'a> for Directives<'a> {
fn default_in(arena: &'a bumpalo::Bump) -> Self {
Directives {
children: Vec::new_in(&arena),
children: Vec::new_in(arena),
}
}
}

impl<'a> DefaultIn<'a> for SelectionSet<'a> {
fn default_in(arena: &'a bumpalo::Bump) -> Self {
SelectionSet {
selections: Vec::new_in(&arena),
selections: Vec::new_in(arena),
}
}
}
Expand Down
52 changes: 33 additions & 19 deletions src/schema/build_client_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub(crate) mod private {
&bumpalo::Bump,
> = HashMap::new_in(&self.ctx.arena);
for introspection_type in introspection.types.iter() {
let schema_type = BuildSchemaType::on_create(introspection_type, self);
let schema_type = BuildSchemaType::on_create(introspection_type, self, &introspection.types);
schema_types.insert(
self.ctx.alloc_str(introspection_type.name()),
self.ctx.alloc(schema_type),
Expand Down Expand Up @@ -94,47 +94,47 @@ pub(crate) mod private {
}

pub trait BuildSchemaType<'arena, T>: Sized {
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>) -> T;
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, introspection_types: &[IntrospectionType<'arena>]) -> T;
}

impl<'arena> BuildSchemaType<'arena, SchemaType<'arena>> for IntrospectionType<'arena> {
#[inline]
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>) -> SchemaType<'arena> {
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, introspection_types: &[IntrospectionType<'arena>]) -> SchemaType<'arena> {
match self {
IntrospectionType::Scalar(scalar) => {
SchemaType::Scalar(ctx.ctx.alloc(scalar.on_create(ctx)))
SchemaType::Scalar(ctx.ctx.alloc(scalar.on_create(ctx, introspection_types)))
}
IntrospectionType::Object(object) => {
SchemaType::Object(ctx.ctx.alloc(object.on_create(ctx)))
SchemaType::Object(ctx.ctx.alloc(object.on_create(ctx, introspection_types)))
}
IntrospectionType::Interface(interface) => {
SchemaType::Interface(ctx.ctx.alloc(interface.on_create(ctx)))
SchemaType::Interface(ctx.ctx.alloc(interface.on_create(ctx, introspection_types)))
}
IntrospectionType::Union(union_type) => {
SchemaType::Union(ctx.ctx.alloc(union_type.on_create(ctx)))
SchemaType::Union(ctx.ctx.alloc(union_type.on_create(ctx, introspection_types)))
}
IntrospectionType::Enum(enum_type) => {
SchemaType::Enum(ctx.ctx.alloc(enum_type.on_create(ctx)))
SchemaType::Enum(ctx.ctx.alloc(enum_type.on_create(ctx, introspection_types)))
}
IntrospectionType::InputObject(input_object) => {
SchemaType::InputObject(ctx.ctx.alloc(input_object.on_create(ctx)))
SchemaType::InputObject(ctx.ctx.alloc(input_object.on_create(ctx, introspection_types)))
}
}
}
}

impl<'arena> BuildSchemaType<'arena, SchemaScalar<'arena>> for IntrospectionScalarType<'arena> {
#[inline]
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>) -> SchemaScalar<'arena> {
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, _introspection_types: &[IntrospectionType<'arena>]) -> SchemaScalar<'arena> {
SchemaScalar::new(ctx.ctx.alloc_str(self.name))
}
}

impl<'arena> BuildSchemaType<'arena, SchemaEnum<'arena>> for IntrospectionEnumType<'arena> {
#[inline]
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>) -> SchemaEnum<'arena> {
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, _introspection_types: &[IntrospectionType<'arena>]) -> SchemaEnum<'arena> {
let name = ctx.ctx.alloc_str(self.name);
let mut enum_type = SchemaEnum::new(&ctx.ctx, name);
let mut enum_type = SchemaEnum::new(ctx.ctx, name);
for value in self.enum_values.iter() {
let value_name = ctx.ctx.alloc_str(value.name);
enum_type.add_value(ctx.ctx, value_name);
Expand All @@ -145,7 +145,7 @@ pub(crate) mod private {

impl<'arena> BuildSchemaType<'arena, SchemaUnion<'arena>> for IntrospectionUnionType<'arena> {
#[inline]
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>) -> SchemaUnion<'arena> {
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, _introspection_types: &[IntrospectionType<'arena>]) -> SchemaUnion<'arena> {
let name = ctx.ctx.alloc_str(self.name);
let mut schema_union_type = SchemaUnion::new(ctx.ctx, name);
for introspection_type_ref in self.possible_types.possible_types.iter() {
Expand Down Expand Up @@ -182,13 +182,13 @@ pub(crate) mod private {

impl<'arena> BuildSchemaType<'arena, SchemaObject<'arena>> for IntrospectionObjectType<'arena> {
#[inline]
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>) -> SchemaObject<'arena> {
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, _introspection_types: &[IntrospectionType<'arena>]) -> SchemaObject<'arena> {
let name = ctx.ctx.alloc_str(self.name);
let mut schema_object_type = SchemaObject::new(ctx.ctx, name);
for field in self.implementation.fields.iter() {
let field_name = ctx.ctx.alloc_str(field.name);
let mut schema_field = SchemaField::new(
&ctx.ctx,
ctx.ctx,
field_name,
from_output_type_ref(ctx.ctx, &field.of_type),
);
Expand Down Expand Up @@ -217,14 +217,14 @@ pub(crate) mod private {
for IntrospectionInterfaceType<'arena>
{
#[inline]
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>) -> SchemaInterface<'arena> {
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, introspection_types: &[IntrospectionType<'arena>]) -> SchemaInterface<'arena> {
let name = ctx.ctx.alloc_str(self.name);
let mut schema_interface_type = SchemaInterface::new(ctx.ctx, name);

for field in self.implementation.fields.iter() {
let field_name = ctx.ctx.alloc_str(field.name);
let mut schema_field = SchemaField::new(
&ctx.ctx,
ctx.ctx,
field_name,
from_output_type_ref(ctx.ctx, &field.of_type),
);
Expand All @@ -246,7 +246,21 @@ pub(crate) mod private {

for introspection_type_ref in self.possible_types.possible_types.iter() {
let name = ctx.ctx.alloc_str(introspection_type_ref.name);
schema_interface_type.add_possible_type(ctx.ctx, name);
if let Some(kind) = introspection_type_ref.kind {
if kind == "INTERFACE" {
schema_interface_type.add_possible_interface(ctx.ctx, name);
} else {
schema_interface_type.add_possible_type(ctx.ctx, name);
}
} else {
let introspection_type = introspection_types.iter().find(|f| f.name() == name);
if let Some(IntrospectionType::Interface(_)) = introspection_type {
schema_interface_type.add_possible_interface(ctx.ctx, name);
} else {
schema_interface_type.add_possible_type(ctx.ctx, name);
}
}

}

schema_interface_type
Expand All @@ -257,7 +271,7 @@ pub(crate) mod private {
for IntrospectionInputObjectType<'arena>
{
#[inline]
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>) -> SchemaInputObject<'arena> {
fn on_create(&self, ctx: &'arena BuildSchemaContext<'arena>, _introspection_types: &[IntrospectionType<'arena>]) -> SchemaInputObject<'arena> {
let name = ctx.ctx.alloc_str(self.name);
let mut input = SchemaInputObject::new(ctx.ctx, name);
for field in self.input_fields.iter() {
Expand Down
2 changes: 1 addition & 1 deletion src/schema/introspection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ pub enum IntrospectionInputTypeRef<'a> {
#[cfg_attr(feature = "json", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "json", serde(rename_all = "camelCase"))]
pub struct IntrospectionNamedTypeRef<'a> {
#[cfg_attr(feature = "json", serde(skip))]
#[cfg_attr(feature = "json", serde(borrow))]
pub kind: Option<&'a str>,
#[cfg_attr(feature = "json", serde(borrow))]
pub name: &'a str,
Expand Down
7 changes: 4 additions & 3 deletions src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@
//!
//! [More information on the Schema struct.](Schema)
pub mod build_client_schema;
pub mod introspection;
#[allow(clippy::module_inception)]
pub mod schema;
mod schema;
mod schema_reference;

pub mod build_client_schema;
pub mod introspection;
pub mod sdl;
pub use build_client_schema::BuildClientSchema;
pub use introspection::{IntrospectionQuery, IntrospectionSchema};
pub use schema::*;
Expand Down
24 changes: 18 additions & 6 deletions src/schema/schema.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::ast::{ASTContext, OperationKind};
use crate::ast::{ASTContext, DefaultIn, OperationKind};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use hashbrown::hash_map::DefaultHashBuilder;
use hashbrown::{HashMap, HashSet};

Expand All @@ -9,7 +10,7 @@ use hashbrown::{HashMap, HashSet};
/// AST documents for validation and execution. In this library the schema is never executable and
/// serves only for metadata and type information. It is hence a "Client Schema".
/// [Reference](https://spec.graphql.org/October2021/#sec-Schema)
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct Schema<'a> {
pub(crate) query_type: Option<&'a SchemaObject<'a>>,
pub(crate) mutation_type: Option<&'a SchemaObject<'a>>,
Expand All @@ -18,6 +19,17 @@ pub struct Schema<'a> {
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> {
/// Returns whether the schema is a default, empty schema
pub fn is_empty(&self) -> bool {
Expand Down Expand Up @@ -58,7 +70,7 @@ impl<'a> Schema<'a> {
/// Retrieves a kind by name from known schema types.
#[inline]
pub fn get_type(&self, name: &'a str) -> Option<&'a SchemaType<'a>> {
self.types.get(name).map(|x| *x)
self.types.get(name).copied()
}

/// Checks whether a given type is a sub type of another.
Expand Down Expand Up @@ -93,7 +105,7 @@ pub trait SchemaFields<'a>: Sized {

/// Get a known field by name
fn get_field(&self, name: &'a str) -> Option<&SchemaField<'a>> {
self.get_fields().get(name).map(|x| *x)
self.get_fields().get(name).copied()
}
}

Expand All @@ -103,7 +115,7 @@ pub trait SchemaInterfaces<'a>: Sized {
fn add_interface(&mut self, ctx: &'a ASTContext, interface: &'a str);

/// Get list of implemented [SchemaInterface]s
fn get_interfaces(&self) -> Vec<&'a str>;
fn get_interfaces(&self) -> Vec<'a, &'a str>;

/// Checks whether given [ObjectType] is a possible subtype
#[inline]
Expand Down Expand Up @@ -276,7 +288,7 @@ impl<'a> SchemaPossibleTypes<'a> for SchemaInterface<'a> {
fn add_possible_type(&mut self, _ctx: &'a ASTContext, object: &'a str) {
self.possible_types.push(object);
}

/// Get list of possible [SchemaObject] types
#[inline]
fn get_possible_types(&self) -> Vec<'a, &'a str> {
Expand Down
54 changes: 54 additions & 0 deletions src/schema/sdl/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use std::{error::Error, fmt::Display};

// Todo: Maybe reuse top level GraphQL/Syntax error struct? Would that be suitable?
#[derive(Debug)]
pub enum SchemaError {
SyntaxError(String),
ValidationError(String),
}

impl Display for SchemaError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SchemaError::SyntaxError(s) => write!(f, "{}", s),
SchemaError::ValidationError(s) => write!(f, "Validation error: {}", s),
}
}
}

impl Error for SchemaError {}

macro_rules! syntax_err {
($msg:literal, $($arg:tt)*) => {
Err(syntax!($msg, $($arg)*))
};

($msg:literal) => {
Err(syntax!($msg))
};
}

macro_rules! syntax {
($msg:literal, $($arg:tt)*) => {
SchemaError::SyntaxError(format!($msg, $($arg)*))
};

($msg:literal) => {
SchemaError::SyntaxError(format!($msg))
};
}

macro_rules! validation {
($msg:literal, $($arg:tt)*) => {
SchemaError::ValidationError(format!($msg, $($arg)*))
};

($msg:literal) => {
SchemaError::ValidationError(format!($msg))
};
}

// Required for macro visibility.
pub(crate) use syntax;
pub(crate) use syntax_err;
pub(crate) use validation;
Loading

0 comments on commit 79440ed

Please sign in to comment.