Skip to content

Commit

Permalink
Add ValidationOptions structure to pass in recursion limit
Browse files Browse the repository at this point in the history
Possibly also a diagnostic limit in the future.

Maybe there would be options that could differ between executable and
schema validation, but not right now
  • Loading branch information
goto-bus-stop committed Nov 21, 2023
1 parent c7e90f0 commit 08e6b6c
Show file tree
Hide file tree
Showing 30 changed files with 240 additions and 136 deletions.
19 changes: 12 additions & 7 deletions crates/apollo-compiler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Older version may or may not be compatible.
You can get started with `apollo-compiler`:
```rust
use apollo_compiler::Schema;
use apollo_compiler::validation::ValidationOptions;

let input = r#"
interface Pet {
Expand Down Expand Up @@ -87,14 +88,15 @@ let schema = Schema::parse(input, "document.graphql");

/// In case of validation errors, the panic message will be nicely formatted
/// to point at relevant parts of the source file(s)
schema.validate().unwrap();
schema.validate(ValidationOptions::default()).unwrap();
```

### Examples
#### Accessing fragment definition field types

```rust
use apollo_compiler::{Schema, ExecutableDocument, Node, executable};
use apollo_compiler::validation::ValidationOptions;

fn main() {
let schema_input = r#"
Expand Down Expand Up @@ -124,8 +126,8 @@ fn main() {
let schema = Schema::parse(schema_input, "schema.graphql");
let document = ExecutableDocument::parse(&schema, query_input, "query.graphql");

schema.validate().unwrap();
document.validate(&schema).unwrap();
schema.validate(ValidationOptions::default()).unwrap();
document.validate(&schema, ValidationOptions::default()).unwrap();

let op = document.get_operation(Some("getUser")).expect("getUser query does not exist");
let fragment_in_op = op.selection_set.selections.iter().filter_map(|sel| match sel {
Expand All @@ -149,6 +151,7 @@ fn main() {
#### Get a directive defined on a field used in a query operation definition.
```rust
use apollo_compiler::{Schema, ExecutableDocument, Node, executable};
use apollo_compiler::validation::ValidationOptions;

fn main() {
let schema_input = r#"
Expand Down Expand Up @@ -187,8 +190,8 @@ fn main() {
let schema = Schema::parse(schema_input, "schema.graphql");
let document = ExecutableDocument::parse(&schema, query_input, "query.graphql");

schema.validate().unwrap();
document.validate(&schema).unwrap();
schema.validate(ValidationOptions::default()).unwrap();
document.validate(&schema, ValidationOptions::default()).unwrap();

let get_product_op = document
.get_operation(Some("getProduct"))
Expand All @@ -215,6 +218,8 @@ fn main() {

#### Printing diagnostics for a faulty GraphQL document
```rust
use apollo_compiler::validation::ValidationOptions;

let input = r#"
query {
cat {
Expand Down Expand Up @@ -273,10 +278,10 @@ union CatOrDog = Cat | Dog

let (schema, executable) = apollo_compiler::parse_mixed(input, "document.graphql");

if let Err(diagnostics) = schema.validate() {
if let Err(diagnostics) = schema.validate(ValidationOptions::default()) {
println!("{diagnostics}")
}
if let Err(diagnostics) = executable.validate(&schema) {
if let Err(diagnostics) = executable.validate(&schema, ValidationOptions::default()) {
println!("{diagnostics}")
}
```
Expand Down
4 changes: 2 additions & 2 deletions crates/apollo-compiler/examples/file_watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ impl FileWatcher {
}

fn validate(&self, (schema, executable): &(Schema, ExecutableDocument)) {
if let Err(err) = schema.validate() {
if let Err(err) = schema.validate(Default::default()) {
println!("{err}")
}
if let Err(err) = executable.validate(schema) {
if let Err(err) = executable.validate(schema, Default::default()) {
println!("{err}")
}
}
Expand Down
12 changes: 6 additions & 6 deletions crates/apollo-compiler/examples/multi_source_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ fn compile_from_dir() -> io::Result<()> {
let entry = entry?;
let src = fs::read_to_string(entry.path()).expect("Could not read document file.");
let (schema, executable) = parse_mixed(&src, entry.path());
schema.validate().unwrap();
executable.validate(&schema).unwrap();
schema.validate(Default::default()).unwrap();
executable.validate(&schema, Default::default()).unwrap();
}
}
Ok(())
Expand All @@ -42,14 +42,14 @@ fn compile_schema_and_query_files() -> io::Result<()> {
.parse(src, schema)
.parse(src_ext, schema_ext)
.build();
schema.validate().unwrap();
schema.validate(Default::default()).unwrap();

// get_dog_name is a query-only file
let query = Path::new("crates/apollo-compiler/examples/documents/get_dog_name.graphql");
let src = fs::read_to_string(query).expect("Could not read query file.");
let doc = ExecutableDocument::parse(&schema, src, query);

doc.validate(&schema).unwrap();
doc.validate(&schema, Default::default()).unwrap();

Ok(())
}
Expand Down Expand Up @@ -127,9 +127,9 @@ query getDogName {
}
"#;
let schema = Schema::parse(schema, "schema.graphl");
schema.validate().unwrap();
schema.validate(Default::default()).unwrap();
let doc = ExecutableDocument::parse(&schema, query, "query.graphql");
doc.validate(&schema).unwrap();
doc.validate(&schema, Default::default()).unwrap();

Ok(())
}
4 changes: 2 additions & 2 deletions crates/apollo-compiler/examples/rename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn main() {
fn renamed() -> Schema {
let input = "type Query { field: Int }";
let mut schema = Schema::parse(input, "schema.graphql");
schema.validate().unwrap();
schema.validate(Default::default()).unwrap();

// 1. Remove the definition from the `types` map, using its old name as a key
let mut type_def = schema.types.remove("Query").unwrap();
Expand All @@ -38,7 +38,7 @@ fn renamed() -> Schema {
.unwrap()
.name = new_name;

schema.validate().unwrap();
schema.validate(Default::default()).unwrap();
schema
}

Expand Down
6 changes: 4 additions & 2 deletions crates/apollo-compiler/examples/timed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fn main() -> ExitCode {
let step = format!("Schema parse ({} bytes)", schema_source.len());
let schema = timed(&step, || Schema::parse(schema_source, schema_filename));

if let Err(errors) = timed("Schema validation", || schema.validate()) {
if let Err(errors) = timed("Schema validation", || schema.validate(Default::default())) {
println!("Schema is invalid:\n{errors}")
}

Expand All @@ -36,7 +36,9 @@ fn main() -> ExitCode {
ExecutableDocument::parse(&schema, executable_source, executable_filename)
});

if let Err(errors) = timed("Executable document validation", || doc.validate(&schema)) {
if let Err(errors) = timed("Executable document validation", || {
doc.validate(&schema, Default::default())
}) {
println!("Executable document is invalid:\n{errors}")
}

Expand Down
4 changes: 2 additions & 2 deletions crates/apollo-compiler/examples/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ fn main() {
};

let (schema, executable) = apollo_compiler::parse_mixed(source, filename);
let schema_result = schema.validate();
let executable_result = executable.validate(&schema);
let schema_result = schema.validate(Default::default());
let executable_result = executable.validate(&schema, Default::default());
let has_errors = schema_result.is_err() || executable_result.is_err();
match schema_result {
Ok(warnings) => println!("{warnings}"),
Expand Down
12 changes: 10 additions & 2 deletions crates/apollo-compiler/src/ast/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::schema::ComponentName;
use crate::schema::ComponentOrigin;
use crate::schema::SchemaBuilder;
use crate::validation::DiagnosticList;
use crate::validation::ValidationOptions;
use crate::ExecutableDocument;
use crate::Parser;
use crate::Schema;
Expand Down Expand Up @@ -47,15 +48,22 @@ impl Document {
}

/// Validate as an executable document, as much as possible without a schema
pub fn validate_standalone_executable(&self) -> Result<(), DiagnosticList> {
pub fn validate_standalone_executable(
&self,
options: ValidationOptions,
) -> Result<(), DiagnosticList> {
let type_system_definitions_are_errors = true;
let executable = crate::executable::from_ast::document_from_ast(
None,
self,
type_system_definitions_are_errors,
);
let mut errors = DiagnosticList::new(None, self.sources.clone());
crate::executable::validation::validate_standalone_executable(&mut errors, &executable);
crate::executable::validation::validate_standalone_executable(
&mut errors,
&executable,
options,
);
errors.into_result()
}

Expand Down
3 changes: 3 additions & 0 deletions crates/apollo-compiler/src/database/inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ pub(crate) trait InputDatabase {
#[salsa::input]
fn input(&self, file_id: FileId) -> Source;

#[salsa::input]
fn recursion_limit(&self) -> usize;

#[salsa::input]
fn schema_input(&self) -> Option<Arc<crate::Schema>>;

Expand Down
9 changes: 7 additions & 2 deletions crates/apollo-compiler/src/executable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub use crate::ast::{
VariableDefinition,
};
use crate::validation::DiagnosticList;
use crate::validation::ValidationOptions;
use crate::NodeLocation;
use std::fmt;

Expand Down Expand Up @@ -227,9 +228,13 @@ impl ExecutableDocument {
Parser::new().parse_executable(schema, source_text, path)
}

pub fn validate(&self, schema: &Schema) -> Result<(), DiagnosticList> {
pub fn validate(
&self,
schema: &Schema,
options: ValidationOptions,
) -> Result<(), DiagnosticList> {
let mut errors = DiagnosticList::new(Some(schema.sources.clone()), self.sources.clone());
validation::validate_executable_document(&mut errors, schema, self);
validation::validate_executable_document(&mut errors, schema, self, options);
errors.into_result()
}

Expand Down
10 changes: 8 additions & 2 deletions crates/apollo-compiler/src/executable/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use super::FieldSet;
use crate::ast;
use crate::validation::Details;
use crate::validation::DiagnosticList;
use crate::validation::ValidationOptions;
use crate::ExecutableDocument;
use crate::FileId;
use crate::InputDatabase;
Expand All @@ -14,18 +15,20 @@ pub(crate) fn validate_executable_document(
errors: &mut DiagnosticList,
schema: &Schema,
document: &ExecutableDocument,
options: ValidationOptions,
) {
validate_common(errors, document);
compiler_validation(errors, Some(schema), document);
compiler_validation(errors, Some(schema), document, options);
// TODO
}

pub(crate) fn validate_standalone_executable(
errors: &mut DiagnosticList,
document: &ExecutableDocument,
options: ValidationOptions,
) {
validate_common(errors, document);
compiler_validation(errors, None, document);
compiler_validation(errors, None, document, options);
}

pub(crate) fn validate_common(errors: &mut DiagnosticList, document: &ExecutableDocument) {
Expand Down Expand Up @@ -75,6 +78,7 @@ fn compiler_validation(
errors: &mut DiagnosticList,
schema: Option<&Schema>,
document: &ExecutableDocument,
options: ValidationOptions,
) {
let mut compiler = crate::ApolloCompiler::new();
let mut ids = Vec::new();
Expand All @@ -93,6 +97,8 @@ fn compiler_validation(
compiler.db.set_schema_input(Some(Arc::new(schema.clone())));
}

compiler.db.set_recursion_limit(options.recursion_limit);

let ast_id = FileId::HACK_TMP;
ids.push(ast_id);
let ast = document.to_ast();
Expand Down
2 changes: 1 addition & 1 deletion crates/apollo-compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod node;
mod node_str;
mod parser;
pub mod schema;
mod validation;
pub mod validation;

use crate::database::{InputDatabase, ReprDatabase, RootDatabase, Source};
use crate::diagnostics::ApolloDiagnostic;
Expand Down
5 changes: 3 additions & 2 deletions crates/apollo-compiler/src/schema/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! High-level representation of a GraphQL schema

use crate::ast;
use crate::validation::ValidationOptions;
use crate::FileId;
use crate::Node;
use crate::NodeLocation;
Expand Down Expand Up @@ -313,9 +314,9 @@ impl Schema {
}

/// Returns `Err` if invalid, or `Ok` for potential warnings or advice
pub fn validate(&self) -> Result<DiagnosticList, DiagnosticList> {
pub fn validate(&self, options: ValidationOptions) -> Result<DiagnosticList, DiagnosticList> {
let mut errors = DiagnosticList::new(None, self.sources.clone());
let warnings_and_advice = validation::validate_schema(&mut errors, self);
let warnings_and_advice = validation::validate_schema(&mut errors, self, options);
let valid = errors.is_empty();
for diagnostic in warnings_and_advice {
errors.push(
Expand Down
6 changes: 5 additions & 1 deletion crates/apollo-compiler/src/schema/validation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::BuildError;
use crate::validation::Details;
use crate::validation::DiagnosticList;
use crate::validation::ValidationOptions;
use crate::FileId;
use crate::InputDatabase;
use crate::Schema;
Expand All @@ -10,14 +11,15 @@ use std::sync::Arc;
pub(crate) fn validate_schema(
errors: &mut DiagnosticList,
schema: &Schema,
options: ValidationOptions,
) -> Vec<crate::ApolloDiagnostic> {
for (&file_id, source) in schema.sources.iter() {
source.validate_parse_errors(errors, file_id)
}
for build_error in &schema.build_errors {
validate_build_error(errors, build_error)
}
compiler_validation(errors, schema)
compiler_validation(errors, schema, options)
}

fn validate_build_error(errors: &mut DiagnosticList, build_error: &BuildError) {
Expand Down Expand Up @@ -47,6 +49,7 @@ fn validate_build_error(errors: &mut DiagnosticList, build_error: &BuildError) {
fn compiler_validation(
errors: &mut DiagnosticList,
schema: &Schema,
options: ValidationOptions,
) -> Vec<crate::ApolloDiagnostic> {
let mut compiler = crate::ApolloCompiler::new();
let mut ids = Vec::new();
Expand All @@ -68,6 +71,7 @@ fn compiler_validation(
},
);
compiler.db.set_source_files(ids);
compiler.db.set_recursion_limit(options.recursion_limit);
let mut warnings_and_advice = Vec::new();
for diagnostic in compiler.db.validate_type_system() {
if diagnostic.data.is_error() {
Expand Down
6 changes: 4 additions & 2 deletions crates/apollo-compiler/src/validation/directive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,10 @@ impl FindRecursiveDirective<'_> {
fn check(
schema: &schema::Schema,
directive_def: &Node<ast::DirectiveDefinition>,
recursion_limit: usize,
) -> Result<(), CycleError<ast::Directive>> {
let mut recursion_stack = RecursionStack::with_root(directive_def.name.clone(), 500);
let mut recursion_stack =
RecursionStack::with_root(directive_def.name.clone(), recursion_limit);
FindRecursiveDirective { schema }
.directive_definition(recursion_stack.guard(), directive_def)
}
Expand All @@ -150,7 +152,7 @@ pub(crate) fn validate_directive_definition(
// references itself directly.
//
// Returns Recursive Definition error.
if let Err(error) = FindRecursiveDirective::check(&db.schema(), &def) {
if let Err(error) = FindRecursiveDirective::check(&db.schema(), &def, db.recursion_limit()) {
let definition_location = def.location();
let head_location = NodeLocation::recompose(def.location(), def.name.location());
let mut diagnostic = ApolloDiagnostic::new(
Expand Down
2 changes: 1 addition & 1 deletion crates/apollo-compiler/src/validation/fragment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ pub(crate) fn validate_fragment_cycles(
Ok(())
}

let mut visited = RecursionStack::with_root(def.name.clone(), 500);
let mut visited = RecursionStack::with_root(def.name.clone(), db.recursion_limit());

if let Err(cycle) =
detect_fragment_cycles(&named_fragments, &def.selection_set, &mut visited.guard())
Expand Down
Loading

0 comments on commit 08e6b6c

Please sign in to comment.