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(apollo-parser, apollo-compiler): support parsing and validating FieldSet #685

Merged
merged 10 commits into from
Oct 10, 2023
17 changes: 13 additions & 4 deletions crates/apollo-compiler/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ Assorted `Schema` API changes by [SimonSapin] in [pull/678]:

## Features

- Add opt-in configuration for “orphan” extensions to be “adopted”, by [SimonSapin] in [pull/678]
- **Add `executable::FieldSet` for a selection set with optional outer brackets - [lrlna], [pull/685] fixing [issue/681]**
This is intended to parse string value of a [`FieldSet` custom scalar][fieldset]
used in some Apollo Federation directives in the context of a specific schema and type.
Its `validate` method calls a subset of validation rules relevant to selection sets.
which is not part of a document.

- **Add opt-in configuration for “orphan” extensions to be “adopted” - [SimonSapin], [pull/678]**

Type extensions and schema extensions without a corresponding definition
are normally ignored except for recording a validation error.
Expand All @@ -50,17 +56,20 @@ Assorted `Schema` API changes by [SimonSapin] in [pull/678]:
schema.validate()?;
```


## Fixes

- Allow built-in directives to be redefined, by [SimonSapin] in [pull/684], [issue/656]
- Allow schema extensions to extend a schema definition implied by object types named after default root operations, by [SimonSapin] in [pull/678], [issues/682]
- **Allow built-in directives to be redefined - [SimonSapin], [pull/684] fixing [issue/656]**
- **Allow schema extensions to extend a schema definition implied by object types named after default root operations - [SimonSapin], [pull/678] fixing [issues/682]**

[lrlna]: https://github.com/lrlna
[SimonSapin]: https://github.com/SimonSapin
[issue/656]: https://github.com/apollographql/apollo-rs/issues/656
[issue/682]: https://github.com/apollographql/apollo-rs/issues/682
[issue/681]: https://github.com/apollographql/apollo-rs/issues/681
[pull/678]: https://github.com/apollographql/apollo-rs/pull/678
[pull/684]: https://github.com/apollographql/apollo-rs/pull/684
[pull/685]: https://github.com/apollographql/apollo-rs/pull/685
[fieldset]: https://www.apollographql.com/docs/federation/subgraph-spec/#scalar-fieldset

# [1.0.0-beta.1](https://crates.io/crates/apollo-compiler/1.0.0-beta.1) - 2023-10-05

Expand Down
16 changes: 11 additions & 5 deletions crates/apollo-compiler/src/ast/from_cst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -641,14 +641,20 @@ impl Convert for cst::SelectionSet {
type Target = Vec<ast::Selection>;

fn convert(&self, file_id: FileId) -> Option<Self::Target> {
Some(
self.selections()
.filter_map(|selection| selection.convert(file_id))
.collect(),
)
Some(convert_selection_set(self, file_id))
}
}

pub(crate) fn convert_selection_set(
selection_set: &cst::SelectionSet,
file_id: FileId,
) -> Vec<ast::Selection> {
selection_set
.selections()
.filter_map(|selection| selection.convert(file_id))
.collect()
}

impl Convert for cst::Selection {
type Target = ast::Selection;

Expand Down
2 changes: 1 addition & 1 deletion crates/apollo-compiler/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use crate::SourceFile;
use std::collections::HashMap;
use std::sync::Arc;

mod from_cst;
pub(crate) mod from_cst;
pub(crate) mod impls;
pub(crate) mod serialize;

Expand Down
8 changes: 4 additions & 4 deletions crates/apollo-compiler/src/executable/from_ast.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use super::*;

struct BuildErrors {
errors: Vec<BuildError>,
path: SelectionPath,
pub(crate) struct BuildErrors {
pub(crate) errors: Vec<BuildError>,
pub(crate) path: SelectionPath,
}

pub(crate) fn document_from_ast(
Expand Down Expand Up @@ -147,7 +147,7 @@ impl Fragment {
}

impl SelectionSet {
fn extend_from_ast(
pub(crate) fn extend_from_ast(
&mut self,
schema: Option<&Schema>,
errors: &mut BuildErrors,
Expand Down
46 changes: 44 additions & 2 deletions crates/apollo-compiler/src/executable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,23 @@ pub struct ExecutableDocument {
pub fragments: IndexMap<Name, Node<Fragment>>,
}

/// FieldSet information created for FieldSet parsing in `@requires` directive.
/// Annotated with type information.
#[derive(Debug, Clone)]
pub struct FieldSet {
/// If this document was originally parsed from a source file,
/// that file and its ID.
///
/// The document may have been modified since.
pub source: Option<(FileId, Arc<SourceFile>)>,

/// Errors that occurred when building this FieldSet,
/// either parsing a source file or converting from AST.
pub(crate) build_errors: Vec<BuildError>,

pub selection_set: SelectionSet,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Operation {
pub operation_type: OperationType,
Expand Down Expand Up @@ -176,8 +193,8 @@ pub(crate) enum BuildError {

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SelectionPath {
root: ExecutableDefinitionName,
nested_fields: Vec<Name>,
pub(crate) root: ExecutableDefinitionName,
pub(crate) nested_fields: Vec<Name>,
}

/// Designates by name a top-level definition in an executable document
Expand Down Expand Up @@ -673,6 +690,31 @@ impl FragmentSpread {
}
}

impl FieldSet {
/// Parse the given source a selection set with optional outer brackets.
///
/// `path` is the filesystem path (or arbitrary string) used in diagnostics
/// to identify this source file to users.
///
/// Create a [`Parser`] to use different parser configuration.
pub fn parse(
schema: &Schema,
type_name: impl Into<NamedType>,
source_text: impl Into<String>,
path: impl AsRef<Path>,
) -> Self {
Parser::new().parse_field_set(schema, type_name, source_text, path)
}

pub fn validate(&self, schema: &Schema) -> Result<(), Diagnostics> {
let mut sources = schema.sources.clone();
sources.extend(self.source.clone());
let mut errors = Diagnostics::new(sources);
validation::validate_field_set(&mut errors, schema, self);
errors.into_result()
}
}

impl fmt::Display for SelectionPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.root {
Expand Down
2 changes: 1 addition & 1 deletion crates/apollo-compiler/src/executable/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl Node<Fragment> {
}

impl SelectionSet {
fn to_ast(&self) -> Vec<ast::Selection> {
pub(crate) fn to_ast(&self) -> Vec<ast::Selection> {
self.selections
.iter()
.map(|selection| match selection {
Expand Down
70 changes: 70 additions & 0 deletions crates/apollo-compiler/src/executable/validation.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use super::BuildError;
use super::FieldSet;
use crate::ast;
use crate::validation::Details;
use crate::validation::Diagnostics;
use crate::ExecutableDocument;
Expand Down Expand Up @@ -128,3 +130,71 @@ fn compiler_validation(
)
}
}

pub(crate) fn validate_field_set(errors: &mut Diagnostics, schema: &Schema, field_set: &FieldSet) {
if let Some((file_id, source)) = &field_set.source {
source.validate_parse_errors(errors, *file_id)
}
for build_error in &field_set.build_errors {
validate_build_error(errors, build_error)
}
let mut compiler = crate::ApolloCompiler::new();
let mut ids = Vec::new();
for (id, source) in &schema.sources {
ids.push(*id);
compiler.db.set_input(*id, source.into());
}
if let Some((id, source)) = &field_set.source {
ids.push(*id);
compiler.db.set_input(*id, source.into());
}

let schema_ast_id = FileId::HACK_TMP;
ids.push(schema_ast_id);
let mut ast = crate::ast::Document::new();
ast.definitions.extend(schema.to_ast());
compiler.db.set_input(
schema_ast_id,
crate::Source {
ty: crate::database::SourceType::Schema,
filename: Default::default(),
text: Default::default(),
ast: Some(Arc::new(ast)),
},
);

let ast_id = FileId::HACK_TMP_2;
ids.push(ast_id);
let ast = ast::Document::new();
compiler.db.set_input(
ast_id,
crate::Source {
ty: crate::database::SourceType::Executable,
filename: Default::default(),
text: Default::default(),
ast: Some(Arc::new(ast)),
},
);
compiler.db.set_source_files(ids);
let diagnostics = crate::validation::selection::validate_selection_set2(
&compiler.db,
ast_id,
Some(&field_set.selection_set.ty),
&field_set.selection_set.to_ast(),
crate::validation::operation::OperationValidationConfig {
has_schema: true,
variables: &[],
},
);
// if schema.is_some() {
// compiler.db.validate_executable(ast_id)
// } else {
// compiler.db.validate_standalone_executable(ast_id)
// };
for diagnostic in diagnostics {
errors.push(
Some(diagnostic.location),
Details::CompilerDiagnostic(diagnostic),
)
}
}
55 changes: 53 additions & 2 deletions crates/apollo-compiler/src/parser.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::ast;
use crate::ast::Document;
use crate::executable;
use crate::schema::SchemaBuilder;
use crate::validation::Details;
use crate::validation::Diagnostics;
Expand Down Expand Up @@ -84,22 +86,32 @@ impl Parser {
path: PathBuf,
file_id: FileId,
) -> Document {
let (tree, source_file) = self.parse_common(source_text, path, |parser| parser.parse());
Document::from_cst(tree.document(), file_id, source_file)
}

pub(crate) fn parse_common<T: apollo_parser::cst::CstNode>(
&mut self,
source_text: String,
path: PathBuf,
parse: impl FnOnce(apollo_parser::Parser) -> apollo_parser::SyntaxTree<T>,
) -> (apollo_parser::SyntaxTree<T>, Arc<SourceFile>) {
let mut parser = apollo_parser::Parser::new(&source_text);
if let Some(value) = self.recursion_limit {
parser = parser.recursion_limit(value)
}
if let Some(value) = self.token_limit {
parser = parser.token_limit(value)
}
let tree = parser.parse();
let tree = parse(parser);
self.recursion_reached = tree.recursion_limit().high;
self.tokens_reached = tree.token_limit().high;
let source_file = Arc::new(SourceFile {
path,
source_text,
parse_errors: tree.errors().cloned().collect(),
});
Document::from_cst(tree.document(), file_id, source_file)
(tree, source_file)
}

/// Parse the given source text as the sole input file of a schema.
Expand Down Expand Up @@ -171,6 +183,45 @@ impl Parser {
self.parse_ast(source_text, path).to_mixed()
}

/// Parse the given source a selection set with optional outer brackets.
///
/// `path` is the filesystem path (or arbitrary string) used in diagnostics
/// to identify this source file to users.
///
/// Parsing is fault-tolerant, so a selection set node is always returned.
/// TODO: document how to validate
pub fn parse_field_set(
&mut self,
schema: &Schema,
type_name: impl Into<ast::NamedType>,
source_text: impl Into<String>,
path: impl AsRef<Path>,
) -> executable::FieldSet {
let (tree, source_file) =
self.parse_common(source_text.into(), path.as_ref().to_owned(), |parser| {
parser.parse_selection_set()
});
let file_id = FileId::new();
let ast = ast::from_cst::convert_selection_set(&tree.field_set(), file_id);
let mut selection_set = executable::SelectionSet::new(type_name);
let mut build_errors = executable::from_ast::BuildErrors {
errors: Vec::new(),
path: executable::SelectionPath {
nested_fields: Vec::new(),
// 🤷
root: executable::ExecutableDefinitionName::AnonymousOperation(
ast::OperationType::Query,
),
},
};
selection_set.extend_from_ast(Some(schema), &mut build_errors, &ast);
executable::FieldSet {
source: Some((file_id, source_file)),
build_errors: build_errors.errors,
selection_set,
}
}

/// What level of recursion was reached during the last call to a `parse_*` method.
///
/// Collecting this on a corpus of documents can help decide
Expand Down
4 changes: 2 additions & 2 deletions crates/apollo-compiler/src/validation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ mod fragment;
mod input_object;
mod interface;
mod object;
mod operation;
pub(crate) mod operation;
mod scalar;
mod schema;
mod selection;
pub(crate) mod selection;
mod union_;
mod value;
mod variable;
Expand Down
2 changes: 1 addition & 1 deletion crates/apollo-compiler/src/validation/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
};

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct OperationValidationConfig<'vars> {
pub(crate) struct OperationValidationConfig<'vars> {
/// When false, rules that require a schema to validate are disabled.
pub has_schema: bool,
/// The variables defined for this operation.
Expand Down
Loading