Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: parse schema from SDL #3

Merged
merged 13 commits into from
Jun 5, 2024
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))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you end up figuring out what's up here with the skips?

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
Loading