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

refactor(validate): remove need for default trait #2

Merged
merged 3 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ test graphql_ast_print_apollo_parser ... bench: 20,861 ns/iter (+/- 518)

```
test graphql_ast_fold ... bench: 8,466 ns/iter (+/- 768)
test graphql_ast_validate ... bench: 2,339 ns/iter (+/- 127)
test graphql_ast_validate ... bench: 1,504 ns/iter (+/- 46)
test graphql_load_introspection ... bench: 90,265 ns/iter (+/- 4,899)
```

Expand Down
9 changes: 9 additions & 0 deletions src/ast/ast_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ pub trait DefaultIn<'a> {
fn default_in(arena: &'a bumpalo::Bump) -> Self;
}

impl<'a, T> DefaultIn<'a> for T
where
T: Default,
{
fn default_in(_ctx: &'a bumpalo::Bump) -> Self {
Self::default()
}
}

impl<'a> DefaultIn<'a> for Document<'a> {
fn default_in(arena: &'a bumpalo::Bump) -> Self {
Document {
Expand Down
1 change: 1 addition & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ mod printer;

pub use ast::*;
pub use ast_kind::ASTKind;
pub use ast_conversion::DefaultIn;
pub use parser::ParseNode;
pub use printer::PrintNode;
2 changes: 1 addition & 1 deletion src/validate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
//! As such, the [`DefaultRules`](rules::DefaultRules) rule is a [`ValidationRule`] itself that's
//! composed using the [`ComposedVisitor`](crate::visit::ComposedVisitor) utility.
//!
//! All rules must implement the `Default` trait, which makes it easier to quickly run a validation
//! All rules must implement the [`DefaultIn`](crate::ast::DefaultIn) or `Default` trait, which makes it easier to quickly run a validation
//! rule and isolates them from external state, since no validation requires any external state.
//!
//! For example, this is one way to run a validation rule, in this case `DefaultRules`:
Expand Down
16 changes: 13 additions & 3 deletions src/validate/rules/known_fragment_names.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
use bumpalo::collections::Vec;

use super::super::{ValidationContext, ValidationRule};
use crate::{ast::*, visit::*};

/// Validate a document for all fragment names in spreads to be defined in the same document.
///
/// See [`ValidationRule`]
/// [Reference](https://spec.graphql.org/October2021/#sec-Fragment-spread-target-defined)
#[derive(Default)]
pub struct KnownFragmentNames<'a> {
fragment_names: Vec<&'a str>,
fragment_spreads: Vec<&'a str>,
fragment_names: Vec<'a, &'a str>,
fragment_spreads: Vec<'a, &'a str>,
}

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

impl<'a> ValidationRule<'a> for KnownFragmentNames<'a> {}
Expand Down
21 changes: 15 additions & 6 deletions src/validate/rules/no_fragment_cycles.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use hashbrown::HashMap;
use bumpalo::{collections::Vec, Bump};
use hashbrown::{hash_map::DefaultHashBuilder, HashMap};

use super::super::{ValidationContext, ValidationRule};
use crate::{ast::*, visit::*};
Expand All @@ -7,10 +8,18 @@ use crate::{ast::*, visit::*};
///
/// See [`ValidationRule`]
/// [Reference](https://spec.graphql.org/October2021/#sec-Fragment-spreads-must-not-form-cycles)
#[derive(Default)]
pub struct NoFragmentCycles<'a> {
fragment_edges: HashMap<&'a str, Vec<&'a str>>,
used_fragments: Vec<&'a str>,
fragment_edges: HashMap<&'a str, Vec<'a, &'a str>, DefaultHashBuilder, &'a Bump>,
used_fragments: Vec<'a, &'a str>,
}

impl<'a> DefaultIn<'a> for NoFragmentCycles<'a> {
fn default_in(arena: &'a bumpalo::Bump) -> Self {
Self {
fragment_edges: HashMap::new_in(arena),
used_fragments: Vec::new_in(arena),
}
}
}

impl<'a> ValidationRule<'a> for NoFragmentCycles<'a> {}
Expand Down Expand Up @@ -64,7 +73,7 @@ impl<'a> Visitor<'a, ValidationContext<'a>> for NoFragmentCycles<'a> {
_document: &'a Document<'a>,
_info: &VisitInfo,
) -> VisitFlow {
let mut visited: Vec<&'a str> = Vec::default();
let mut visited: Vec<&'a str> = Vec::new_in(&ctx.arena);
for (name, _) in self.fragment_edges.iter() {
if contains_edge(&mut visited, name, name, &self.fragment_edges) {
ctx.add_error("Cannot spread fragments within themselves");
Expand Down Expand Up @@ -107,7 +116,7 @@ fn contains_edge<'a>(
visited: &mut Vec<&'a str>,
toplevel_name: &'a str,
current_name: &'a str,
fragment_edges: &HashMap<&'a str, Vec<&'a str>>,
fragment_edges: &HashMap<&'a str, Vec<&'a str>, DefaultHashBuilder, &'a Bump>,
) -> bool {
if visited.contains(&current_name) {
true
Expand Down
42 changes: 27 additions & 15 deletions src/validate/rules/no_undefined_variables.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,43 @@
use hashbrown::HashMap;
use bumpalo::{collections::Vec, Bump};
use hashbrown::{hash_map::DefaultHashBuilder, HashMap};

use super::super::{ValidationContext, ValidationRule};
use crate::{ast::*, visit::*};

#[derive(Default, Clone)]
#[derive(Clone)]
struct OperationEdge<'a> {
defined_vars: Vec<&'a str>,
used_fragments: Vec<&'a str>,
defined_vars: Vec<'a, &'a str>,
used_fragments: Vec<'a, &'a str>,
}

#[derive(Default, Clone)]
#[derive(Clone)]
struct FragmentEdge<'a> {
used_vars: Vec<&'a str>,
used_fragments: Vec<&'a str>,
used_vars: Vec<'a, &'a str>,
used_fragments: Vec<'a, &'a str>,
}

/// Validate that a document defines all the variables it uses per operation
///
/// See [`ValidationRule`]
/// [Reference](https://spec.graphql.org/October2021/#sec-All-Variable-Uses-Defined)
#[derive(Default)]
pub struct NoUndefinedVariables<'a> {
used_vars: Vec<&'a str>,
defined_vars: Vec<&'a str>,
used_fragments: Vec<&'a str>,
operation_edges: std::vec::Vec<OperationEdge<'a>>,
fragment_edges: HashMap<&'a str, FragmentEdge<'a>>,
used_vars: Vec<'a, &'a str>,
defined_vars: Vec<'a, &'a str>,
used_fragments: Vec<'a, &'a str>,
operation_edges: Vec<'a, OperationEdge<'a>>,
fragment_edges: HashMap<&'a str, FragmentEdge<'a>, DefaultHashBuilder, &'a Bump>,
}

impl<'a> DefaultIn<'a> for NoUndefinedVariables<'a> {
fn default_in(arena: &'a bumpalo::Bump) -> Self {
Self {
used_vars: Vec::new_in(arena),
defined_vars: Vec::new_in(arena),
used_fragments: Vec::new_in(arena),
operation_edges: Vec::new_in(arena),
fragment_edges: HashMap::new_in(arena),
}
}
}

impl<'a> ValidationRule<'a> for NoUndefinedVariables<'a> {}
Expand Down Expand Up @@ -112,7 +124,7 @@ impl<'a> Visitor<'a, ValidationContext<'a>> for NoUndefinedVariables<'a> {
_document: &'a Document<'a>,
_info: &VisitInfo,
) -> VisitFlow {
let mut visited: Vec<&'a str> = Vec::default();
let mut visited: Vec<&'a str> = Vec::new_in(&ctx.arena);
for operation_edge in self.operation_edges.iter() {
if references_undefined_var(
&mut visited,
Expand All @@ -131,7 +143,7 @@ impl<'a> Visitor<'a, ValidationContext<'a>> for NoUndefinedVariables<'a> {

fn references_undefined_var<'a>(
visited: &mut Vec<&'a str>,
fragment_edges: &HashMap<&'a str, FragmentEdge<'a>>,
fragment_edges: &HashMap<&'a str, FragmentEdge<'a>, DefaultHashBuilder, &'a Bump>,
defined_vars: &Vec<&'a str>,
used_fragments: &Vec<&'a str>,
) -> bool {
Expand Down
16 changes: 13 additions & 3 deletions src/validate/rules/no_unused_fragments.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
use bumpalo::collections::Vec;

use super::super::{ValidationContext, ValidationRule};
use crate::{ast::*, visit::*};

/// Validate that a document uses all the fragments it defines at least once.
///
/// See [`ValidationRule`]
/// [Reference](https://spec.graphql.org/October2021/#sec-Fragments-Must-Be-Used)
#[derive(Default)]
pub struct NoUnusedFragments<'a> {
fragment_names: Vec<&'a str>,
fragment_spreads: Vec<&'a str>,
fragment_names: Vec<'a, &'a str>,
fragment_spreads: Vec<'a, &'a str>,
}

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

impl<'a> ValidationRule<'a> for NoUnusedFragments<'a> {}
Expand Down
13 changes: 11 additions & 2 deletions src/validate/rules/unique_argument_names.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
use bumpalo::collections::Vec;

use super::super::{ValidationContext, ValidationRule};
use crate::{ast::*, visit::*};

/// Validates that no arguments anywhere contain duplicate names.
///
/// See [`ValidationRule`]
/// [Reference](https://spec.graphql.org/October2021/#sec-Argument-Uniqueness)
#[derive(Default)]
pub struct UniqueArgumentNames<'a> {
used_argument_names: Vec<&'a str>,
used_argument_names: Vec<'a, &'a str>,
}

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

impl<'a> ValidationRule<'a> for UniqueArgumentNames<'a> {}
Expand Down
13 changes: 11 additions & 2 deletions src/validate/rules/unique_fragment_names.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use bumpalo::collections::Vec;

use super::super::{ValidationContext, ValidationRule};
use crate::{ast::*, visit::*};

Expand All @@ -6,9 +8,16 @@ use crate::{ast::*, visit::*};
///
/// See [`ValidationRule`]
/// [Reference](https://spec.graphql.org/October2021/#sec-Fragment-Name-Uniqueness)
#[derive(Default)]
pub struct UniqueFragmentNames<'a> {
used_fragment_names: Vec<&'a str>,
used_fragment_names: Vec<'a, &'a str>,
}

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

impl<'a> ValidationRule<'a> for UniqueFragmentNames<'a> {}
Expand Down
13 changes: 11 additions & 2 deletions src/validate/rules/unique_operation_names.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use bumpalo::collections::Vec;

use super::super::{ValidationContext, ValidationRule};
use crate::{ast::*, visit::*};

Expand All @@ -6,9 +8,16 @@ use crate::{ast::*, visit::*};
///
/// See [`ValidationRule`]
/// [Reference](https://spec.graphql.org/October2021/#sec-Operation-Name-Uniqueness)
#[derive(Default)]
pub struct UniqueOperationNames<'a> {
used_operation_names: Vec<&'a str>,
used_operation_names: Vec<'a, &'a str>,
}

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

impl<'a> ValidationRule<'a> for UniqueOperationNames<'a> {}
Expand Down
13 changes: 11 additions & 2 deletions src/validate/rules/unique_variable_names.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use bumpalo::collections::Vec;

use super::super::{ValidationContext, ValidationRule};
use crate::{ast::*, visit::*};

Expand All @@ -6,9 +8,16 @@ use crate::{ast::*, visit::*};
///
/// See [`ValidationRule`]
/// [Reference](https://spec.graphql.org/October2021/#sec-Variable-Uniqueness)
#[derive(Default)]
pub struct UniqueVariableNames<'a> {
used_variable_names: Vec<&'a str>,
used_variable_names: Vec<'a, &'a str>,
}

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

impl<'a> ValidationRule<'a> for UniqueVariableNames<'a> {}
Expand Down
13 changes: 7 additions & 6 deletions src/validate/validate.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::context::ValidationContext;
use crate::ast::{ASTContext, Document};
use crate::ast::{ASTContext, DefaultIn, Document};
use crate::error::Result;
use crate::visit::{ComposedVisitor, VisitNode, Visitor};
use std::borrow::Borrow;
Expand All @@ -13,26 +13,27 @@ use std::borrow::Borrow;
/// Rules implement the `Default` trait, which allows them to be instantiated easily.
/// The intention of using `Default` is for rules to not carry any external
/// state as for GraphQL validation no external state is needed.
pub trait ValidationRule<'a>: Visitor<'a, ValidationContext<'a>> + Default {
pub trait ValidationRule<'a>: Visitor<'a, ValidationContext<'a>> + DefaultIn<'a> {
/// Run this `ValidationRule` against the given document and return a result which errors if
/// the rule fails on the document.
#[inline]
fn validate(ctx: &'a ASTContext, document: &'a Document<'a>) -> Result<()> {
let mut validation = ValidationContext::new(ctx);
let mut visitor = Self::default();
let mut visitor = Self::default_in(&ctx.arena);
document.visit(&mut validation, &mut visitor);
validation.to_result()
}
}

impl<'a, A, B> Default for ComposedVisitor<'a, ValidationContext<'a>, A, B>
impl<'a, A, B> DefaultIn<'a> for ComposedVisitor<'a, ValidationContext<'a>, A, B>
where
A: ValidationRule<'a>,
B: ValidationRule<'a>,
{
#[inline]
fn default() -> Self {
ComposedVisitor::new(A::default(), B::default())
fn default_in(arena: &'a bumpalo::Bump) -> Self {
ComposedVisitor::new(A::default_in(arena), B::default_in(arena))

}
}

Expand Down
Loading