Support service parsing in async-graphql-parser
- Instead of adding a separate module `schema` like there was before, since service parsing and executable parsing have a fair amount of overlap I put them as two submodules `executable` and `service` in both `parse` and `types`. Also, the grammar is unified under one `.pest` file. - Added const equivalents to `Value`, `Directive` etc - Change the reexport `async_graphql::Value` from `async_graphql_parser::types::Value` to `async_graphql_parser::types::ConstValue` since in 99% of cases in this library a const value is wanted instead of a value. - Added consistent usage of executable/service instead of the ambiguous query/schema. - Some of the tests actually had invalid GraphQL so the new more correct grammar made them fail, that was fixed. - Added a `Name` newtype to refer to GraphQL names (`[A-Za-z_][A-Za-z_0-9]*`) since they are used so frequently.
This commit is contained in:
parent
26f7b60bbd
commit
47259548c4
|
@ -118,7 +118,7 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
|
|||
#[#crate_name::async_trait::async_trait]
|
||||
impl #crate_name::OutputValueType for #ident {
|
||||
async fn resolve(&self, _: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
Ok(#crate_name::EnumType::to_value(self).into())
|
||||
Ok(#crate_name::EnumType::to_value(self).into_json().unwrap())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -111,7 +111,10 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
|
|||
}
|
||||
|
||||
put_fields.push(quote! {
|
||||
map.insert(#name.to_string(), #crate_name::InputValueType::to_value(&self.#ident));
|
||||
map.insert(
|
||||
#crate_name::parser::types::Name::new_unchecked(#name.to_owned()),
|
||||
#crate_name::InputValueType::to_value(&self.#ident)
|
||||
);
|
||||
});
|
||||
|
||||
fields.push(ident);
|
||||
|
|
|
@ -65,7 +65,7 @@ pub fn generate(scalar_args: &args::Scalar, item_impl: &mut ItemImpl) -> Result<
|
|||
_: &#crate_name::ContextSelectionSet<'_>,
|
||||
_field: &#crate_name::Positioned<#crate_name::parser::types::Field>
|
||||
) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
Ok(#crate_name::ScalarType::to_value(self).into())
|
||||
Ok(#crate_name::ScalarType::to_value(self).into_json().unwrap())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,28 +2,124 @@ WHITESPACE = _{ " " | "," | "\t" | "\u{feff}" | line_terminator }
|
|||
COMMENT = _{ "#" ~ (!line_terminator ~ ANY)* }
|
||||
line_terminator = @{ "\r\n" | "\r" | "\n" }
|
||||
|
||||
document = { SOI ~ definition+ ~ EOI }
|
||||
definition = { operation_definition | fragment_definition }
|
||||
// Executable //
|
||||
|
||||
executable_document = { SOI ~ executable_definition+ ~ EOI }
|
||||
executable_definition = { operation_definition | fragment_definition }
|
||||
|
||||
operation_definition = { named_operation_definition | selection_set }
|
||||
named_operation_definition = { operation_type ~ name? ~ variable_definitions? ~ directives? ~ selection_set }
|
||||
operation_type = { "query" | "mutation" | "subscription" }
|
||||
variable_definitions = { "(" ~ variable_definition* ~ ")" }
|
||||
variable_definition = { variable ~ ":" ~ type_ ~ default_value? }
|
||||
variable = { "$" ~ name }
|
||||
default_value = { "=" ~ value }
|
||||
|
||||
selection_set = { "{" ~ selection+ ~ "}" }
|
||||
selection = { field | inline_fragment | fragment_spread }
|
||||
field = { alias? ~ name ~ arguments? ~ directives? ~ selection_set? }
|
||||
alias = { name ~ ":" }
|
||||
fragment_spread = { "..." ~ name ~ directives? }
|
||||
inline_fragment = { "..." ~ type_condition? ~ directives? ~ selection_set }
|
||||
|
||||
fragment_definition = { "fragment" ~ name ~ type_condition ~ directives? ~ selection_set }
|
||||
type_condition = { "on" ~ name }
|
||||
|
||||
// Service //
|
||||
|
||||
service_document = { SOI ~ type_system_definition+ ~ EOI }
|
||||
type_system_definition = { schema_definition | type_definition | directive_definition }
|
||||
|
||||
schema_definition = {
|
||||
"schema" ~ const_directives? ~ "{" ~ operation_type_definition+ ~ "}"
|
||||
| extend ~ "schema" ~ (const_directives? ~ "{" ~ operation_type_definition+ ~ "}" | const_directives)
|
||||
}
|
||||
operation_type_definition = { operation_type ~ ":" ~ name }
|
||||
|
||||
type_definition = { scalar_type | object_type | interface_type | union_type | enum_type | input_object_type }
|
||||
|
||||
scalar_type = {
|
||||
string? ~ "scalar" ~ name ~ const_directives?
|
||||
| extend ~ "scalar" ~ name ~ const_directives
|
||||
}
|
||||
|
||||
object_type = {
|
||||
string? ~ "type" ~ name ~ implements_interfaces? ~ const_directives? ~ fields_definition?
|
||||
| extend ~ "type" ~ name ~ (implements_interfaces? ~ (const_directives? ~ fields_definition | const_directives) | implements_interfaces)
|
||||
}
|
||||
implements_interfaces = { "implements" ~ "&"? ~ name ~ ("&" ~ name)* }
|
||||
|
||||
interface_type = {
|
||||
string? ~ "interface" ~ name ~ const_directives? ~ fields_definition?
|
||||
| extend ~ "interface" ~ name ~ (const_directives? ~ fields_definition | const_directives)
|
||||
}
|
||||
|
||||
fields_definition = { "{" ~ field_definition+ ~ "}" }
|
||||
field_definition = { string? ~ name ~ arguments_definition? ~ ":" ~ type_ ~ const_directives? }
|
||||
|
||||
union_type = {
|
||||
string? ~ "union" ~ name ~ const_directives? ~ union_member_types?
|
||||
| extend ~ "union" ~ name ~ (const_directives? ~ union_member_types | const_directives)
|
||||
}
|
||||
union_member_types = { "=" ~ "|"? ~ name ~ ("|" ~ name)* }
|
||||
|
||||
enum_type = {
|
||||
string? ~ "enum" ~ name ~ const_directives? ~ enum_values?
|
||||
| extend ~ "enum" ~ name ~ (const_directives? ~ enum_values | const_directives)
|
||||
}
|
||||
enum_values = { "{" ~ enum_value_definition+ ~ "}" }
|
||||
enum_value_definition = { string? ~ enum_value ~ const_directives? }
|
||||
|
||||
input_object_type = {
|
||||
string? ~ "input" ~ name ~ const_directives? ~ input_fields_definition?
|
||||
| extend ~ "input" ~ name ~ (const_directives? ~ input_fields_definition | const_directives)
|
||||
}
|
||||
input_fields_definition = { "{" ~ input_value_definition+ ~ "}" }
|
||||
|
||||
extend = { "extend" }
|
||||
|
||||
directive_definition = { string? ~ "directive" ~ "@" ~ name ~ arguments_definition? ~ "on" ~ directive_locations }
|
||||
directive_locations = { "|"? ~ directive_location ~ ("|" ~ directive_location)* }
|
||||
directive_location = {
|
||||
"QUERY"
|
||||
| "MUTATION"
|
||||
| "SUBSCRIPTION"
|
||||
| "FIELD"
|
||||
| "FRAGMENT_DEFINITION"
|
||||
| "FRAGMENT_SPREAD"
|
||||
| "INLINE_FRAGMENT"
|
||||
| "SCHEMA"
|
||||
| "SCALAR"
|
||||
| "OBJECT"
|
||||
| "FIELD_DEFINITION"
|
||||
| "ARGUMENT_DEFINITION"
|
||||
| "INTERFACE"
|
||||
| "UNION"
|
||||
| "ENUM"
|
||||
| "ENUM_VALUE"
|
||||
| "INPUT_OBJECT"
|
||||
| "INPUT_FIELD_DEFINITION"
|
||||
}
|
||||
|
||||
arguments_definition = { "(" ~ input_value_definition+ ~ ")" }
|
||||
|
||||
input_value_definition = { string? ~ name ~ ":" ~ type_ ~ default_value? ~ const_directives? }
|
||||
|
||||
// Common //
|
||||
|
||||
operation_type = { "query" | "mutation" | "subscription" }
|
||||
|
||||
default_value = { "=" ~ const_value }
|
||||
|
||||
type_ = @{ (name | "[" ~ type_ ~ "]") ~ "!"? }
|
||||
|
||||
// float must come before int; other than that order does not matter
|
||||
value = { variable | float | int | string | boolean | null | enum_ | list | object }
|
||||
const_value = { number | string | boolean | null | enum_value | const_list | const_object }
|
||||
value = { variable | number | string | boolean | null | enum_value | list | object }
|
||||
|
||||
float = @{ int ~ ((fractional ~ exponent) | fractional | exponent) }
|
||||
variable = { "$" ~ name }
|
||||
|
||||
number = @{ float | int }
|
||||
float = { int ~ ((fractional ~ exponent) | fractional | exponent) }
|
||||
fractional = { "." ~ ASCII_DIGIT+ }
|
||||
exponent = { ("E" | "e") ~ ("+" | "-")? ~ ASCII_DIGIT+ }
|
||||
|
||||
int = @{ "-"? ~ ("0" | (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*)) }
|
||||
boolean = { "true" | "false" }
|
||||
int = { "-"? ~ ("0" | (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*)) }
|
||||
|
||||
string = ${ ("\"\"\"" ~ block_string_content ~ "\"\"\"") | ("\"" ~ string_content ~ "\"") }
|
||||
block_string_content = @{ block_string_character* }
|
||||
|
@ -43,27 +139,29 @@ string_character = {
|
|||
// slightly and disallow non-scalar value code points at the parsing level.
|
||||
unicode_scalar_value_hex = { !(^"d" ~ ('8'..'9' | 'a'..'f' | 'A'..'F')) ~ ASCII_HEX_DIGIT{4} }
|
||||
|
||||
boolean = { "true" | "false" }
|
||||
|
||||
null = { "null" }
|
||||
|
||||
enum_ = ${ !(boolean | null) ~ name }
|
||||
enum_value = ${ !(boolean | null) ~ name }
|
||||
|
||||
list = { "[" ~ value* ~ "]" }
|
||||
const_list = { "[" ~ const_value* ~ "]" }
|
||||
list = { "[" ~ value* ~ "]" }
|
||||
|
||||
name_value = { name ~ ":" ~ value }
|
||||
object = { "{" ~ name_value* ~ "}" }
|
||||
const_object = { "{" ~ const_object_field* ~ "}" }
|
||||
object = { "{" ~ object_field* ~ "}" }
|
||||
const_object_field = { name ~ ":" ~ const_value }
|
||||
object_field = { name ~ ":" ~ value }
|
||||
|
||||
selection_set = { "{" ~ selection+ ~ "}" }
|
||||
selection = { field | inline_fragment | fragment_spread }
|
||||
field = { alias? ~ name ~ arguments? ~ directives? ~ selection_set? }
|
||||
alias = { name ~ ":" }
|
||||
arguments = { "(" ~ name_value* ~ ")" }
|
||||
fragment_spread = { "..." ~ name ~ directives? }
|
||||
inline_fragment = { "..." ~ type_condition? ~ directives? ~ selection_set }
|
||||
|
||||
fragment_definition = { "fragment" ~ name ~ type_condition ~ directives? ~ selection_set }
|
||||
type_condition = { "on" ~ name }
|
||||
const_directives = { const_directive+ }
|
||||
directives = { directive+ }
|
||||
const_directive = { "@" ~ name ~ const_arguments? }
|
||||
directive = { "@" ~ name ~ arguments? }
|
||||
|
||||
directives = { directive* }
|
||||
directive = { "@" ~ name ~ arguments? }
|
||||
const_arguments = { "(" ~ const_argument+ ~ ")" }
|
||||
arguments = { "(" ~ argument+ ~ ")" }
|
||||
const_argument = { name ~ ":" ~ const_value }
|
||||
argument = { name ~ ":" ~ value }
|
||||
|
||||
name = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHA | ASCII_DIGIT | "_")* }
|
||||
|
|
|
@ -9,14 +9,13 @@ use pest::error::LineColLocation;
|
|||
use pest::RuleType;
|
||||
use std::fmt;
|
||||
|
||||
pub use parser::parse_query;
|
||||
pub use parse::{parse_query, parse_schema};
|
||||
pub use pos::{Pos, Positioned};
|
||||
|
||||
pub mod types;
|
||||
|
||||
mod parser;
|
||||
mod parse;
|
||||
mod pos;
|
||||
mod utils;
|
||||
|
||||
/// Parser error.
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -27,6 +26,16 @@ pub struct Error {
|
|||
pub message: String,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Create a new error with the given position and message.
|
||||
pub fn new(message: impl Into<String>, pos: Pos) -> Self {
|
||||
Self {
|
||||
pos,
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.message)
|
||||
|
|
312
async-graphql-parser/src/parse/executable.rs
Normal file
312
async-graphql-parser/src/parse/executable.rs
Normal file
|
@ -0,0 +1,312 @@
|
|||
use super::*;
|
||||
|
||||
/// Parse a GraphQL query document.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Fails if the query is not a valid GraphQL document.
|
||||
pub fn parse_query<T: AsRef<str>>(input: T) -> Result<ExecutableDocument> {
|
||||
let mut pc = PositionCalculator::new(input.as_ref());
|
||||
Ok(parse_executable_document(
|
||||
exactly_one(GraphQLParser::parse(Rule::executable_document, input.as_ref())?),
|
||||
&mut pc,
|
||||
)?)
|
||||
}
|
||||
|
||||
fn parse_executable_document(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<ExecutableDocument> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::executable_document);
|
||||
|
||||
Ok(ExecutableDocument {
|
||||
definitions: pair
|
||||
.into_inner()
|
||||
.filter(|pair| pair.as_rule() != Rule::EOI)
|
||||
.map(|pair| parse_executable_definition(pair, pc))
|
||||
.collect::<Result<_>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_executable_definition(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<ExecutableDefinition> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::executable_definition);
|
||||
|
||||
let pair = exactly_one(pair.into_inner());
|
||||
Ok(match pair.as_rule() {
|
||||
Rule::operation_definition => ExecutableDefinition::Operation(parse_operation_definition(pair, pc)?),
|
||||
Rule::fragment_definition => ExecutableDefinition::Fragment(parse_fragment_definition(pair, pc)?),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_operation_definition(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Result<Positioned<OperationDefinition>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::operation_definition);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let pair = exactly_one(pair.into_inner());
|
||||
Ok(Positioned::new(
|
||||
match pair.as_rule() {
|
||||
Rule::named_operation_definition => parse_named_operation_definition(pair, pc)?,
|
||||
Rule::selection_set => OperationDefinition {
|
||||
ty: OperationType::Query,
|
||||
name: None,
|
||||
variable_definitions: Vec::new(),
|
||||
directives: Vec::new(),
|
||||
selection_set: parse_selection_set(pair, pc)?,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
},
|
||||
pos,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_named_operation_definition(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Result<OperationDefinition> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::named_operation_definition);
|
||||
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let ty = parse_operation_type(pairs.next().unwrap(), pc)?;
|
||||
let name = parse_if_rule(&mut pairs, Rule::name, |pair| parse_name(pair, pc))?;
|
||||
let variable_definitions = parse_if_rule(&mut pairs, Rule::variable_definitions, |pair| parse_variable_definitions(pair, pc))?;
|
||||
let directives = parse_opt_directives(&mut pairs, pc)?;
|
||||
let selection_set = parse_selection_set(pairs.next().unwrap(), pc)?;
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok(OperationDefinition {
|
||||
ty: ty.node,
|
||||
name,
|
||||
variable_definitions: variable_definitions.unwrap_or_default(),
|
||||
directives,
|
||||
selection_set,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_variable_definitions(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Result<Vec<Positioned<VariableDefinition>>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::variable_definitions);
|
||||
|
||||
pair.into_inner()
|
||||
.map(|pair| parse_variable_definition(pair, pc))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_variable_definition(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Result<Positioned<VariableDefinition>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::variable_definition);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let variable = parse_variable(pairs.next().unwrap(), pc)?;
|
||||
let var_type = parse_type(pairs.next().unwrap(), pc)?;
|
||||
let default_value = parse_if_rule(&mut pairs, Rule::default_value, |pair| parse_default_value(pair, pc))?;
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok(Positioned::new(
|
||||
VariableDefinition {
|
||||
name: variable,
|
||||
var_type,
|
||||
default_value,
|
||||
},
|
||||
pos,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_selection_set(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<SelectionSet>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::selection_set);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
|
||||
Ok(Positioned::new(
|
||||
SelectionSet {
|
||||
items: pair
|
||||
.into_inner()
|
||||
.map(|pair| parse_selection(pair, pc))
|
||||
.collect::<Result<_>>()?,
|
||||
},
|
||||
pos,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_selection(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Selection>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::selection);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let pair = exactly_one(pair.into_inner());
|
||||
|
||||
Ok(Positioned::new(
|
||||
match pair.as_rule() {
|
||||
Rule::field => Selection::Field(parse_field(pair, pc)?),
|
||||
Rule::fragment_spread => Selection::FragmentSpread(parse_fragment_spread(pair, pc)?),
|
||||
Rule::inline_fragment => Selection::InlineFragment(parse_inline_fragment(pair, pc)?),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
pos,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_field(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Field>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::field);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let alias = parse_if_rule(&mut pairs, Rule::alias, |pair| parse_alias(pair, pc))?;
|
||||
let name = parse_name(pairs.next().unwrap(), pc)?;
|
||||
let arguments = parse_if_rule(&mut pairs, Rule::arguments, |pair| parse_arguments(pair, pc))?;
|
||||
let directives = parse_opt_directives(&mut pairs, pc)?;
|
||||
let selection_set =
|
||||
parse_if_rule(&mut pairs, Rule::selection_set, |pair| parse_selection_set(pair, pc))?;
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok(Positioned::new(
|
||||
Field {
|
||||
alias,
|
||||
name,
|
||||
arguments: arguments.unwrap_or_default(),
|
||||
directives,
|
||||
selection_set: selection_set.unwrap_or_default(),
|
||||
},
|
||||
pos,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_alias(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Name>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::alias);
|
||||
parse_name(exactly_one(pair.into_inner()), pc)
|
||||
}
|
||||
|
||||
|
||||
fn parse_fragment_spread(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Result<Positioned<FragmentSpread>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::fragment_spread);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let fragment_name = parse_name(pairs.next().unwrap(), pc)?;
|
||||
let directives = parse_opt_directives(&mut pairs, pc)?;
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok(Positioned::new(
|
||||
FragmentSpread {
|
||||
fragment_name,
|
||||
directives,
|
||||
},
|
||||
pos,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_inline_fragment(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Result<Positioned<InlineFragment>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::inline_fragment);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let type_condition =
|
||||
parse_if_rule(&mut pairs, Rule::type_condition, |pair| parse_type_condition(pair, pc))?;
|
||||
let directives = parse_opt_directives(&mut pairs, pc)?;
|
||||
let selection_set = parse_selection_set(pairs.next().unwrap(), pc)?;
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok(Positioned::new(
|
||||
InlineFragment {
|
||||
type_condition,
|
||||
directives,
|
||||
selection_set,
|
||||
},
|
||||
pos,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_fragment_definition(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Result<Positioned<FragmentDefinition>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::fragment_definition);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let name = parse_name(pairs.next().unwrap(), pc)?;
|
||||
let type_condition = parse_type_condition(pairs.next().unwrap(), pc)?;
|
||||
let directives = parse_opt_directives(&mut pairs, pc)?;
|
||||
let selection_set = parse_selection_set(pairs.next().unwrap(), pc)?;
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok(Positioned::new(
|
||||
FragmentDefinition {
|
||||
name,
|
||||
type_condition,
|
||||
directives,
|
||||
selection_set,
|
||||
},
|
||||
pos,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_type_condition(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Result<Positioned<TypeCondition>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::type_condition);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
Ok(Positioned::new(
|
||||
TypeCondition {
|
||||
on: parse_name(exactly_one(pair.into_inner()), pc)?,
|
||||
},
|
||||
pos,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
for entry in fs::read_dir("tests/executables").unwrap() {
|
||||
if let Ok(entry) = entry {
|
||||
GraphQLParser::parse(Rule::executable_document, &fs::read_to_string(entry.path()).unwrap())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser_ast() {
|
||||
for entry in fs::read_dir("tests/executables").unwrap() {
|
||||
if let Ok(entry) = entry {
|
||||
parse_query(fs::read_to_string(entry.path()).unwrap()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_overflowing_int() {
|
||||
let query_ok = format!("mutation {{ add(big: {}) }} ", std::i32::MAX);
|
||||
let query_overflow = format!("mutation {{ add(big: {}0000) }} ", std::i32::MAX);
|
||||
assert!(parse_query(query_ok).is_ok());
|
||||
assert!(parse_query(query_overflow).is_ok());
|
||||
}
|
||||
}
|
269
async-graphql-parser/src/parse/mod.rs
Normal file
269
async-graphql-parser/src/parse/mod.rs
Normal file
|
@ -0,0 +1,269 @@
|
|||
//! Parsing module.
|
||||
//!
|
||||
//! This module's structure mirrors `types`.
|
||||
|
||||
use crate::pos::{Positioned, PositionCalculator};
|
||||
use crate::types::*;
|
||||
use crate::{Result, Error};
|
||||
use pest::iterators::{Pair, Pairs};
|
||||
use pest::Parser;
|
||||
use pest_derive::Parser;
|
||||
use utils::*;
|
||||
|
||||
mod executable;
|
||||
mod service;
|
||||
mod utils;
|
||||
|
||||
pub use executable::parse_query;
|
||||
pub use service::parse_schema;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[grammar = "graphql.pest"]
|
||||
struct GraphQLParser;
|
||||
|
||||
fn parse_operation_type(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Result<Positioned<OperationType>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::operation_type);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
|
||||
Ok(Positioned::new(
|
||||
match pair.as_str() {
|
||||
"query" => OperationType::Query,
|
||||
"mutation" => OperationType::Mutation,
|
||||
"subscription" => OperationType::Subscription,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
pos,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_default_value(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<ConstValue>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::default_value);
|
||||
|
||||
parse_const_value(exactly_one(pair.into_inner()), pc)
|
||||
}
|
||||
|
||||
fn parse_type(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Type>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::type_);
|
||||
|
||||
Ok(Positioned::new(Type::new(pair.as_str()).unwrap(), pc.step(&pair)))
|
||||
}
|
||||
|
||||
fn parse_const_value(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<ConstValue>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::const_value);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let pair = exactly_one(pair.into_inner());
|
||||
|
||||
Ok(Positioned::new(
|
||||
match pair.as_rule() {
|
||||
Rule::number => ConstValue::Number(parse_number(pair, pc)?.node),
|
||||
Rule::string => ConstValue::String(parse_string(pair, pc)?.node),
|
||||
Rule::boolean => ConstValue::Boolean(parse_boolean(pair, pc)?.node),
|
||||
Rule::null => ConstValue::Null,
|
||||
Rule::enum_value => ConstValue::Enum(parse_enum_value(pair, pc)?.node),
|
||||
Rule::const_list => ConstValue::List(
|
||||
pair.into_inner()
|
||||
.map(|pair| Ok(parse_const_value(pair, pc)?.node))
|
||||
.collect::<Result<_>>()?,
|
||||
),
|
||||
Rule::const_object => ConstValue::Object(
|
||||
pair.into_inner()
|
||||
.map(|pair| {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::const_object_field);
|
||||
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let name = parse_name(pairs.next().unwrap(), pc)?;
|
||||
let value = parse_const_value(pairs.next().unwrap(), pc)?;
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok((name.node, value.node))
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
pos,
|
||||
))
|
||||
}
|
||||
fn parse_value(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Value>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::value);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let pair = exactly_one(pair.into_inner());
|
||||
|
||||
Ok(Positioned::new(
|
||||
match pair.as_rule() {
|
||||
Rule::variable => Value::Variable(parse_variable(pair, pc)?.node),
|
||||
Rule::number => Value::Number(parse_number(pair, pc)?.node),
|
||||
Rule::string => Value::String(parse_string(pair, pc)?.node),
|
||||
Rule::boolean => Value::Boolean(parse_boolean(pair, pc)?.node),
|
||||
Rule::null => Value::Null,
|
||||
Rule::enum_value => Value::Enum(parse_enum_value(pair, pc)?.node),
|
||||
Rule::list => Value::List(
|
||||
pair.into_inner()
|
||||
.map(|pair| Ok(parse_value(pair, pc)?.node))
|
||||
.collect::<Result<_>>()?,
|
||||
),
|
||||
Rule::object => Value::Object(
|
||||
pair.into_inner()
|
||||
.map(|pair| {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::object_field);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let name = parse_name(pairs.next().unwrap(), pc)?;
|
||||
let value = parse_value(pairs.next().unwrap(), pc)?;
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok((name.node, value.node))
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
pos,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_variable(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Name>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::variable);
|
||||
parse_name(exactly_one(pair.into_inner()), pc)
|
||||
}
|
||||
fn parse_number(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Number>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::number);
|
||||
let pos = pc.step(&pair);
|
||||
Ok(Positioned::new(pair.as_str().parse().expect("failed to parse number"), pos))
|
||||
}
|
||||
fn parse_string(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<String>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::string);
|
||||
let pos = pc.step(&pair);
|
||||
let pair = exactly_one(pair.into_inner());
|
||||
Ok(Positioned::new(match pair.as_rule() {
|
||||
Rule::block_string_content => block_string_value(pair.as_str()),
|
||||
Rule::string_content => string_value(pair.as_str()),
|
||||
_ => unreachable!(),
|
||||
}, pos))
|
||||
}
|
||||
fn parse_boolean(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<bool>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::boolean);
|
||||
let pos = pc.step(&pair);
|
||||
Ok(Positioned::new(match pair.as_str() {
|
||||
"true" => true,
|
||||
"false" => false,
|
||||
_ => unreachable!(),
|
||||
}, pos))
|
||||
}
|
||||
fn parse_enum_value(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Name>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::enum_value);
|
||||
parse_name(exactly_one(pair.into_inner()), pc)
|
||||
}
|
||||
|
||||
fn parse_opt_const_directives<'a>(pairs: &mut Pairs<'a, Rule>, pc: &mut PositionCalculator) -> Result<Vec<Positioned<ConstDirective>>> {
|
||||
Ok(parse_if_rule(pairs, Rule::const_directives, |pair| parse_const_directives(pair, pc))?.unwrap_or_default())
|
||||
}
|
||||
fn parse_opt_directives<'a>(pairs: &mut Pairs<'a, Rule>, pc: &mut PositionCalculator) -> Result<Vec<Positioned<Directive>>> {
|
||||
Ok(parse_if_rule(pairs, Rule::directives, |pair| parse_directives(pair, pc))?.unwrap_or_default())
|
||||
}
|
||||
fn parse_const_directives(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Vec<Positioned<ConstDirective>>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::const_directives);
|
||||
|
||||
pair.into_inner()
|
||||
.map(|pair| parse_const_directive(pair, pc))
|
||||
.collect()
|
||||
}
|
||||
fn parse_directives(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Vec<Positioned<Directive>>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::directives);
|
||||
|
||||
pair.into_inner()
|
||||
.map(|pair| parse_directive(pair, pc))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_const_directive(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<ConstDirective>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::const_directive);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let name = parse_name(pairs.next().unwrap(), pc)?;
|
||||
let arguments = parse_if_rule(&mut pairs, Rule::const_arguments, |pair| parse_const_arguments(pair, pc))?;
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok(Positioned::new(
|
||||
ConstDirective {
|
||||
name,
|
||||
arguments: arguments.unwrap_or_default(),
|
||||
},
|
||||
pos,
|
||||
))
|
||||
}
|
||||
fn parse_directive(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Directive>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::directive);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let name = parse_name(pairs.next().unwrap(), pc)?;
|
||||
let arguments = parse_if_rule(&mut pairs, Rule::arguments, |pair| parse_arguments(pair, pc))?;
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok(Positioned::new(
|
||||
Directive {
|
||||
name,
|
||||
arguments: arguments.unwrap_or_default(),
|
||||
},
|
||||
pos,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_const_arguments(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Result<Vec<(Positioned<Name>, Positioned<ConstValue>)>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::const_arguments);
|
||||
pair.into_inner()
|
||||
.map(|pair| {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::const_argument);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let name = parse_name(pairs.next().unwrap(), pc)?;
|
||||
let value = parse_const_value(pairs.next().unwrap(), pc)?;
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok((name, value))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
fn parse_arguments(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Result<Vec<(Positioned<Name>, Positioned<Value>)>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::arguments);
|
||||
pair.into_inner()
|
||||
.map(|pair| {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::argument);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let name = parse_name(pairs.next().unwrap(), pc)?;
|
||||
let value = parse_value(pairs.next().unwrap(), pc)?;
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok((name, value))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_name(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Name>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::name);
|
||||
Ok(Positioned::new(Name::new_unchecked(pair.as_str().to_owned()), pc.step(&pair)))
|
||||
}
|
331
async-graphql-parser/src/parse/service.rs
Normal file
331
async-graphql-parser/src/parse/service.rs
Normal file
|
@ -0,0 +1,331 @@
|
|||
use super::*;
|
||||
|
||||
/// Parse a GraphQL schema document.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Fails if the schema is not a valid GraphQL document.
|
||||
pub fn parse_schema<T: AsRef<str>>(input: T) -> Result<ServiceDocument> {
|
||||
let mut pc = PositionCalculator::new(input.as_ref());
|
||||
Ok(parse_service_document(
|
||||
exactly_one(GraphQLParser::parse(Rule::service_document, input.as_ref())?),
|
||||
&mut pc,
|
||||
)?)
|
||||
}
|
||||
|
||||
fn parse_service_document(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<ServiceDocument> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::service_document);
|
||||
|
||||
Ok(ServiceDocument {
|
||||
definitions: pair
|
||||
.into_inner()
|
||||
.filter(|pair| pair.as_rule() != Rule::EOI)
|
||||
.map(|pair| parse_type_system_definition(pair, pc))
|
||||
.collect::<Result<_>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_type_system_definition(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<TypeSystemDefinition> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::type_system_definition);
|
||||
|
||||
let pair = exactly_one(pair.into_inner());
|
||||
Ok(match pair.as_rule() {
|
||||
Rule::schema_definition => TypeSystemDefinition::Schema(parse_schema_definition(pair, pc)?),
|
||||
Rule::type_definition => TypeSystemDefinition::Type(parse_type_definition(pair, pc)?),
|
||||
Rule::directive_definition => TypeSystemDefinition::Directive(parse_directive_definition(pair, pc)?),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_schema_definition(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<SchemaDefinition>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::schema_definition);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let extend = next_if_rule(&mut pairs, Rule::extend).is_some();
|
||||
let directives = parse_opt_const_directives(&mut pairs, pc)?;
|
||||
|
||||
let mut query = None;
|
||||
let mut mutation = None;
|
||||
let mut subscription = None;
|
||||
|
||||
for pair in pairs {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::operation_type_definition);
|
||||
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let operation_type = parse_operation_type(pairs.next().unwrap(), pc)?;
|
||||
let name = parse_name(pairs.next().unwrap(), pc)?;
|
||||
|
||||
match operation_type.node {
|
||||
OperationType::Query => {
|
||||
if query.is_some() {
|
||||
return Err(operation_type.error_here("multiple query roots"));
|
||||
}
|
||||
query = Some(name);
|
||||
}
|
||||
OperationType::Mutation => {
|
||||
if mutation.is_some() {
|
||||
return Err(operation_type.error_here("multiple mutation roots"));
|
||||
}
|
||||
mutation = Some(name);
|
||||
}
|
||||
OperationType::Subscription => {
|
||||
if subscription.is_some() {
|
||||
return Err(operation_type.error_here("multiple subscription roots"));
|
||||
}
|
||||
subscription = Some(name);
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
}
|
||||
|
||||
if !extend && query.is_none() {
|
||||
return Err(Error::new("missing query root", pos));
|
||||
}
|
||||
|
||||
Ok(Positioned::new(SchemaDefinition {
|
||||
extend,
|
||||
directives,
|
||||
query,
|
||||
mutation,
|
||||
subscription,
|
||||
}, pos))
|
||||
}
|
||||
|
||||
fn parse_type_definition(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<TypeDefinition>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::type_definition);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let pair = exactly_one(pair.into_inner());
|
||||
let rule = pair.as_rule();
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let description = parse_if_rule(&mut pairs, Rule::string, |pair| parse_string(pair, pc))?;
|
||||
let extend = next_if_rule(&mut pairs, Rule::extend).is_some();
|
||||
let name = parse_name(pairs.next().unwrap(), pc)?;
|
||||
|
||||
let (directives, kind) = match rule {
|
||||
Rule::scalar_type => {
|
||||
let directives = parse_opt_const_directives(&mut pairs, pc)?;
|
||||
(directives, TypeKind::Scalar)
|
||||
}
|
||||
Rule::object_type => {
|
||||
let implements = parse_if_rule(&mut pairs, Rule::implements_interfaces, |pair| {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::implements_interfaces);
|
||||
|
||||
pair.into_inner()
|
||||
.map(|pair| parse_name(pair, pc))
|
||||
.collect::<Result<_>>()
|
||||
})?;
|
||||
|
||||
let directives = parse_opt_const_directives(&mut pairs, pc)?;
|
||||
|
||||
let fields = parse_if_rule(&mut pairs, Rule::fields_definition, |pair| parse_fields_definition(pair, pc))?.unwrap_or_default();
|
||||
|
||||
(directives, TypeKind::Object(ObjectType {
|
||||
implements: implements.unwrap_or_default(),
|
||||
fields,
|
||||
}))
|
||||
}
|
||||
Rule::interface_type => {
|
||||
let directives = parse_opt_const_directives(&mut pairs, pc)?;
|
||||
let fields = parse_if_rule(&mut pairs, Rule::fields_definition, |pair| parse_fields_definition(pair, pc))?.unwrap_or_default();
|
||||
(directives, TypeKind::Interface(InterfaceType { fields }))
|
||||
}
|
||||
Rule::union_type => {
|
||||
let directives = parse_opt_const_directives(&mut pairs, pc)?;
|
||||
let members = parse_if_rule(&mut pairs, Rule::union_member_types, |pair| {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::union_member_types);
|
||||
|
||||
pair.into_inner()
|
||||
.map(|pair| parse_name(pair, pc))
|
||||
.collect()
|
||||
})?.unwrap_or_default();
|
||||
(directives, TypeKind::Union(UnionType { members }))
|
||||
}
|
||||
Rule::enum_type => {
|
||||
let directives = parse_opt_const_directives(&mut pairs, pc)?;
|
||||
let values = parse_if_rule(&mut pairs, Rule::enum_values, |pair| {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::enum_values);
|
||||
|
||||
pair.into_inner()
|
||||
.map(|pair| {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::enum_value_definition);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let description = parse_if_rule(&mut pairs, Rule::string, |pair| parse_string(pair, pc))?;
|
||||
let value = parse_enum_value(pairs.next().unwrap(), pc)?;
|
||||
let directives = parse_opt_const_directives(&mut pairs, pc)?;
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok(Positioned::new(EnumValueDefinition {
|
||||
description,
|
||||
value,
|
||||
directives,
|
||||
}, pos))
|
||||
})
|
||||
.collect()
|
||||
})?.unwrap_or_default();
|
||||
(directives, TypeKind::Enum(EnumType { values }))
|
||||
}
|
||||
Rule::input_object_type => {
|
||||
let directives = parse_opt_const_directives(&mut pairs, pc)?;
|
||||
let fields = parse_if_rule(&mut pairs, Rule::input_fields_definition, |pair| {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::input_fields_definition);
|
||||
|
||||
pair
|
||||
.into_inner()
|
||||
.map(|pair| parse_input_value_definition(pair, pc))
|
||||
.collect()
|
||||
})?.unwrap_or_default();
|
||||
|
||||
(directives, TypeKind::InputObject(InputObjectType { fields }))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok(Positioned::new(TypeDefinition {
|
||||
extend,
|
||||
description,
|
||||
name,
|
||||
directives,
|
||||
kind,
|
||||
}, pos))
|
||||
}
|
||||
|
||||
fn parse_fields_definition(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Vec<Positioned<FieldDefinition>>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::fields_definition);
|
||||
|
||||
pair.into_inner().map(|pair| parse_field_definition(pair, pc)).collect()
|
||||
}
|
||||
|
||||
fn parse_field_definition(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<FieldDefinition>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::field_definition);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let description = parse_if_rule(&mut pairs, Rule::string, |pair| parse_string(pair, pc))?;
|
||||
let name = parse_name(pairs.next().unwrap(), pc)?;
|
||||
let arguments = parse_if_rule(&mut pairs, Rule::arguments_definition, |pair| parse_arguments_definition(pair, pc))?.unwrap_or_default();
|
||||
let ty = parse_type(pairs.next().unwrap(), pc)?;
|
||||
let directives = parse_opt_const_directives(&mut pairs, pc)?;
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok(Positioned::new(FieldDefinition { description, name, arguments, ty, directives }, pos))
|
||||
}
|
||||
|
||||
fn parse_directive_definition(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<DirectiveDefinition>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::directive_definition);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let description = parse_if_rule(&mut pairs, Rule::string, |pair| parse_string(pair, pc))?;
|
||||
let name = parse_name(pairs.next().unwrap(), pc)?;
|
||||
let arguments = parse_if_rule(&mut pairs, Rule::arguments_definition, |pair| {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::arguments_definition);
|
||||
pair
|
||||
.into_inner()
|
||||
.map(|pair| parse_input_value_definition(pair, pc))
|
||||
.collect()
|
||||
})?.unwrap_or_default();
|
||||
let locations = {
|
||||
let pair = pairs.next().unwrap();
|
||||
debug_assert_eq!(pair.as_rule(), Rule::directive_locations);
|
||||
pair
|
||||
.into_inner()
|
||||
.map(|pair| {
|
||||
let pos = pc.step(&pair);
|
||||
debug_assert_eq!(pair.as_rule(), Rule::directive_location);
|
||||
Positioned::new(match pair.as_str() {
|
||||
"QUERY" => DirectiveLocation::Query,
|
||||
"MUTATION" => DirectiveLocation::Mutation,
|
||||
"SUBSCRIPTION" => DirectiveLocation::Subscription,
|
||||
"FIELD" => DirectiveLocation::Field,
|
||||
"FRAGMENT_DEFINITION" => DirectiveLocation::FragmentDefinition,
|
||||
"FRAGMENT_SPREAD" => DirectiveLocation::FragmentSpread,
|
||||
"INLINE_FRAGMENT" => DirectiveLocation::InlineFragment,
|
||||
"SCHEMA" => DirectiveLocation::Schema,
|
||||
"SCALAR" => DirectiveLocation::Scalar,
|
||||
"OBJECT" => DirectiveLocation::Object,
|
||||
"FIELD_DEFINITION" => DirectiveLocation::FieldDefinition,
|
||||
"ARGUMENT_DEFINITION" => DirectiveLocation::ArgumentDefinition,
|
||||
"INTERFACE" => DirectiveLocation::Interface,
|
||||
"UNION" => DirectiveLocation::Union,
|
||||
"ENUM" => DirectiveLocation::Enum,
|
||||
"ENUM_VALUE" => DirectiveLocation::EnumValue,
|
||||
"INPUT_OBJECT" => DirectiveLocation::InputObject,
|
||||
"INPUT_FIELD_DEFINITION" => DirectiveLocation::InputFieldDefinition,
|
||||
_ => unreachable!(),
|
||||
}, pos)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Ok(Positioned::new(DirectiveDefinition {
|
||||
description,
|
||||
name,
|
||||
arguments,
|
||||
locations,
|
||||
}, pos))
|
||||
}
|
||||
|
||||
fn parse_arguments_definition(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Vec<Positioned<InputValueDefinition>>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::arguments_definition);
|
||||
|
||||
pair.into_inner()
|
||||
.map(|pair| parse_input_value_definition(pair, pc))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_input_value_definition(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<InputValueDefinition>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::input_value_definition);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let description = parse_if_rule(&mut pairs, Rule::string, |pair| parse_string(pair, pc))?;
|
||||
let name = parse_name(pairs.next().unwrap(), pc)?;
|
||||
let ty = parse_type(pairs.next().unwrap(), pc)?;
|
||||
let default_value = parse_if_rule(&mut pairs, Rule::default_value, |pair| parse_default_value(pair, pc))?;
|
||||
let directives = parse_opt_const_directives(&mut pairs, pc)?;
|
||||
|
||||
Ok(Positioned::new(InputValueDefinition { description, name, ty, default_value, directives }, pos))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
for entry in fs::read_dir("tests/services").unwrap() {
|
||||
if let Ok(entry) = entry {
|
||||
GraphQLParser::parse(Rule::service_document, &fs::read_to_string(entry.path()).unwrap())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser_ast() {
|
||||
for entry in fs::read_dir("tests/services").unwrap() {
|
||||
if let Ok(entry) = entry {
|
||||
parse_schema(fs::read_to_string(entry.path()).unwrap()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,53 +1,27 @@
|
|||
use crate::Pos;
|
||||
use pest::iterators::Pair;
|
||||
use pest::RuleType;
|
||||
use std::str::Chars;
|
||||
use super::Rule;
|
||||
use crate::Result;
|
||||
use pest::iterators::{Pair, Pairs};
|
||||
|
||||
pub struct PositionCalculator<'a> {
|
||||
input: Chars<'a>,
|
||||
pos: usize,
|
||||
line: usize,
|
||||
column: usize,
|
||||
}
|
||||
|
||||
impl<'a> PositionCalculator<'a> {
|
||||
pub fn new(input: &'a str) -> PositionCalculator<'a> {
|
||||
Self {
|
||||
input: input.chars(),
|
||||
pos: 0,
|
||||
line: 1,
|
||||
column: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn step<R: RuleType>(&mut self, pair: &Pair<R>) -> Pos {
|
||||
let pos = pair.as_span().start();
|
||||
debug_assert!(pos >= self.pos);
|
||||
for _ in 0..pos - self.pos {
|
||||
match self.input.next() {
|
||||
Some('\r') => {
|
||||
self.column = 1;
|
||||
}
|
||||
Some('\n') => {
|
||||
self.line += 1;
|
||||
self.column = 1;
|
||||
}
|
||||
Some(_) => {
|
||||
self.column += 1;
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
self.pos = pos;
|
||||
Pos {
|
||||
line: self.line,
|
||||
column: self.column,
|
||||
}
|
||||
pub(super) fn next_if_rule<'a>(pairs: &mut Pairs<'a, Rule>, rule: Rule) -> Option<Pair<'a, Rule>> {
|
||||
if pairs.peek().map_or(false, |pair| pair.as_rule() == rule) {
|
||||
Some(pairs.next().unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub(super) fn parse_if_rule<'a, T>(pairs: &mut Pairs<'a, Rule>, rule: Rule, f: impl FnOnce(Pair<Rule>) -> Result<T>) -> Result<Option<T>> {
|
||||
next_if_rule(pairs, rule).map(f).transpose()
|
||||
}
|
||||
|
||||
// See https://spec.graphql.org/June2018/#BlockStringValue()
|
||||
pub(crate) fn block_string_value(raw: &str) -> String {
|
||||
pub(super) fn exactly_one<T>(iter: impl IntoIterator<Item = T>) -> T {
|
||||
let mut iter = iter.into_iter();
|
||||
let res = iter.next().unwrap();
|
||||
debug_assert!(matches!(iter.next(), None));
|
||||
res
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn block_string_value(raw: &str) -> String {
|
||||
// Split the string by either \r\n, \r or \n
|
||||
let lines: Vec<_> = raw
|
||||
.split("\r\n")
|
||||
|
@ -111,7 +85,7 @@ fn test_block_string_value() {
|
|||
);
|
||||
}
|
||||
|
||||
pub(crate) fn string_value(s: &str) -> String {
|
||||
pub(super) fn string_value(s: &str) -> String {
|
||||
let mut chars = s.chars();
|
||||
|
||||
std::iter::from_fn(|| {
|
|
@ -1,482 +0,0 @@
|
|||
use crate::pos::Positioned;
|
||||
use crate::types::*;
|
||||
use crate::utils::{block_string_value, string_value, PositionCalculator};
|
||||
use crate::Result;
|
||||
use pest::iterators::{Pair, Pairs};
|
||||
use pest::Parser;
|
||||
use pest_derive::Parser;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[grammar = "graphql.pest"]
|
||||
struct GraphQLParser;
|
||||
|
||||
/// Parse a GraphQL query.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Fails if the query is not a valid GraphQL document.
|
||||
pub fn parse_query<T: AsRef<str>>(input: T) -> Result<Document> {
|
||||
let mut pc = PositionCalculator::new(input.as_ref());
|
||||
Ok(parse_document(
|
||||
exactly_one(GraphQLParser::parse(Rule::document, input.as_ref())?),
|
||||
&mut pc,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_document(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Document {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::document);
|
||||
|
||||
Document {
|
||||
definitions: pair
|
||||
.into_inner()
|
||||
.filter(|pair| pair.as_rule() != Rule::EOI)
|
||||
.map(|pair| parse_definition(pair, pc))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_definition(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Definition {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::definition);
|
||||
|
||||
let pair = exactly_one(pair.into_inner());
|
||||
match pair.as_rule() {
|
||||
Rule::operation_definition => Definition::Operation(parse_operation_definition(pair, pc)),
|
||||
Rule::fragment_definition => Definition::Fragment(parse_fragment_definition(pair, pc)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_operation_definition(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Positioned<OperationDefinition> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::operation_definition);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let pair = exactly_one(pair.into_inner());
|
||||
Positioned::new(
|
||||
match pair.as_rule() {
|
||||
Rule::named_operation_definition => parse_named_operation_definition(pair, pc),
|
||||
Rule::selection_set => OperationDefinition {
|
||||
ty: OperationType::Query,
|
||||
name: None,
|
||||
variable_definitions: Vec::new(),
|
||||
directives: Vec::new(),
|
||||
selection_set: parse_selection_set(pair, pc),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
},
|
||||
pos,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_named_operation_definition(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> OperationDefinition {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::named_operation_definition);
|
||||
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let ty = parse_operation_type(&pairs.next().unwrap(), pc);
|
||||
let name = next_if_rule(&mut pairs, Rule::name).map(|pair| parse_name(&pair, pc));
|
||||
let variable_definitions = next_if_rule(&mut pairs, Rule::variable_definitions)
|
||||
.map(|pair| parse_variable_definitions(pair, pc));
|
||||
let directives =
|
||||
next_if_rule(&mut pairs, Rule::directives).map(|pair| parse_directives(pair, pc));
|
||||
let selection_set = parse_selection_set(pairs.next().unwrap(), pc);
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
OperationDefinition {
|
||||
ty: ty.node,
|
||||
name,
|
||||
variable_definitions: variable_definitions.unwrap_or_default(),
|
||||
directives: directives.unwrap_or_default(),
|
||||
selection_set,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_operation_type(
|
||||
pair: &Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Positioned<OperationType> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::operation_type);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
|
||||
Positioned::new(
|
||||
match pair.as_str() {
|
||||
"query" => OperationType::Query,
|
||||
"mutation" => OperationType::Mutation,
|
||||
"subscription" => OperationType::Subscription,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
pos,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_variable_definitions(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Vec<Positioned<VariableDefinition>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::variable_definitions);
|
||||
|
||||
pair.into_inner()
|
||||
.map(|pair| parse_variable_definition(pair, pc))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_variable_definition(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Positioned<VariableDefinition> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::variable_definition);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let variable = parse_variable(pairs.next().unwrap(), pc);
|
||||
let var_type = parse_type(&pairs.next().unwrap(), pc);
|
||||
let default_value = pairs.next().map(|pair| parse_default_value(pair, pc));
|
||||
|
||||
debug_assert_eq!(pairs.peek(), None);
|
||||
|
||||
Positioned::new(
|
||||
VariableDefinition {
|
||||
name: variable,
|
||||
var_type,
|
||||
default_value,
|
||||
},
|
||||
pos,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_variable(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<String> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::variable);
|
||||
|
||||
parse_name(&exactly_one(pair.into_inner()), pc)
|
||||
}
|
||||
|
||||
fn parse_default_value(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<Value> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::default_value);
|
||||
|
||||
parse_value(exactly_one(pair.into_inner()), pc)
|
||||
}
|
||||
|
||||
fn parse_type(pair: &Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<Type> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::type_);
|
||||
|
||||
Positioned::new(Type::new(pair.as_str()), pc.step(&pair))
|
||||
}
|
||||
|
||||
fn parse_value(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<Value> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::value);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let pair = exactly_one(pair.into_inner());
|
||||
|
||||
Positioned::new(
|
||||
match pair.as_rule() {
|
||||
Rule::variable => Value::Variable(parse_variable(pair, pc).node),
|
||||
Rule::float | Rule::int => {
|
||||
Value::Number(pair.as_str().parse().expect("failed to parse number"))
|
||||
}
|
||||
Rule::string => Value::String({
|
||||
let pair = exactly_one(pair.into_inner());
|
||||
match pair.as_rule() {
|
||||
Rule::block_string_content => block_string_value(pair.as_str()),
|
||||
Rule::string_content => string_value(pair.as_str()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}),
|
||||
Rule::boolean => Value::Boolean(match pair.as_str() {
|
||||
"true" => true,
|
||||
"false" => false,
|
||||
_ => unreachable!(),
|
||||
}),
|
||||
Rule::null => Value::Null,
|
||||
Rule::enum_ => Value::Enum(parse_name(&exactly_one(pair.into_inner()), pc).node),
|
||||
Rule::list => Value::List(
|
||||
pair.into_inner()
|
||||
.map(|pair| parse_value(pair, pc).node)
|
||||
.collect(),
|
||||
),
|
||||
Rule::object => Value::Object(
|
||||
pair.into_inner()
|
||||
.map(|pair| {
|
||||
let (name, value) = parse_name_value(pair, pc);
|
||||
(name.node, value.node)
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
pos,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_name_value(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> (Positioned<String>, Positioned<Value>) {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::name_value);
|
||||
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let name = parse_name(&pairs.next().unwrap(), pc);
|
||||
let value = parse_value(pairs.next().unwrap(), pc);
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
(name, value)
|
||||
}
|
||||
|
||||
fn parse_selection_set(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<SelectionSet> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::selection_set);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
|
||||
Positioned::new(
|
||||
SelectionSet {
|
||||
items: pair
|
||||
.into_inner()
|
||||
.map(|pair| parse_selection(pair, pc))
|
||||
.collect(),
|
||||
},
|
||||
pos,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_selection(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<Selection> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::selection);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let pair = exactly_one(pair.into_inner());
|
||||
|
||||
Positioned::new(
|
||||
match pair.as_rule() {
|
||||
Rule::field => Selection::Field(parse_field(pair, pc)),
|
||||
Rule::fragment_spread => Selection::FragmentSpread(parse_fragment_spread(pair, pc)),
|
||||
Rule::inline_fragment => Selection::InlineFragment(parse_inline_fragment(pair, pc)),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
pos,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_field(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<Field> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::field);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let alias = next_if_rule(&mut pairs, Rule::alias).map(|pair| parse_alias(pair, pc));
|
||||
let name = parse_name(&pairs.next().unwrap(), pc);
|
||||
let arguments = next_if_rule(&mut pairs, Rule::arguments).map(|pair| parse_arguments(pair, pc));
|
||||
let directives =
|
||||
next_if_rule(&mut pairs, Rule::directives).map(|pair| parse_directives(pair, pc));
|
||||
let selection_set =
|
||||
next_if_rule(&mut pairs, Rule::selection_set).map(|pair| parse_selection_set(pair, pc));
|
||||
|
||||
Positioned::new(
|
||||
Field {
|
||||
alias,
|
||||
name,
|
||||
arguments: arguments.unwrap_or_default(),
|
||||
directives: directives.unwrap_or_default(),
|
||||
selection_set: selection_set.unwrap_or_default(),
|
||||
},
|
||||
pos,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_alias(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<String> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::alias);
|
||||
parse_name(&exactly_one(pair.into_inner()), pc)
|
||||
}
|
||||
|
||||
fn parse_arguments(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Vec<(Positioned<String>, Positioned<Value>)> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::arguments);
|
||||
pair.into_inner()
|
||||
.map(|pair| parse_name_value(pair, pc))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_fragment_spread(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Positioned<FragmentSpread> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::fragment_spread);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let fragment_name = parse_name(&pairs.next().unwrap(), pc);
|
||||
let directives = pairs.next().map(|pair| parse_directives(pair, pc));
|
||||
|
||||
debug_assert_eq!(pairs.peek(), None);
|
||||
|
||||
Positioned::new(
|
||||
FragmentSpread {
|
||||
fragment_name,
|
||||
directives: directives.unwrap_or_default(),
|
||||
},
|
||||
pos,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_inline_fragment(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Positioned<InlineFragment> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::inline_fragment);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let type_condition =
|
||||
next_if_rule(&mut pairs, Rule::type_condition).map(|pair| parse_type_condition(pair, pc));
|
||||
let directives =
|
||||
next_if_rule(&mut pairs, Rule::directives).map(|pair| parse_directives(pair, pc));
|
||||
let selection_set = parse_selection_set(pairs.next().unwrap(), pc);
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Positioned::new(
|
||||
InlineFragment {
|
||||
type_condition,
|
||||
directives: directives.unwrap_or_default(),
|
||||
selection_set,
|
||||
},
|
||||
pos,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_fragment_definition(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Positioned<FragmentDefinition> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::fragment_definition);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let name = parse_name(&pairs.next().unwrap(), pc);
|
||||
let type_condition = parse_type_condition(pairs.next().unwrap(), pc);
|
||||
let directives =
|
||||
next_if_rule(&mut pairs, Rule::directives).map(|pair| parse_directives(pair, pc));
|
||||
let selection_set = parse_selection_set(pairs.next().unwrap(), pc);
|
||||
|
||||
debug_assert_eq!(pairs.next(), None);
|
||||
|
||||
Positioned::new(
|
||||
FragmentDefinition {
|
||||
name,
|
||||
type_condition,
|
||||
directives: directives.unwrap_or_default(),
|
||||
selection_set,
|
||||
},
|
||||
pos,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_type_condition(
|
||||
pair: Pair<Rule>,
|
||||
pc: &mut PositionCalculator,
|
||||
) -> Positioned<TypeCondition> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::type_condition);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
Positioned::new(
|
||||
TypeCondition {
|
||||
on: parse_name(&exactly_one(pair.into_inner()), pc),
|
||||
},
|
||||
pos,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_directives(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Vec<Positioned<Directive>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::directives);
|
||||
|
||||
pair.into_inner()
|
||||
.map(|pair| parse_directive(pair, pc))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_directive(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<Directive> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::directive);
|
||||
|
||||
let pos = pc.step(&pair);
|
||||
let mut pairs = pair.into_inner();
|
||||
|
||||
let name = parse_name(&pairs.next().unwrap(), pc);
|
||||
let arguments = pairs.next().map(|pair| parse_arguments(pair, pc));
|
||||
|
||||
debug_assert_eq!(pairs.peek(), None);
|
||||
|
||||
Positioned::new(
|
||||
Directive {
|
||||
name,
|
||||
arguments: arguments.unwrap_or_default(),
|
||||
},
|
||||
pos,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_name(pair: &Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<String> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::name);
|
||||
Positioned::new(pair.as_str().to_owned(), pc.step(&pair))
|
||||
}
|
||||
|
||||
// Parser helper functions
|
||||
|
||||
fn next_if_rule<'a>(pairs: &mut Pairs<'a, Rule>, rule: Rule) -> Option<Pair<'a, Rule>> {
|
||||
if pairs.peek().map_or(false, |pair| pair.as_rule() == rule) {
|
||||
Some(pairs.next().unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn exactly_one<T>(iter: impl IntoIterator<Item = T>) -> T {
|
||||
let mut iter = iter.into_iter();
|
||||
let res = iter.next().unwrap();
|
||||
debug_assert!(matches!(iter.next(), None));
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
for entry in fs::read_dir("tests/queries").unwrap() {
|
||||
if let Ok(entry) = entry {
|
||||
GraphQLParser::parse(Rule::document, &fs::read_to_string(entry.path()).unwrap())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser_ast() {
|
||||
for entry in fs::read_dir("tests/queries").unwrap() {
|
||||
if let Ok(entry) = entry {
|
||||
parse_query(fs::read_to_string(entry.path()).unwrap()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_overflowing_int() {
|
||||
let query_ok = format!("mutation {{ add(big: {}) }} ", std::i32::MAX);
|
||||
let query_overflow = format!("mutation {{ add(big: {}0000) }} ", std::i32::MAX);
|
||||
assert!(parse_query(query_ok).is_ok());
|
||||
assert!(parse_query(query_overflow).is_ok());
|
||||
}
|
||||
}
|
|
@ -3,6 +3,10 @@ use std::borrow::{Borrow, BorrowMut};
|
|||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::str::Chars;
|
||||
use pest::RuleType;
|
||||
use pest::iterators::Pair;
|
||||
use crate::Error;
|
||||
|
||||
/// Original position of an element in source code.
|
||||
#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Default, Hash, Serialize)]
|
||||
|
@ -35,6 +39,39 @@ pub struct Positioned<T: ?Sized> {
|
|||
pub node: T,
|
||||
}
|
||||
|
||||
impl<T> Positioned<T> {
|
||||
/// Create a new positioned node from the node and its position.
|
||||
#[must_use]
|
||||
pub const fn new(node: T, pos: Pos) -> Positioned<T> {
|
||||
Positioned { node, pos }
|
||||
}
|
||||
|
||||
/// Get the inner node.
|
||||
///
|
||||
/// This is most useful in callback chains where `Positioned::into_inner` is easier to read than
|
||||
/// `|positioned| positioned.node`.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> T {
|
||||
self.node
|
||||
}
|
||||
|
||||
/// Create a new positioned node with the same position as this one.
|
||||
#[must_use]
|
||||
pub fn position_node<U>(&self, other: U) -> Positioned<U> {
|
||||
Positioned::new(other, self.pos)
|
||||
}
|
||||
|
||||
/// Map the inner value of this positioned node.
|
||||
#[must_use]
|
||||
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Positioned<U> {
|
||||
Positioned::new(f(self.node), self.pos)
|
||||
}
|
||||
|
||||
pub(crate) fn error_here(&self, message: impl Into<String>) -> Error {
|
||||
Error::new(message, self.pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Display> fmt::Display for Positioned<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.node.fmt(f)
|
||||
|
@ -74,16 +111,45 @@ impl BorrowMut<str> for Positioned<String> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Positioned<T> {
|
||||
/// Create a new positioned node from the node and its position.
|
||||
#[must_use]
|
||||
pub const fn new(node: T, pos: Pos) -> Positioned<T> {
|
||||
Positioned { node, pos }
|
||||
pub(crate) struct PositionCalculator<'a> {
|
||||
input: Chars<'a>,
|
||||
pos: usize,
|
||||
line: usize,
|
||||
column: usize,
|
||||
}
|
||||
|
||||
impl<'a> PositionCalculator<'a> {
|
||||
pub(crate) fn new(input: &'a str) -> PositionCalculator<'a> {
|
||||
Self {
|
||||
input: input.chars(),
|
||||
pos: 0,
|
||||
line: 1,
|
||||
column: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the inner node.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> T {
|
||||
self.node
|
||||
pub(crate) fn step<R: RuleType>(&mut self, pair: &Pair<R>) -> Pos {
|
||||
let pos = pair.as_span().start();
|
||||
debug_assert!(pos >= self.pos);
|
||||
for _ in 0..pos - self.pos {
|
||||
match self.input.next() {
|
||||
Some('\r') => {
|
||||
self.column = 1;
|
||||
}
|
||||
Some('\n') => {
|
||||
self.line += 1;
|
||||
self.column = 1;
|
||||
}
|
||||
Some(_) => {
|
||||
self.column += 1;
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
self.pos = pos;
|
||||
Pos {
|
||||
line: self.line,
|
||||
column: self.column,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,551 +0,0 @@
|
|||
//! GraphQL types.
|
||||
|
||||
use crate::pos::{Pos, Positioned};
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fmt::{self, Formatter, Write};
|
||||
use std::fs::File;
|
||||
|
||||
/// A complete GraphQL file or request string.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Document).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Document {
|
||||
/// The definitions of the document.
|
||||
pub definitions: Vec<Definition>,
|
||||
}
|
||||
|
||||
impl Document {
|
||||
/// Convert the document into an [`ExecutableDocument`](struct.ExecutableDocument). Will return
|
||||
/// `None` if there is no operation in the document.
|
||||
///
|
||||
/// The `operation_name` parameter, if set, makes sure that the main operation of the document,
|
||||
/// if named, will have that name.
|
||||
#[must_use]
|
||||
pub fn into_executable(self, operation_name: Option<&str>) -> Option<ExecutableDocument> {
|
||||
let mut operation = None;
|
||||
let mut fragments = HashMap::new();
|
||||
|
||||
for definition in self.definitions {
|
||||
match definition {
|
||||
Definition::Operation(op)
|
||||
if operation_name
|
||||
.zip(op.node.name.as_ref())
|
||||
.map_or(false, |(required_name, op_name)| {
|
||||
required_name != op_name.node
|
||||
}) => {}
|
||||
Definition::Operation(op) => {
|
||||
operation.get_or_insert(op);
|
||||
}
|
||||
Definition::Fragment(fragment) => {
|
||||
fragments.insert(fragment.node.name.node.clone(), fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
operation.map(|operation| ExecutableDocument {
|
||||
operation,
|
||||
fragments,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// An executable document. This is a [`Document`](struct.Document.html) with at least one
|
||||
/// operation, and any number of fragments.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExecutableDocument {
|
||||
/// The main operation of the document.
|
||||
pub operation: Positioned<OperationDefinition>,
|
||||
/// The fragments of the document.
|
||||
pub fragments: HashMap<String, Positioned<FragmentDefinition>>,
|
||||
}
|
||||
|
||||
/// An executable definition in a query; a query, mutation, subscription or fragment definition.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#ExecutableDefinition).
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Definition {
|
||||
/// The definition of an operation.
|
||||
Operation(Positioned<OperationDefinition>),
|
||||
/// The definition of a fragment.
|
||||
Fragment(Positioned<FragmentDefinition>),
|
||||
}
|
||||
|
||||
impl Definition {
|
||||
/// Get the position of the definition.
|
||||
#[must_use]
|
||||
pub fn pos(&self) -> Pos {
|
||||
match self {
|
||||
Self::Operation(op) => op.pos,
|
||||
Self::Fragment(frag) => frag.pos,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the directives of the definition.
|
||||
#[must_use]
|
||||
pub fn directives(&self) -> &Vec<Positioned<Directive>> {
|
||||
match self {
|
||||
Self::Operation(op) => &op.node.directives,
|
||||
Self::Fragment(frag) => &frag.node.directives,
|
||||
}
|
||||
}
|
||||
/// Get a mutable reference to the directives of the definition.
|
||||
#[must_use]
|
||||
pub fn directives_mut(&mut self) -> &mut Vec<Positioned<Directive>> {
|
||||
match self {
|
||||
Self::Operation(op) => &mut op.node.directives,
|
||||
Self::Fragment(frag) => &mut frag.node.directives,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A GraphQL operation, such as `mutation($content:String!) { makePost(content: $content) { id } }`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#OperationDefinition).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OperationDefinition {
|
||||
/// The type of operation.
|
||||
pub ty: OperationType,
|
||||
/// The name of the operation.
|
||||
pub name: Option<Positioned<String>>,
|
||||
/// The variable definitions.
|
||||
pub variable_definitions: Vec<Positioned<VariableDefinition>>,
|
||||
/// The operation's directives.
|
||||
pub directives: Vec<Positioned<Directive>>,
|
||||
/// The operation's selection set.
|
||||
pub selection_set: Positioned<SelectionSet>,
|
||||
}
|
||||
|
||||
/// The type of an operation; `query`, `mutation` or `subscription`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#OperationType).
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum OperationType {
|
||||
/// A query.
|
||||
Query,
|
||||
/// A mutation.
|
||||
Mutation,
|
||||
/// A subscription.
|
||||
Subscription,
|
||||
}
|
||||
|
||||
impl fmt::Display for OperationType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::Query => "query",
|
||||
Self::Mutation => "mutation",
|
||||
Self::Subscription => "subscription",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A variable definition inside a list of variable definitions, for example `$name:String!`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#VariableDefinition).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VariableDefinition {
|
||||
/// The name of the variable, without the preceding `$`.
|
||||
pub name: Positioned<String>,
|
||||
/// The type of the variable.
|
||||
pub var_type: Positioned<Type>,
|
||||
/// The optional default value of the variable.
|
||||
pub default_value: Option<Positioned<Value>>,
|
||||
}
|
||||
|
||||
impl VariableDefinition {
|
||||
/// Get the default value of the variable; this is `default_value` if it is present,
|
||||
/// `Value::Null` if it is nullable and `None` otherwise.
|
||||
#[must_use]
|
||||
pub fn default_value(&self) -> Option<&Value> {
|
||||
self.default_value
|
||||
.as_ref()
|
||||
.map(|value| &value.node)
|
||||
.or_else(|| {
|
||||
if self.var_type.node.nullable {
|
||||
Some(&Value::Null)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A GraphQL type, for example `String` or `[String!]!`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Type).
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Type {
|
||||
/// The base type.
|
||||
pub base: BaseType,
|
||||
/// Whether the type is nullable.
|
||||
pub nullable: bool,
|
||||
}
|
||||
|
||||
impl Type {
|
||||
/// Create a type from the type string.
|
||||
#[must_use]
|
||||
pub fn new(ty: &str) -> Self {
|
||||
let (nullable, ty) = if let Some(rest) = ty.strip_suffix('!') {
|
||||
(false, rest)
|
||||
} else {
|
||||
(true, ty)
|
||||
};
|
||||
|
||||
Self {
|
||||
base: if let Some(ty) = ty.strip_prefix('[').and_then(|ty| ty.strip_suffix(']')) {
|
||||
BaseType::List(Box::new(Self::new(ty)))
|
||||
} else {
|
||||
BaseType::Named(ty.to_owned())
|
||||
},
|
||||
nullable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.base.fmt(f)?;
|
||||
if !self.nullable {
|
||||
f.write_char('!')?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A GraphQL base type, for example `String` or `[String!]`. This does not include whether the
|
||||
/// type is nullable; for that see [Type](struct.Type.html).
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum BaseType {
|
||||
/// A named type, such as `String`.
|
||||
Named(String),
|
||||
/// A list type, such as `[String]`.
|
||||
List(Box<Type>),
|
||||
}
|
||||
|
||||
impl fmt::Display for BaseType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Named(name) => f.write_str(name),
|
||||
Self::List(ty) => write!(f, "[{}]", ty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A GraphQL value, for example `1`, `$name` or `"Hello World!"`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Value).
|
||||
#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
|
||||
#[serde(untagged)]
|
||||
pub enum Value {
|
||||
/// `null`.
|
||||
Null,
|
||||
/// A variable, without the `$`.
|
||||
#[serde(serialize_with = "serialize_variable")]
|
||||
Variable(String),
|
||||
/// A number.
|
||||
Number(serde_json::Number),
|
||||
/// A string.
|
||||
String(String),
|
||||
/// A boolean.
|
||||
Boolean(bool),
|
||||
/// An enum. These are typically in `SCREAMING_SNAKE_CASE`.
|
||||
Enum(String),
|
||||
/// A list of values.
|
||||
List(Vec<Value>),
|
||||
/// An object. This is a map of keys to values.
|
||||
Object(BTreeMap<String, Value>),
|
||||
/// An uploaded file.
|
||||
#[serde(serialize_with = "serialize_upload")]
|
||||
Upload(UploadValue),
|
||||
}
|
||||
fn serialize_variable<S: Serializer>(name: &str, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(&format!("${}", name))
|
||||
}
|
||||
fn serialize_upload<S: Serializer>(_: &UploadValue, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_unit()
|
||||
}
|
||||
|
||||
impl Default for Value {
|
||||
fn default() -> Self {
|
||||
Value::Null
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Value {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Value::Variable(name) => write!(f, "${}", name),
|
||||
Value::Number(num) => write!(f, "{}", *num),
|
||||
Value::String(val) => write_quoted(val, f),
|
||||
Value::Boolean(true) => f.write_str("true"),
|
||||
Value::Boolean(false) => f.write_str("false"),
|
||||
Value::Null | Value::Upload(_) => f.write_str("null"),
|
||||
Value::Enum(name) => f.write_str(name),
|
||||
Value::List(items) => {
|
||||
f.write_char('[')?;
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
if i != 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
item.fmt(f)?;
|
||||
}
|
||||
f.write_char(']')
|
||||
}
|
||||
Value::Object(items) => {
|
||||
f.write_str("{")?;
|
||||
for (i, (name, value)) in items.iter().enumerate() {
|
||||
write!(f, "{}{}: {}", if i == 0 { "" } else { ", " }, name, value)?;
|
||||
}
|
||||
f.write_str("}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_quoted(s: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_char('"')?;
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
'\r' => f.write_str("\\r"),
|
||||
'\n' => f.write_str("\\n"),
|
||||
'\t' => f.write_str("\\t"),
|
||||
'"' => f.write_str("\\\""),
|
||||
'\\' => f.write_str("\\\\"),
|
||||
c if c.is_control() => write!(f, "\\u{:04}", c as u32),
|
||||
c => f.write_char(c),
|
||||
}?
|
||||
}
|
||||
f.write_char('"')
|
||||
}
|
||||
|
||||
impl From<Value> for serde_json::Value {
|
||||
fn from(value: Value) -> Self {
|
||||
match value {
|
||||
Value::Null | Value::Upload(_) => serde_json::Value::Null,
|
||||
Value::Variable(name) => name.into(),
|
||||
Value::Number(n) => serde_json::Value::Number(n),
|
||||
Value::String(s) => s.into(),
|
||||
Value::Boolean(v) => v.into(),
|
||||
Value::Enum(e) => e.into(),
|
||||
Value::List(values) => values
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<serde_json::Value>>()
|
||||
.into(),
|
||||
Value::Object(obj) => serde_json::Value::Object(
|
||||
obj.into_iter()
|
||||
.map(|(name, value)| (name, value.into()))
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Value> for Value {
|
||||
fn from(value: serde_json::Value) -> Self {
|
||||
match value {
|
||||
serde_json::Value::Null => Value::Null,
|
||||
serde_json::Value::Bool(n) => Value::Boolean(n),
|
||||
serde_json::Value::Number(n) => Value::Number(n),
|
||||
serde_json::Value::String(s) => Value::String(s),
|
||||
serde_json::Value::Array(ls) => Value::List(ls.into_iter().map(Into::into).collect()),
|
||||
serde_json::Value::Object(obj) => Value::Object(
|
||||
obj.into_iter()
|
||||
.map(|(name, value)| (name, value.into()))
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A file upload value.
|
||||
pub struct UploadValue {
|
||||
/// The name of the file.
|
||||
pub filename: String,
|
||||
/// The content type of the file.
|
||||
pub content_type: Option<String>,
|
||||
/// The file data.
|
||||
pub content: File,
|
||||
}
|
||||
|
||||
impl UploadValue {
|
||||
/// Attempt to clone the upload value. This type's `Clone` implementation simply calls this and
|
||||
/// panics on failure.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Fails if cloning the inner `File` fails.
|
||||
pub fn try_clone(&self) -> std::io::Result<Self> {
|
||||
Ok(Self {
|
||||
filename: self.filename.clone(),
|
||||
content_type: self.content_type.clone(),
|
||||
content: self.content.try_clone()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for UploadValue {
|
||||
fn clone(&self) -> Self {
|
||||
self.try_clone().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for UploadValue {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Upload({})", self.filename)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for UploadValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.filename == other.filename
|
||||
}
|
||||
}
|
||||
impl Eq for UploadValue {}
|
||||
|
||||
/// A set of fields to be selected, for example `{ name age }`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#SelectionSet).
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct SelectionSet {
|
||||
/// The fields to be selected.
|
||||
pub items: Vec<Positioned<Selection>>,
|
||||
}
|
||||
|
||||
/// A part of an object to be selected; a single field, a fragment spread or an inline fragment.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Selection).
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Selection {
|
||||
/// Select a single field, such as `name` or `weightKilos: weight(unit: KILOGRAMS)`.
|
||||
Field(Positioned<Field>),
|
||||
/// Select using a fragment.
|
||||
FragmentSpread(Positioned<FragmentSpread>),
|
||||
/// Select using an inline fragment.
|
||||
InlineFragment(Positioned<InlineFragment>),
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
/// Get a reference to the directives of the selection.
|
||||
#[must_use]
|
||||
pub fn directives(&self) -> &Vec<Positioned<Directive>> {
|
||||
match self {
|
||||
Self::Field(field) => &field.node.directives,
|
||||
Self::FragmentSpread(spread) => &spread.node.directives,
|
||||
Self::InlineFragment(fragment) => &fragment.node.directives,
|
||||
}
|
||||
}
|
||||
/// Get a mutable reference to the directives of the selection.
|
||||
#[must_use]
|
||||
pub fn directives_mut(&mut self) -> &mut Vec<Positioned<Directive>> {
|
||||
match self {
|
||||
Self::Field(field) => &mut field.node.directives,
|
||||
Self::FragmentSpread(spread) => &mut spread.node.directives,
|
||||
Self::InlineFragment(fragment) => &mut fragment.node.directives,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A field being selected on an object, such as `name` or `weightKilos: weight(unit: KILOGRAMS)`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Field).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Field {
|
||||
/// The optional field alias.
|
||||
pub alias: Option<Positioned<String>>,
|
||||
/// The name of the field.
|
||||
pub name: Positioned<String>,
|
||||
/// The arguments to the field, empty if no arguments are provided.
|
||||
pub arguments: Vec<(Positioned<String>, Positioned<Value>)>,
|
||||
/// The directives in the field selector.
|
||||
pub directives: Vec<Positioned<Directive>>,
|
||||
/// The subfields being selected in this field, if it is an object. Empty if no fields are
|
||||
/// being selected.
|
||||
pub selection_set: Positioned<SelectionSet>,
|
||||
}
|
||||
|
||||
impl Field {
|
||||
/// Get the response key of the field. This is the alias if present and the name otherwise.
|
||||
#[must_use]
|
||||
pub fn response_key(&self) -> &Positioned<String> {
|
||||
self.alias.as_ref().unwrap_or(&self.name)
|
||||
}
|
||||
|
||||
/// Get the value of the argument with the specified name.
|
||||
#[must_use]
|
||||
pub fn get_argument(&self, name: &str) -> Option<&Positioned<Value>> {
|
||||
self.arguments
|
||||
.iter()
|
||||
.find(|item| item.0.node == name)
|
||||
.map(|item| &item.1)
|
||||
}
|
||||
}
|
||||
|
||||
/// A fragment selector, such as `... userFields`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#FragmentSpread).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FragmentSpread {
|
||||
/// The name of the fragment being selected.
|
||||
pub fragment_name: Positioned<String>,
|
||||
/// The directives in the fragment selector.
|
||||
pub directives: Vec<Positioned<Directive>>,
|
||||
}
|
||||
|
||||
/// An inline fragment selector, such as `... on User { name }`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#InlineFragment).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InlineFragment {
|
||||
/// The type condition.
|
||||
pub type_condition: Option<Positioned<TypeCondition>>,
|
||||
/// The directives in the inline fragment.
|
||||
pub directives: Vec<Positioned<Directive>>,
|
||||
/// The selected fields of the fragment.
|
||||
pub selection_set: Positioned<SelectionSet>,
|
||||
}
|
||||
|
||||
/// The definition of a fragment, such as `fragment userFields on User { name age }`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#FragmentDefinition).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FragmentDefinition {
|
||||
/// The name of the fragment. Any name is allowed except `on`.
|
||||
pub name: Positioned<String>,
|
||||
/// The type this fragment operates on.
|
||||
pub type_condition: Positioned<TypeCondition>,
|
||||
/// Directives in the fragment.
|
||||
pub directives: Vec<Positioned<Directive>>,
|
||||
/// The fragment's selection set.
|
||||
pub selection_set: Positioned<SelectionSet>,
|
||||
}
|
||||
|
||||
/// A type a fragment can apply to (`on` followed by the type).
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#TypeCondition).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypeCondition {
|
||||
/// The type this fragment applies to.
|
||||
pub on: Positioned<String>,
|
||||
}
|
||||
|
||||
/// A GraphQL directive, such as `@deprecated(reason: "Use the other field")`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Directive).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Directive {
|
||||
/// The name of the directive.
|
||||
pub name: Positioned<String>,
|
||||
/// The arguments to the directive.
|
||||
pub arguments: Vec<(Positioned<String>, Positioned<Value>)>,
|
||||
}
|
||||
|
||||
impl Directive {
|
||||
/// Get the argument with the given name.
|
||||
#[must_use]
|
||||
pub fn get_argument(&self, name: &str) -> Option<&Positioned<Value>> {
|
||||
self.arguments
|
||||
.iter()
|
||||
.find(|item| item.0.node == name)
|
||||
.map(|item| &item.1)
|
||||
}
|
||||
}
|
269
async-graphql-parser/src/types/executable.rs
Normal file
269
async-graphql-parser/src/types/executable.rs
Normal file
|
@ -0,0 +1,269 @@
|
|||
//! Executable document-related GraphQL types.
|
||||
|
||||
use super::*;
|
||||
|
||||
/// An executable GraphQL file or request string.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#ExecutableDocument).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExecutableDocument {
|
||||
/// The definitions of the document.
|
||||
pub definitions: Vec<ExecutableDefinition>,
|
||||
}
|
||||
|
||||
impl ExecutableDocument {
|
||||
/// Convert the document into an [`ExecutableDocumentData`](struct.ExecutableDocumentData.html).
|
||||
/// Will return `None` if there is no operation in the document.
|
||||
///
|
||||
/// The `operation_name` parameter, if set, makes sure that the main operation of the document,
|
||||
/// if named, will have that name.
|
||||
#[must_use]
|
||||
pub fn into_data(self, operation_name: Option<&str>) -> Option<ExecutableDocumentData> {
|
||||
let mut operation = None;
|
||||
let mut fragments = HashMap::new();
|
||||
|
||||
for definition in self.definitions {
|
||||
match definition {
|
||||
ExecutableDefinition::Operation(op)
|
||||
if operation_name
|
||||
.zip(op.node.name.as_ref())
|
||||
.map_or(false, |(required_name, op_name)| {
|
||||
required_name != op_name.node
|
||||
}) => {}
|
||||
ExecutableDefinition::Operation(op) => {
|
||||
operation.get_or_insert(op);
|
||||
}
|
||||
ExecutableDefinition::Fragment(fragment) => {
|
||||
fragments.insert(fragment.node.name.node.clone(), fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
operation.map(|operation| ExecutableDocumentData {
|
||||
operation,
|
||||
fragments,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The data of an executable document. This is a [`ExecutableDocument`](struct.ExecutableDocument.html) with at least
|
||||
/// one operation, and any number of fragments.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExecutableDocumentData {
|
||||
/// The main operation of the document.
|
||||
pub operation: Positioned<OperationDefinition>,
|
||||
/// The fragments of the document.
|
||||
pub fragments: HashMap<Name, Positioned<FragmentDefinition>>,
|
||||
}
|
||||
|
||||
/// An executable definition in a query; a query, mutation, subscription or fragment definition.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#ExecutableDefinition).
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ExecutableDefinition {
|
||||
/// The definition of an operation.
|
||||
Operation(Positioned<OperationDefinition>),
|
||||
/// The definition of a fragment.
|
||||
Fragment(Positioned<FragmentDefinition>),
|
||||
}
|
||||
|
||||
impl ExecutableDefinition {
|
||||
/// Get the position of the definition.
|
||||
#[must_use]
|
||||
pub fn pos(&self) -> Pos {
|
||||
match self {
|
||||
Self::Operation(op) => op.pos,
|
||||
Self::Fragment(frag) => frag.pos,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the directives of the definition.
|
||||
#[must_use]
|
||||
pub fn directives(&self) -> &Vec<Positioned<Directive>> {
|
||||
match self {
|
||||
Self::Operation(op) => &op.node.directives,
|
||||
Self::Fragment(frag) => &frag.node.directives,
|
||||
}
|
||||
}
|
||||
/// Get a mutable reference to the directives of the definition.
|
||||
#[must_use]
|
||||
pub fn directives_mut(&mut self) -> &mut Vec<Positioned<Directive>> {
|
||||
match self {
|
||||
Self::Operation(op) => &mut op.node.directives,
|
||||
Self::Fragment(frag) => &mut frag.node.directives,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A GraphQL operation, such as `mutation($content:String!) { makePost(content: $content) { id } }`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#OperationDefinition).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OperationDefinition {
|
||||
/// The type of operation.
|
||||
pub ty: OperationType,
|
||||
/// The name of the operation.
|
||||
pub name: Option<Positioned<Name>>,
|
||||
/// The variable definitions.
|
||||
pub variable_definitions: Vec<Positioned<VariableDefinition>>,
|
||||
/// The operation's directives.
|
||||
pub directives: Vec<Positioned<Directive>>,
|
||||
/// The operation's selection set.
|
||||
pub selection_set: Positioned<SelectionSet>,
|
||||
}
|
||||
|
||||
/// A variable definition inside a list of variable definitions, for example `$name:String!`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#VariableDefinition).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VariableDefinition {
|
||||
/// The name of the variable, without the preceding `$`.
|
||||
pub name: Positioned<Name>,
|
||||
/// The type of the variable.
|
||||
pub var_type: Positioned<Type>,
|
||||
/// The optional default value of the variable.
|
||||
pub default_value: Option<Positioned<ConstValue>>,
|
||||
}
|
||||
|
||||
impl VariableDefinition {
|
||||
/// Get the default value of the variable; this is `default_value` if it is present,
|
||||
/// `Value::Null` if it is nullable and `None` otherwise.
|
||||
#[must_use]
|
||||
pub fn default_value(&self) -> Option<&ConstValue> {
|
||||
self.default_value
|
||||
.as_ref()
|
||||
.map(|value| &value.node)
|
||||
.or_else(|| {
|
||||
if self.var_type.node.nullable {
|
||||
Some(&ConstValue::Null)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of fields to be selected, for example `{ name age }`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#SelectionSet).
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct SelectionSet {
|
||||
/// The fields to be selected.
|
||||
pub items: Vec<Positioned<Selection>>,
|
||||
}
|
||||
|
||||
/// A part of an object to be selected; a single field, a fragment spread or an inline fragment.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Selection).
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Selection {
|
||||
/// Select a single field, such as `name` or `weightKilos: weight(unit: KILOGRAMS)`.
|
||||
Field(Positioned<Field>),
|
||||
/// Select using a fragment.
|
||||
FragmentSpread(Positioned<FragmentSpread>),
|
||||
/// Select using an inline fragment.
|
||||
InlineFragment(Positioned<InlineFragment>),
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
/// Get a reference to the directives of the selection.
|
||||
#[must_use]
|
||||
pub fn directives(&self) -> &Vec<Positioned<Directive>> {
|
||||
match self {
|
||||
Self::Field(field) => &field.node.directives,
|
||||
Self::FragmentSpread(spread) => &spread.node.directives,
|
||||
Self::InlineFragment(fragment) => &fragment.node.directives,
|
||||
}
|
||||
}
|
||||
/// Get a mutable reference to the directives of the selection.
|
||||
#[must_use]
|
||||
pub fn directives_mut(&mut self) -> &mut Vec<Positioned<Directive>> {
|
||||
match self {
|
||||
Self::Field(field) => &mut field.node.directives,
|
||||
Self::FragmentSpread(spread) => &mut spread.node.directives,
|
||||
Self::InlineFragment(fragment) => &mut fragment.node.directives,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A field being selected on an object, such as `name` or `weightKilos: weight(unit: KILOGRAMS)`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Field).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Field {
|
||||
/// The optional field alias.
|
||||
pub alias: Option<Positioned<Name>>,
|
||||
/// The name of the field.
|
||||
pub name: Positioned<Name>,
|
||||
/// The arguments to the field, empty if no arguments are provided.
|
||||
pub arguments: Vec<(Positioned<Name>, Positioned<Value>)>,
|
||||
/// The directives in the field selector.
|
||||
pub directives: Vec<Positioned<Directive>>,
|
||||
/// The subfields being selected in this field, if it is an object. Empty if no fields are
|
||||
/// being selected.
|
||||
pub selection_set: Positioned<SelectionSet>,
|
||||
}
|
||||
|
||||
impl Field {
|
||||
/// Get the response key of the field. This is the alias if present and the name otherwise.
|
||||
#[must_use]
|
||||
pub fn response_key(&self) -> &Positioned<Name> {
|
||||
self.alias.as_ref().unwrap_or(&self.name)
|
||||
}
|
||||
|
||||
/// Get the value of the argument with the specified name.
|
||||
#[must_use]
|
||||
pub fn get_argument(&self, name: &str) -> Option<&Positioned<Value>> {
|
||||
self.arguments
|
||||
.iter()
|
||||
.find(|item| item.0.node == name)
|
||||
.map(|item| &item.1)
|
||||
}
|
||||
}
|
||||
|
||||
/// A fragment selector, such as `... userFields`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#FragmentSpread).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FragmentSpread {
|
||||
/// The name of the fragment being selected.
|
||||
pub fragment_name: Positioned<Name>,
|
||||
/// The directives in the fragment selector.
|
||||
pub directives: Vec<Positioned<Directive>>,
|
||||
}
|
||||
|
||||
/// An inline fragment selector, such as `... on User { name }`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#InlineFragment).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InlineFragment {
|
||||
/// The type condition.
|
||||
pub type_condition: Option<Positioned<TypeCondition>>,
|
||||
/// The directives in the inline fragment.
|
||||
pub directives: Vec<Positioned<Directive>>,
|
||||
/// The selected fields of the fragment.
|
||||
pub selection_set: Positioned<SelectionSet>,
|
||||
}
|
||||
|
||||
/// The definition of a fragment, such as `fragment userFields on User { name age }`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#FragmentDefinition).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FragmentDefinition {
|
||||
/// The name of the fragment. Any name is allowed except `on`.
|
||||
pub name: Positioned<Name>,
|
||||
/// The type this fragment operates on.
|
||||
pub type_condition: Positioned<TypeCondition>,
|
||||
/// Directives in the fragment.
|
||||
pub directives: Vec<Positioned<Directive>>,
|
||||
/// The fragment's selection set.
|
||||
pub selection_set: Positioned<SelectionSet>,
|
||||
}
|
||||
|
||||
/// A type a fragment can apply to (`on` followed by the type).
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#TypeCondition).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypeCondition {
|
||||
/// The type this fragment applies to.
|
||||
pub on: Positioned<Name>,
|
||||
}
|
611
async-graphql-parser/src/types/mod.rs
Normal file
611
async-graphql-parser/src/types/mod.rs
Normal file
|
@ -0,0 +1,611 @@
|
|||
//! GraphQL types.
|
||||
//!
|
||||
//! The two root types are [`ExecutableDocument`](struct.ExecutableDocument.html) and
|
||||
//! [`ServiceDocument`](struct.ServiceDocument.html), representing an executable GraphQL query and a
|
||||
//! GraphQL service respectively.
|
||||
//!
|
||||
//! This follows the [June 2018 edition of the GraphQL spec](https://spec.graphql.org/June2018/).
|
||||
|
||||
use crate::pos::{Pos, Positioned};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::ser::{Serializer, Error as _};
|
||||
use serde::de::{Deserializer, Error as _, Unexpected};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fmt::{self, Display, Formatter, Write};
|
||||
use std::fs::File;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::borrow::Borrow;
|
||||
use std::ops::Deref;
|
||||
|
||||
pub use executable::*;
|
||||
pub use service::*;
|
||||
pub use serde_json::Number;
|
||||
|
||||
mod executable;
|
||||
mod service;
|
||||
|
||||
/// The type of an operation; `query`, `mutation` or `subscription`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#OperationType).
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum OperationType {
|
||||
/// A query.
|
||||
Query,
|
||||
/// A mutation.
|
||||
Mutation,
|
||||
/// A subscription.
|
||||
Subscription,
|
||||
}
|
||||
|
||||
impl Display for OperationType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::Query => "query",
|
||||
Self::Mutation => "mutation",
|
||||
Self::Subscription => "subscription",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A GraphQL type, for example `String` or `[String!]!`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Type).
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Type {
|
||||
/// The base type.
|
||||
pub base: BaseType,
|
||||
/// Whether the type is nullable.
|
||||
pub nullable: bool,
|
||||
}
|
||||
|
||||
impl Type {
|
||||
/// Create a type from the type string.
|
||||
#[must_use]
|
||||
pub fn new(ty: &str) -> Option<Self> {
|
||||
let (nullable, ty) = if let Some(rest) = ty.strip_suffix('!') {
|
||||
(false, rest)
|
||||
} else {
|
||||
(true, ty)
|
||||
};
|
||||
|
||||
Some(Self {
|
||||
base: if let Some(ty) = ty.strip_prefix('[') {
|
||||
BaseType::List(Box::new(Self::new(ty.strip_suffix(']')?)?))
|
||||
} else {
|
||||
BaseType::Named(Name::new(ty.to_owned()).ok()?)
|
||||
},
|
||||
nullable,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Type {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
self.base.fmt(f)?;
|
||||
if !self.nullable {
|
||||
f.write_char('!')?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A GraphQL base type, for example `String` or `[String!]`. This does not include whether the
|
||||
/// type is nullable; for that see [Type](struct.Type.html).
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum BaseType {
|
||||
/// A named type, such as `String`.
|
||||
Named(Name),
|
||||
/// A list type, such as `[String]`.
|
||||
List(Box<Type>),
|
||||
}
|
||||
|
||||
impl Display for BaseType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Named(name) => f.write_str(name),
|
||||
Self::List(ty) => write!(f, "[{}]", ty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A resolved GraphQL value, for example `1` or `"Hello World!"`.
|
||||
///
|
||||
/// It can be serialized and deserialized. Enums will be converted to strings. Attempting to
|
||||
/// serialize `Upload` will fail, and `Enum` and `Upload` cannot be deserialized.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Value).
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ConstValue {
|
||||
/// `null`.
|
||||
Null,
|
||||
/// A number.
|
||||
Number(Number),
|
||||
/// A string.
|
||||
String(String),
|
||||
/// A boolean.
|
||||
Boolean(bool),
|
||||
/// An enum. These are typically in `SCREAMING_SNAKE_CASE`.
|
||||
#[serde(skip_deserializing)]
|
||||
Enum(Name),
|
||||
/// A list of values.
|
||||
List(Vec<ConstValue>),
|
||||
/// An object. This is a map of keys to values.
|
||||
Object(BTreeMap<Name, ConstValue>),
|
||||
/// An uploaded file.
|
||||
#[serde(serialize_with = "fail_serialize_upload", skip_deserializing)]
|
||||
Upload(UploadValue),
|
||||
}
|
||||
|
||||
impl ConstValue {
|
||||
/// Convert this `ConstValue` into a `Value`.
|
||||
#[must_use]
|
||||
pub fn into_value(self) -> Value {
|
||||
match self {
|
||||
Self::Null => Value::Null,
|
||||
Self::Number(num) => Value::Number(num),
|
||||
Self::String(s) => Value::String(s),
|
||||
Self::Boolean(b) => Value::Boolean(b),
|
||||
Self::Enum(v) => Value::Enum(v),
|
||||
Self::List(items) => Value::List(items.into_iter().map(ConstValue::into_value).collect()),
|
||||
Self::Object(map) => Value::Object(map.into_iter().map(|(key, value)| (key, value.into_value())).collect()),
|
||||
Self::Upload(upload) => Value::Upload(upload),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to convert the value into JSON. This is equivalent to the `TryFrom` implementation.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Fails if serialization fails (see enum docs for more info).
|
||||
pub fn into_json(self) -> serde_json::Result<serde_json::Value> {
|
||||
self.try_into()
|
||||
}
|
||||
|
||||
/// Attempt to convert JSON into a value. This is equivalent to the `TryFrom` implementation.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Fails if deserialization fails (see enum docs for more info).
|
||||
pub fn from_json(json: serde_json::Value) -> serde_json::Result<Self> {
|
||||
json.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConstValue {
|
||||
fn default() -> Self {
|
||||
Self::Null
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ConstValue {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Number(num) => write!(f, "{}", *num),
|
||||
Self::String(val) => write_quoted(val, f),
|
||||
Self::Boolean(true) => f.write_str("true"),
|
||||
Self::Boolean(false) => f.write_str("false"),
|
||||
Self::Null | Self::Upload(_) => f.write_str("null"),
|
||||
Self::Enum(name) => f.write_str(name),
|
||||
Self::List(items) => write_list(items, f),
|
||||
Self::Object(map) => write_object(map, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<serde_json::Value> for ConstValue {
|
||||
type Error = serde_json::Error;
|
||||
fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
|
||||
Self::deserialize(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<ConstValue> for serde_json::Value {
|
||||
type Error = serde_json::Error;
|
||||
fn try_from(value: ConstValue) -> Result<Self, Self::Error> {
|
||||
serde_json::to_value(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A GraphQL value, for example `1`, `$name` or `"Hello World!"`. This is
|
||||
/// [`ConstValue`](enum.ConstValue.html) with variables.
|
||||
///
|
||||
/// It can be serialized and deserialized. Enums will be converted to strings. Attempting to
|
||||
/// serialize `Upload` or `Variable` will fail, and `Enum`, `Upload` and `Variable` cannot be
|
||||
/// deserialized.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Value).
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(untagged)]
|
||||
pub enum Value {
|
||||
/// A variable, without the `$`.
|
||||
#[serde(serialize_with = "fail_serialize_variable", skip_deserializing)]
|
||||
Variable(Name),
|
||||
/// `null`.
|
||||
Null,
|
||||
/// A number.
|
||||
Number(Number),
|
||||
/// A string.
|
||||
String(String),
|
||||
/// A boolean.
|
||||
Boolean(bool),
|
||||
/// An enum. These are typically in `SCREAMING_SNAKE_CASE`.
|
||||
#[serde(skip_deserializing)]
|
||||
Enum(Name),
|
||||
/// A list of values.
|
||||
List(Vec<Value>),
|
||||
/// An object. This is a map of keys to values.
|
||||
Object(BTreeMap<Name, Value>),
|
||||
/// An uploaded file.
|
||||
#[serde(serialize_with = "fail_serialize_upload", skip_deserializing)]
|
||||
Upload(UploadValue),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
/// Attempt to convert the value into a const value by using a function to get a variable.
|
||||
pub fn into_const_with<E>(self, mut f: impl FnMut(Name) -> Result<ConstValue, E>) -> Result<ConstValue, E> {
|
||||
self.into_const_with_mut(&mut f)
|
||||
}
|
||||
|
||||
fn into_const_with_mut<E>(self, f: &mut impl FnMut(Name) -> Result<ConstValue, E>) -> Result<ConstValue, E> {
|
||||
Ok(match self {
|
||||
Self::Variable(name) => f(name)?,
|
||||
Self::Null => ConstValue::Null,
|
||||
Self::Number(num) => ConstValue::Number(num),
|
||||
Self::String(s) => ConstValue::String(s),
|
||||
Self::Boolean(b) => ConstValue::Boolean(b),
|
||||
Self::Enum(v) => ConstValue::Enum(v),
|
||||
Self::List(items) => ConstValue::List(items.into_iter().map(|value| value.into_const_with_mut(f)).collect::<Result<_, _>>()?),
|
||||
Self::Object(map) => ConstValue::Object(map.into_iter().map(|(key, value)| Ok((key, value.into_const_with_mut(f)?))).collect::<Result<_, _>>()?),
|
||||
Self::Upload(upload) => ConstValue::Upload(upload),
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempt to convert the value into a const value.
|
||||
///
|
||||
/// Will fail if the value contains variables.
|
||||
#[must_use]
|
||||
pub fn into_const(self) -> Option<ConstValue> {
|
||||
self.into_const_with(|_| Err(())).ok()
|
||||
}
|
||||
|
||||
/// Attempt to convert the value into JSON. This is equivalent to the `TryFrom` implementation.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Fails if serialization fails (see enum docs for more info).
|
||||
pub fn into_json(self) -> serde_json::Result<serde_json::Value> {
|
||||
self.try_into()
|
||||
}
|
||||
|
||||
/// Attempt to convert JSON into a value. This is equivalent to the `TryFrom` implementation.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Fails if deserialization fails (see enum docs for more info).
|
||||
pub fn from_json(json: serde_json::Value) -> serde_json::Result<Self> {
|
||||
json.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Value {
|
||||
fn default() -> Self {
|
||||
Self::Null
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Variable(name) => write!(f, "${}", name),
|
||||
Self::Number(num) => write!(f, "{}", *num),
|
||||
Self::String(val) => write_quoted(val, f),
|
||||
Self::Boolean(true) => f.write_str("true"),
|
||||
Self::Boolean(false) => f.write_str("false"),
|
||||
Self::Null | Self::Upload(_) => f.write_str("null"),
|
||||
Self::Enum(name) => f.write_str(name),
|
||||
Self::List(items) => write_list(items, f),
|
||||
Self::Object(map) => write_object(map, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConstValue> for Value {
|
||||
fn from(value: ConstValue) -> Self {
|
||||
value.into_value()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<serde_json::Value> for Value {
|
||||
type Error = serde_json::Error;
|
||||
fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
|
||||
Self::deserialize(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<Value> for serde_json::Value {
|
||||
type Error = serde_json::Error;
|
||||
fn try_from(value: Value) -> Result<Self, Self::Error> {
|
||||
serde_json::to_value(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn fail_serialize_variable<S: Serializer>(_: &str, _: S) -> Result<S::Ok, S::Error> {
|
||||
Err(S::Error::custom("cannot serialize variable"))
|
||||
}
|
||||
fn fail_serialize_upload<S: Serializer>(_: &UploadValue, _: S) -> Result<S::Ok, S::Error> {
|
||||
Err(S::Error::custom("cannot serialize uploaded file"))
|
||||
}
|
||||
|
||||
fn write_quoted(s: &str, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.write_char('"')?;
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
'\r' => f.write_str("\\r"),
|
||||
'\n' => f.write_str("\\n"),
|
||||
'\t' => f.write_str("\\t"),
|
||||
'"' => f.write_str("\\\""),
|
||||
'\\' => f.write_str("\\\\"),
|
||||
c if c.is_control() => write!(f, "\\u{:04}", c as u32),
|
||||
c => f.write_char(c),
|
||||
}?
|
||||
}
|
||||
f.write_char('"')
|
||||
}
|
||||
fn write_list<T: Display>(list: impl IntoIterator<Item = T>, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.write_char('[')?;
|
||||
for item in list {
|
||||
item.fmt(f)?;
|
||||
f.write_char(',')?;
|
||||
}
|
||||
f.write_char(']')
|
||||
}
|
||||
fn write_object<K: Display, V: Display>(object: impl IntoIterator<Item = (K, V)>, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.write_char('{')?;
|
||||
for (name, value) in object {
|
||||
write!(f, "{}: {},", name, value)?;
|
||||
}
|
||||
f.write_char('}')
|
||||
}
|
||||
|
||||
/// A file upload value.
|
||||
pub struct UploadValue {
|
||||
/// The name of the file.
|
||||
pub filename: String,
|
||||
/// The content type of the file.
|
||||
pub content_type: Option<String>,
|
||||
/// The file data.
|
||||
pub content: File,
|
||||
}
|
||||
|
||||
impl UploadValue {
|
||||
/// Attempt to clone the upload value. This type's `Clone` implementation simply calls this and
|
||||
/// panics on failure.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Fails if cloning the inner `File` fails.
|
||||
pub fn try_clone(&self) -> std::io::Result<Self> {
|
||||
Ok(Self {
|
||||
filename: self.filename.clone(),
|
||||
content_type: self.content_type.clone(),
|
||||
content: self.content.try_clone()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for UploadValue {
|
||||
fn clone(&self) -> Self {
|
||||
self.try_clone().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for UploadValue {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Upload({})", self.filename)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for UploadValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.filename == other.filename
|
||||
}
|
||||
}
|
||||
impl Eq for UploadValue {}
|
||||
|
||||
/// A const GraphQL directive, such as `@deprecated(reason: "Use the other field)`. This differs
|
||||
/// from [`Directive`](struct.Directive.html) in that it uses [`ConstValue`](enum.ConstValue.html)
|
||||
/// instead of [`Value`](enum.Value.html).
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Directive).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConstDirective {
|
||||
/// The name of the directive.
|
||||
pub name: Positioned<Name>,
|
||||
/// The arguments to the directive.
|
||||
pub arguments: Vec<(Positioned<Name>, Positioned<ConstValue>)>,
|
||||
}
|
||||
|
||||
impl ConstDirective {
|
||||
/// Convert this `ConstDirective` into a `Directive`.
|
||||
#[must_use]
|
||||
pub fn into_directive(self) -> Directive {
|
||||
Directive {
|
||||
name: self.name,
|
||||
arguments: self.arguments.into_iter().map(|(name, value)| (name, value.map(ConstValue::into_value))).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the argument with the given name.
|
||||
#[must_use]
|
||||
pub fn get_argument(&self, name: &str) -> Option<&Positioned<ConstValue>> {
|
||||
self.arguments
|
||||
.iter()
|
||||
.find(|item| item.0.node == name)
|
||||
.map(|item| &item.1)
|
||||
}
|
||||
}
|
||||
|
||||
/// A GraphQL directive, such as `@deprecated(reason: "Use the other field")`.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Directive).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Directive {
|
||||
/// The name of the directive.
|
||||
pub name: Positioned<Name>,
|
||||
/// The arguments to the directive.
|
||||
pub arguments: Vec<(Positioned<Name>, Positioned<Value>)>,
|
||||
}
|
||||
|
||||
impl Directive {
|
||||
/// Attempt to convert this `Directive` into a `ConstDirective`.
|
||||
#[must_use]
|
||||
pub fn into_const(self) -> Option<ConstDirective> {
|
||||
Some(ConstDirective {
|
||||
name: self.name,
|
||||
arguments: self.arguments.into_iter().map(|(name, value)| Some((name, Positioned::new(value.node.into_const()?, value.pos)))).collect::<Option<_>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the argument with the given name.
|
||||
#[must_use]
|
||||
pub fn get_argument(&self, name: &str) -> Option<&Positioned<Value>> {
|
||||
self.arguments
|
||||
.iter()
|
||||
.find(|item| item.0.node == name)
|
||||
.map(|item| &item.1)
|
||||
}
|
||||
}
|
||||
|
||||
/// A GraphQL name. This is a newtype wrapper around a string with the addition guarantee that it
|
||||
/// is a valid GraphQL name (follows the regex `[_A-Za-z][_0-9A-Za-z]*`).
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Name).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Name(String);
|
||||
|
||||
impl Name {
|
||||
/// Check whether the name is valid (follows the regex `[_A-Za-z][_0-9A-Za-z]*`).
|
||||
#[must_use]
|
||||
pub fn is_valid(name: &str) -> bool {
|
||||
let mut bytes = name.bytes();
|
||||
bytes.next().map_or(false, |c| c.is_ascii_alphabetic() || c == b'_')
|
||||
&& bytes.all(|c| c.is_ascii_alphanumeric() || c == b'_')
|
||||
}
|
||||
|
||||
/// Create a new name without checking whether it is valid or not. This will always check in
|
||||
/// debug mode.
|
||||
///
|
||||
/// This function is not `unsafe` because an invalid name does not cause UB, but care should be
|
||||
/// taken to make sure it is a valid name.
|
||||
#[must_use]
|
||||
pub fn new_unchecked(name: String) -> Self {
|
||||
debug_assert!(Self::is_valid(&name));
|
||||
Self(name)
|
||||
}
|
||||
|
||||
/// Create a new name, checking whether it is valid. Returns ownership of the string if it
|
||||
/// fails.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Fails if the name is not a valid name.
|
||||
pub fn new(name: String) -> Result<Self, String> {
|
||||
if Self::is_valid(&name) {
|
||||
Ok(Self(name))
|
||||
} else {
|
||||
Err(name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the name as a string.
|
||||
#[must_use]
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Convert the name to a `String`.
|
||||
#[must_use]
|
||||
pub fn into_string(self) -> String {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for Name {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for Name {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Name> for String {
|
||||
fn from(name: Name) -> Self {
|
||||
name.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Name {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Name {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<String> for Name {
|
||||
fn eq(&self, other: &String) -> bool {
|
||||
self.0 == *other
|
||||
}
|
||||
}
|
||||
impl PartialEq<str> for Name {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.0 == other
|
||||
}
|
||||
}
|
||||
impl PartialEq<Name> for String {
|
||||
fn eq(&self, other: &Name) -> bool {
|
||||
other == self
|
||||
}
|
||||
}
|
||||
impl PartialEq<Name> for str {
|
||||
fn eq(&self, other: &Name) -> bool {
|
||||
other == self
|
||||
}
|
||||
}
|
||||
impl<'a> PartialEq<&'a str> for Name {
|
||||
fn eq(&self, other: &&'a str) -> bool {
|
||||
self == *other
|
||||
}
|
||||
}
|
||||
impl<'a> PartialEq<Name> for &'a str {
|
||||
fn eq(&self, other: &Name) -> bool {
|
||||
other == self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Name {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
Self::new(String::deserialize(deserializer)?).map_err(|s| D::Error::invalid_value(Unexpected::Str(&s), &"a GraphQL name"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_valid_names() {
|
||||
assert!(Name::is_valid("valid_name"));
|
||||
assert!(Name::is_valid("numbers123_456_789abc"));
|
||||
assert!(Name::is_valid("MiXeD_CaSe"));
|
||||
assert!(Name::is_valid("_"));
|
||||
assert!(!Name::is_valid("invalid name"));
|
||||
assert!(!Name::is_valid("123and_text"));
|
||||
}
|
233
async-graphql-parser/src/types/service.rs
Normal file
233
async-graphql-parser/src/types/service.rs
Normal file
|
@ -0,0 +1,233 @@
|
|||
//! Service-related GraphQL types.
|
||||
|
||||
use super::*;
|
||||
|
||||
/// An GraphQL file or request string defining a GraphQL service.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Document).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServiceDocument {
|
||||
/// The definitions of this document.
|
||||
pub definitions: Vec<TypeSystemDefinition>,
|
||||
}
|
||||
|
||||
/// A definition concerning the type system of a GraphQL service.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#TypeSystemDefinition). This enum also covers
|
||||
/// [extensions](https://spec.graphql.org/June2018/#TypeSystemExtension).
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TypeSystemDefinition {
|
||||
/// The definition of the schema of the service.
|
||||
Schema(Positioned<SchemaDefinition>),
|
||||
/// The definition of a type in the service.
|
||||
Type(Positioned<TypeDefinition>),
|
||||
/// The definition of a directive in the service.
|
||||
Directive(Positioned<DirectiveDefinition>),
|
||||
}
|
||||
|
||||
/// The definition of the schema in a GraphQL service.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#SchemaDefinition). This also covers
|
||||
/// [extensions](https://spec.graphql.org/June2018/#SchemaExtension).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SchemaDefinition {
|
||||
/// Whether the schema is an extension of another schema.
|
||||
pub extend: bool,
|
||||
/// The directives of the schema definition.
|
||||
pub directives: Vec<Positioned<ConstDirective>>,
|
||||
/// The query root. This is always `Some` when the schema is not extended.
|
||||
pub query: Option<Positioned<Name>>,
|
||||
/// The mutation root, if present.
|
||||
pub mutation: Option<Positioned<Name>>,
|
||||
/// The subscription root, if present.
|
||||
pub subscription: Option<Positioned<Name>>,
|
||||
}
|
||||
|
||||
/// The definition of a type in a GraphQL service.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#TypeDefinition). This also covers
|
||||
/// [extensions](https://spec.graphql.org/June2018/#TypeExtension).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypeDefinition {
|
||||
/// Whether the type is an extension of another type.
|
||||
pub extend: bool,
|
||||
/// The description of the type, if present. This is never present on an extension type.
|
||||
pub description: Option<Positioned<String>>,
|
||||
/// The name of the type.
|
||||
pub name: Positioned<Name>,
|
||||
/// The directives of type definition.
|
||||
pub directives: Vec<Positioned<ConstDirective>>,
|
||||
/// Which kind of type is being defined; scalar, object, enum, etc.
|
||||
pub kind: TypeKind,
|
||||
}
|
||||
|
||||
/// A kind of type; scalar, object, enum, etc.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TypeKind {
|
||||
/// A scalar type.
|
||||
Scalar,
|
||||
/// An object type.
|
||||
Object(ObjectType),
|
||||
/// An interface type.
|
||||
Interface(InterfaceType),
|
||||
/// A union type.
|
||||
Union(UnionType),
|
||||
/// An enum type.
|
||||
Enum(EnumType),
|
||||
/// An input object type.
|
||||
InputObject(InputObjectType),
|
||||
}
|
||||
|
||||
/// The definition of an object type.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#ObjectType).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ObjectType {
|
||||
/// The interfaces implemented by the object.
|
||||
pub implements: Vec<Positioned<Name>>,
|
||||
/// The fields of the object type.
|
||||
pub fields: Vec<Positioned<FieldDefinition>>,
|
||||
}
|
||||
|
||||
/// The definition of a field inside an object or interface.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#FieldDefinition).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FieldDefinition {
|
||||
/// The description of the field.
|
||||
pub description: Option<Positioned<String>>,
|
||||
/// The name of the field.
|
||||
pub name: Positioned<Name>,
|
||||
/// The arguments of the field.
|
||||
pub arguments: Vec<Positioned<InputValueDefinition>>,
|
||||
/// The type of the field.
|
||||
pub ty: Positioned<Type>,
|
||||
/// The directives of the field.
|
||||
pub directives: Vec<Positioned<ConstDirective>>,
|
||||
}
|
||||
|
||||
/// The definition of an interface type.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#InterfaceType).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InterfaceType {
|
||||
/// The fields of the interface type.
|
||||
pub fields: Vec<Positioned<FieldDefinition>>,
|
||||
}
|
||||
|
||||
/// The definition of a union type.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#UnionType).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UnionType {
|
||||
/// The member types of the union.
|
||||
pub members: Vec<Positioned<Name>>,
|
||||
}
|
||||
|
||||
/// The definition of an enum.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#EnumType).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EnumType {
|
||||
/// The possible values of the enum.
|
||||
pub values: Vec<Positioned<EnumValueDefinition>>,
|
||||
}
|
||||
|
||||
/// The definition of a value inside an enum.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#EnumValueDefinition).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EnumValueDefinition {
|
||||
/// The description of the argument.
|
||||
pub description: Option<Positioned<String>>,
|
||||
/// The value name.
|
||||
pub value: Positioned<Name>,
|
||||
/// The directives of the enum value.
|
||||
pub directives: Vec<Positioned<ConstDirective>>,
|
||||
}
|
||||
|
||||
/// The definition of an input object.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#InputObjectType).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InputObjectType {
|
||||
/// The fields of the input object.
|
||||
pub fields: Vec<Positioned<InputValueDefinition>>,
|
||||
}
|
||||
|
||||
/// The definition of an input value inside the arguments of a field.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#InputValueDefinition).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InputValueDefinition {
|
||||
/// The description of the argument.
|
||||
pub description: Option<Positioned<String>>,
|
||||
/// The name of the argument.
|
||||
pub name: Positioned<Name>,
|
||||
/// The type of the argument.
|
||||
pub ty: Positioned<Type>,
|
||||
/// The default value of the argument, if there is one.
|
||||
pub default_value: Option<Positioned<ConstValue>>,
|
||||
/// The directives of the input value.
|
||||
pub directives: Vec<Positioned<ConstDirective>>,
|
||||
}
|
||||
|
||||
/// The definition of a directive in a service.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#DirectiveDefinition).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DirectiveDefinition {
|
||||
/// The description of the directive.
|
||||
pub description: Option<Positioned<String>>,
|
||||
/// The name of the directive.
|
||||
pub name: Positioned<Name>,
|
||||
/// The arguments of the directive.
|
||||
pub arguments: Vec<Positioned<InputValueDefinition>>,
|
||||
/// The locations the directive applies to.
|
||||
pub locations: Vec<Positioned<DirectiveLocation>>,
|
||||
}
|
||||
|
||||
/// Where a directive can apply to.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#DirectiveLocation).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DirectiveLocation {
|
||||
/// A [query](enum.OperationType.html#variant.Query) [operation](struct.OperationDefinition.html).
|
||||
Query,
|
||||
/// A [mutation](enum.OperationType.html#variant.Mutation) [operation](struct.OperationDefinition.html).
|
||||
Mutation,
|
||||
/// A [subscription](enum.OperationType.html#variant.Subscription) [operation](struct.OperationDefinition.html).
|
||||
Subscription,
|
||||
/// A [field](struct.Field.html).
|
||||
Field,
|
||||
/// A [fragment definition](struct.FragmentDefinition.html).
|
||||
FragmentDefinition,
|
||||
/// A [fragment spread](struct.FragmentSpread.html).
|
||||
FragmentSpread,
|
||||
/// An [inline fragment](struct.InlineFragment.html).
|
||||
InlineFragment,
|
||||
/// A [schema](struct.Schema.html).
|
||||
Schema,
|
||||
/// A [scalar](enum.TypeKind.html#variant.Scalar).
|
||||
Scalar,
|
||||
/// An [object](struct.ObjectType.html).
|
||||
Object,
|
||||
/// A [field definition](struct.FieldDefinition.html).
|
||||
FieldDefinition,
|
||||
/// An [input value definition](struct.InputFieldDefinition.html) as the arguments of a field
|
||||
/// but not an input object.
|
||||
ArgumentDefinition,
|
||||
/// An [interface](struct.InterfaceType.html).
|
||||
Interface,
|
||||
/// A [union](struct.UnionType.html).
|
||||
Union,
|
||||
/// An [enum](struct.EnumType.html).
|
||||
Enum,
|
||||
/// A [value on an enum](struct.EnumValueDefinition.html).
|
||||
EnumValue,
|
||||
/// An [input object](struct.InputObjectType.html).
|
||||
InputObject,
|
||||
/// An [input value definition](struct.InputValueDefinition.html) on an input object but not a
|
||||
/// field.
|
||||
InputFieldDefinition,
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
type Type1 implements IOne
|
||||
|
||||
type Type1 implements IOne & ITwo
|
||||
|
||||
interface Type1 implements IOne
|
||||
|
||||
interface Type1 implements IOne & ITwo
|
|
@ -1,5 +0,0 @@
|
|||
type Type1 implements & IOne & ITwo
|
||||
type Type2 implements & IOne
|
||||
|
||||
interface Type1 implements & IOne & ITwo
|
||||
interface Type2 implements & IOne
|
|
@ -1,7 +0,0 @@
|
|||
type Type1 implements IOne & ITwo
|
||||
|
||||
type Type2 implements IOne
|
||||
|
||||
interface Type1 implements IOne & ITwo
|
||||
|
||||
interface Type2 implements IOne
|
3
async-graphql-parser/tests/services/implements.graphql
Normal file
3
async-graphql-parser/tests/services/implements.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
type Type1 implements IOne
|
||||
|
||||
type Type1 implements IOne & ITwo
|
|
@ -0,0 +1,2 @@
|
|||
type Type1 implements & IOne & ITwo
|
||||
type Type2 implements & IOne
|
|
@ -0,0 +1,3 @@
|
|||
type Type1 implements IOne & ITwo
|
||||
|
||||
type Type2 implements IOne
|
|
@ -1,8 +1,8 @@
|
|||
use crate::base::Type;
|
||||
use crate::extensions::Extensions;
|
||||
use crate::parser::types::{Directive, ExecutableDocument, Field, SelectionSet, Value};
|
||||
use crate::parser::types::{Directive, ExecutableDocumentData, Field, SelectionSet, Value as InputValue, Name};
|
||||
use crate::schema::SchemaEnv;
|
||||
use crate::{FieldResult, InputValueType, Lookahead, Pos, Positioned, QueryError, Result};
|
||||
use crate::{FieldResult, InputValueType, Lookahead, Pos, Positioned, QueryError, Result, Value};
|
||||
use fnv::FnvHashMap;
|
||||
use serde::ser::SerializeSeq;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
@ -16,7 +16,7 @@ use std::sync::Arc;
|
|||
|
||||
/// Variables of a query.
|
||||
#[derive(Debug, Clone, Default, Serialize)]
|
||||
pub struct Variables(pub BTreeMap<String, Value>);
|
||||
pub struct Variables(pub BTreeMap<Name, Value>);
|
||||
|
||||
impl Display for Variables {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
|
@ -30,13 +30,15 @@ impl Display for Variables {
|
|||
|
||||
impl Variables {
|
||||
/// Parse variables from JSON object.
|
||||
// TODO: Is this supposed to be able to return an error?
|
||||
pub fn parse_from_json(value: serde_json::Value) -> Result<Self> {
|
||||
Ok(if let Value::Object(obj) = value.into() {
|
||||
///
|
||||
/// If the value is not a map, or the keys of map are not valid GraphQL names, then an empty
|
||||
/// `Variables` instance will be returned.
|
||||
pub fn parse_from_json(value: serde_json::Value) -> Self {
|
||||
if let Ok(Value::Object(obj)) = Value::from_json(value) {
|
||||
Self(obj)
|
||||
} else {
|
||||
Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn variable_path(&mut self, path: &str) -> Option<&mut Value> {
|
||||
|
@ -212,7 +214,7 @@ impl<'a, T> Deref for ContextBase<'a, T> {
|
|||
pub struct QueryEnvInner {
|
||||
pub extensions: spin::Mutex<Extensions>,
|
||||
pub variables: Variables,
|
||||
pub document: ExecutableDocument,
|
||||
pub document: ExecutableDocumentData,
|
||||
pub ctx_data: Arc<Data>,
|
||||
}
|
||||
|
||||
|
@ -233,7 +235,7 @@ impl QueryEnv {
|
|||
pub fn new(
|
||||
extensions: spin::Mutex<Extensions>,
|
||||
variables: Variables,
|
||||
document: ExecutableDocument,
|
||||
document: ExecutableDocumentData,
|
||||
ctx_data: Arc<Data>,
|
||||
) -> QueryEnv {
|
||||
QueryEnv(Arc::new(QueryEnvInner {
|
||||
|
@ -364,27 +366,9 @@ impl<'a, T> ContextBase<'a, T> {
|
|||
})
|
||||
}
|
||||
|
||||
fn resolved_input_value(&self, mut value: Positioned<Value>) -> Result<Value> {
|
||||
self.resolve_input_value(&mut value.node, value.pos)?;
|
||||
Ok(value.node)
|
||||
}
|
||||
|
||||
fn resolve_input_value(&self, value: &mut Value, pos: Pos) -> Result<()> {
|
||||
match value {
|
||||
Value::Variable(var_name) => *value = self.var_value(&var_name, pos)?,
|
||||
Value::List(ls) => {
|
||||
for ls_value in ls {
|
||||
self.resolve_input_value(ls_value, pos)?;
|
||||
}
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
for obj_value in obj.values_mut() {
|
||||
self.resolve_input_value(obj_value, pos)?;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Ok(())
|
||||
fn resolve_input_value(&self, value: Positioned<InputValue>) -> Result<Value> {
|
||||
let pos = value.pos;
|
||||
value.node.into_const_with(|name| self.var_value(&name, pos))
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
|
@ -403,10 +387,7 @@ impl<'a, T> ContextBase<'a, T> {
|
|||
_ => continue,
|
||||
};
|
||||
|
||||
let Positioned {
|
||||
node: mut condition_input,
|
||||
pos,
|
||||
} = directive
|
||||
let condition_input = directive
|
||||
.node
|
||||
.get_argument("if")
|
||||
.ok_or_else(|| {
|
||||
|
@ -419,10 +400,10 @@ impl<'a, T> ContextBase<'a, T> {
|
|||
})?
|
||||
.clone();
|
||||
|
||||
self.resolve_input_value(&mut condition_input, pos)?;
|
||||
let pos = condition_input.pos;
|
||||
let condition_input = self.resolve_input_value(condition_input)?;
|
||||
|
||||
if include
|
||||
!= <bool as InputValueType>::parse(Some(condition_input))
|
||||
if include != <bool as InputValueType>::parse(Some(condition_input))
|
||||
.map_err(|e| e.into_error(pos, bool::qualified_type_name()))?
|
||||
{
|
||||
return Ok(true);
|
||||
|
@ -474,7 +455,7 @@ impl<'a> ContextBase<'a, &'a Positioned<Field>> {
|
|||
}
|
||||
}
|
||||
let (pos, value) = match value {
|
||||
Some(value) => (value.pos, Some(self.resolved_input_value(value)?)),
|
||||
Some(value) => (value.pos, Some(self.resolve_input_value(value)?)),
|
||||
None => (Pos::default(), None),
|
||||
};
|
||||
InputValueType::parse(value).map_err(|e| e.into_error(pos, T::qualified_type_name()))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::extensions::{Extension, ResolveInfo};
|
||||
use crate::parser::types::{Definition, Document, OperationType, Selection};
|
||||
use crate::parser::types::{ExecutableDefinition, ExecutableDocument, OperationType, Selection};
|
||||
use crate::{Error, Variables};
|
||||
use itertools::Itertools;
|
||||
use log::{error, info, trace};
|
||||
|
@ -31,12 +31,12 @@ impl Extension for Logger {
|
|||
self.variables = variables.clone();
|
||||
}
|
||||
|
||||
fn parse_end(&mut self, document: &Document) {
|
||||
fn parse_end(&mut self, document: &ExecutableDocument) {
|
||||
let is_schema = document
|
||||
.definitions
|
||||
.iter()
|
||||
.filter_map(|definition| match definition {
|
||||
Definition::Operation(operation) if operation.node.ty == OperationType::Query => Some(operation),
|
||||
ExecutableDefinition::Operation(operation) if operation.node.ty == OperationType::Query => Some(operation),
|
||||
_ => None,
|
||||
})
|
||||
.any(|operation| operation.node.selection_set.node.items.iter().any(|selection| matches!(&selection.node, Selection::Field(field) if field.node.name.node == "__schema")));
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::{Result, Variables};
|
|||
pub use self::apollo_tracing::ApolloTracing;
|
||||
pub use self::logger::Logger;
|
||||
pub use self::tracing::Tracing;
|
||||
use crate::parser::types::Document;
|
||||
use crate::parser::types::ExecutableDocument;
|
||||
use crate::Error;
|
||||
use serde_json::Value;
|
||||
|
||||
|
@ -47,7 +47,7 @@ pub trait Extension: Sync + Send + 'static {
|
|||
fn parse_start(&mut self, query_source: &str, variables: &Variables) {}
|
||||
|
||||
/// Called at the end of the parse.
|
||||
fn parse_end(&mut self, document: &Document) {}
|
||||
fn parse_end(&mut self, document: &ExecutableDocument) {}
|
||||
|
||||
/// Called at the begin of the validation.
|
||||
fn validation_start(&mut self) {}
|
||||
|
@ -96,7 +96,7 @@ impl Extension for Extensions {
|
|||
.for_each(|e| e.parse_start(query_source, variables));
|
||||
}
|
||||
|
||||
fn parse_end(&mut self, document: &Document) {
|
||||
fn parse_end(&mut self, document: &ExecutableDocument) {
|
||||
self.0.iter_mut().for_each(|e| e.parse_end(document));
|
||||
}
|
||||
|
||||
|
|
|
@ -45,9 +45,7 @@ impl IntoQueryBuilder for GQLRequest {
|
|||
builder = builder.operation_name(operation_name);
|
||||
}
|
||||
if let Some(variables) = self.variables {
|
||||
if let Ok(variables) = Variables::parse_from_json(variables) {
|
||||
builder = builder.variables(variables);
|
||||
}
|
||||
builder = builder.variables(Variables::parse_from_json(variables))
|
||||
}
|
||||
Ok(builder)
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ pub use error::{
|
|||
ParseRequestError, QueryError, ResultExt, RuleError,
|
||||
};
|
||||
pub use look_ahead::Lookahead;
|
||||
pub use parser::{types::Value, Pos, Positioned};
|
||||
pub use parser::{types::ConstValue as Value, Pos, Positioned};
|
||||
pub use query::{IntoQueryBuilder, IntoQueryBuilderOpts, QueryBuilder, QueryResponse};
|
||||
pub use registry::CacheControl;
|
||||
pub use scalars::{Any, Json, OutputJson, ID};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::parser::types::{ExecutableDocument, Field, Selection, SelectionSet};
|
||||
use crate::parser::types::{ExecutableDocumentData, Field, Selection, SelectionSet};
|
||||
|
||||
/// A selection performed by a query
|
||||
pub struct Lookahead<'a> {
|
||||
pub(crate) document: &'a ExecutableDocument,
|
||||
pub(crate) document: &'a ExecutableDocumentData,
|
||||
pub(crate) field: Option<&'a Field>,
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ impl<'a> Lookahead<'a> {
|
|||
}
|
||||
|
||||
fn find<'a>(
|
||||
document: &'a ExecutableDocument,
|
||||
document: &'a ExecutableDocumentData,
|
||||
selection_set: &'a SelectionSet,
|
||||
name: &str,
|
||||
) -> Option<&'a Field> {
|
||||
|
|
|
@ -52,7 +52,7 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>(
|
|||
if let Some(MetaType::Object { fields, .. }) =
|
||||
ctx.schema_env.registry.types.get(T::type_name().as_ref())
|
||||
{
|
||||
if !fields.contains_key(&field.node.name.node) {
|
||||
if !fields.contains_key(field.node.name.node.as_str()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>(
|
|||
pos: field.pos,
|
||||
path: None,
|
||||
err: QueryError::FieldNotFound {
|
||||
field_name: field.node.name.node.clone(),
|
||||
field_name: field.node.name.node.clone().into_string(),
|
||||
object: T::type_name().to_string(),
|
||||
},
|
||||
});
|
||||
|
@ -96,7 +96,7 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>(
|
|||
.resolve_field(&ctx_field)
|
||||
.await
|
||||
.log_error(&ctx.query_env.extensions)?;
|
||||
values.insert(field_name, value);
|
||||
values.insert(field_name.into_string(), value);
|
||||
|
||||
ctx_field
|
||||
.query_env
|
||||
|
@ -126,7 +126,7 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>(
|
|||
pos: fragment_spread.pos,
|
||||
path: None,
|
||||
err: QueryError::UnknownFragment {
|
||||
name: fragment_spread.node.fragment_name.node.clone(),
|
||||
name: fragment_spread.node.fragment_name.node.clone().into_string(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ use crate::context::{Data, ResolveId};
|
|||
use crate::error::ParseRequestError;
|
||||
use crate::extensions::{BoxExtension, ErrorLogger, Extension};
|
||||
use crate::mutation_resolver::do_mutation_resolve;
|
||||
use crate::parser::types::{OperationType, UploadValue, Value};
|
||||
use crate::parser::types::{OperationType, UploadValue};
|
||||
use crate::registry::CacheControl;
|
||||
use crate::{
|
||||
do_resolve, ContextBase, Error, ObjectType, Pos, QueryEnv, QueryError, Result, Schema,
|
||||
SubscriptionType, Variables,
|
||||
SubscriptionType, Variables,Value
|
||||
};
|
||||
use std::any::Any;
|
||||
use std::fs::File;
|
||||
|
@ -141,7 +141,7 @@ impl QueryBuilder {
|
|||
|
||||
// execute
|
||||
let inc_resolve_id = AtomicUsize::default();
|
||||
let document = match document.into_executable(self.operation_name.as_deref()) {
|
||||
let document = match document.into_data(self.operation_name.as_deref()) {
|
||||
Some(document) => document,
|
||||
None => {
|
||||
return if let Some(operation_name) = self.operation_name {
|
||||
|
|
|
@ -54,7 +54,7 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>(
|
|||
if field.node.name.node == "__typename" {
|
||||
// Get the typename
|
||||
let ctx_field = ctx.with_field(field);
|
||||
let field_name = ctx_field.item.node.response_key().node.clone();
|
||||
let field_name = ctx_field.item.node.response_key().node.clone().into_string();
|
||||
futures.push(Box::pin(
|
||||
future::ok::<serde_json::Value, Error>(
|
||||
root.introspection_type_name().to_string().into(),
|
||||
|
@ -78,7 +78,7 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>(
|
|||
let ctx = ctx.clone();
|
||||
async move {
|
||||
let ctx_field = ctx.with_field(field);
|
||||
let field_name = ctx_field.item.node.response_key().node.clone();
|
||||
let field_name = ctx_field.item.node.response_key().node.clone().into_string();
|
||||
|
||||
let resolve_info = ResolveInfo {
|
||||
resolve_id: ctx_field.resolve_id,
|
||||
|
@ -98,7 +98,7 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>(
|
|||
pos: field.pos,
|
||||
path: None,
|
||||
err: QueryError::FieldNotFound {
|
||||
field_name: field.node.name.node.to_owned(),
|
||||
field_name: field.node.name.node.clone().into_string(),
|
||||
object: T::type_name().to_string(),
|
||||
},
|
||||
});
|
||||
|
|
|
@ -26,15 +26,12 @@ impl ScalarType for Any {
|
|||
|
||||
impl Any {
|
||||
/// Parse this `Any` value to T by `serde_json`.
|
||||
pub fn parse_value<T: DeserializeOwned>(&self) -> std::result::Result<T, serde_json::Error> {
|
||||
serde_json::from_value(self.to_value().into())
|
||||
pub fn parse_value<T: DeserializeOwned>(&self) -> serde_json::Result<T> {
|
||||
serde_json::from_value(self.to_value().into_json()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Any
|
||||
where
|
||||
T: Into<Value>,
|
||||
{
|
||||
impl<T: Into<Value>> From<T> for Any {
|
||||
fn from(value: T) -> Any {
|
||||
Any(value.into())
|
||||
}
|
||||
|
|
|
@ -11,7 +11,10 @@ use std::borrow::Cow;
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// A scalar that can represent any JSON value.
|
||||
///
|
||||
/// If the inner type cannot be serialized as JSON (e.g. it has non-string keys) it will be `null`.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash, Default)]
|
||||
#[serde(transparent)]
|
||||
pub struct Json<T>(pub T);
|
||||
|
||||
impl<T> Deref for Json<T> {
|
||||
|
@ -28,8 +31,8 @@ impl<T> DerefMut for Json<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Value> for Json<serde_json::Value> {
|
||||
fn from(value: serde_json::Value) -> Self {
|
||||
impl<T> From<T> for Json<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
@ -38,13 +41,14 @@ impl From<serde_json::Value> for Json<serde_json::Value> {
|
|||
#[Scalar(internal, name = "JSON")]
|
||||
impl<T: DeserializeOwned + Serialize + Send + Sync> ScalarType for Json<T> {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
Ok(serde_json::from_value(value.into()).map(Json)?)
|
||||
Ok(serde_json::from_value(value.into_json()?)?)
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
serde_json::to_value(&self.0)
|
||||
.unwrap_or_else(|_| serde_json::Value::Null)
|
||||
.into()
|
||||
.ok()
|
||||
.and_then(|json| Value::from_json(json).ok())
|
||||
.unwrap_or_else(|| Value::Null)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,8 +70,8 @@ impl<T> DerefMut for OutputJson<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Value> for OutputJson<serde_json::Value> {
|
||||
fn from(value: serde_json::Value) -> Self {
|
||||
impl<T> From<T> for OutputJson<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::context::Data;
|
|||
use crate::extensions::{BoxExtension, ErrorLogger, Extension, Extensions};
|
||||
use crate::model::__DirectiveLocation;
|
||||
use crate::parser::parse_query;
|
||||
use crate::parser::types::{Document, OperationType};
|
||||
use crate::parser::types::{ExecutableDocument, OperationType};
|
||||
use crate::query::QueryBuilder;
|
||||
use crate::registry::{MetaDirective, MetaInputValue, Registry};
|
||||
use crate::subscription::{create_connection, create_subscription_stream, ConnectionTransport};
|
||||
|
@ -12,7 +12,6 @@ use crate::{
|
|||
CacheControl, Error, ObjectType, Pos, QueryEnv, QueryError, QueryResponse, Result,
|
||||
SubscriptionType, Type, Variables, ID,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures::channel::mpsc;
|
||||
use futures::Stream;
|
||||
use indexmap::map::IndexMap;
|
||||
|
@ -318,7 +317,7 @@ where
|
|||
source: &str,
|
||||
variables: &Variables,
|
||||
query_extensions: &[Box<dyn Fn() -> BoxExtension + Send + Sync>],
|
||||
) -> Result<(Document, CacheControl, spin::Mutex<Extensions>)> {
|
||||
) -> Result<(ExecutableDocument, CacheControl, spin::Mutex<Extensions>)> {
|
||||
// create extension instances
|
||||
let extensions = spin::Mutex::new(Extensions(
|
||||
self.0
|
||||
|
@ -377,7 +376,7 @@ where
|
|||
) -> Result<impl Stream<Item = Result<serde_json::Value>> + Send> {
|
||||
let (document, _, extensions) = self.prepare_query(source, &variables, &Vec::new())?;
|
||||
|
||||
let document = match document.into_executable(operation_name) {
|
||||
let document = match document.into_data(operation_name) {
|
||||
Some(document) => document,
|
||||
None => {
|
||||
return if let Some(name) = operation_name {
|
||||
|
|
|
@ -76,7 +76,7 @@ where
|
|||
.as_ref()
|
||||
.map(|v| &v.node)
|
||||
{
|
||||
if name.node == Subscription::type_name() {
|
||||
if name.node.as_str() == Subscription::type_name() {
|
||||
create_subscription_stream(
|
||||
schema,
|
||||
environment.clone(),
|
||||
|
|
|
@ -84,11 +84,7 @@ impl ConnectionTransport for WebSocketTransport {
|
|||
"start" => {
|
||||
if let (Some(id), Some(payload)) = (msg.id, msg.payload) {
|
||||
if let Ok(request) = serde_json::from_value::<GQLRequest>(payload) {
|
||||
let variables = request
|
||||
.variables
|
||||
.map(|value| Variables::parse_from_json(value).ok())
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
let variables = Variables::parse_from_json(request.variables.unwrap_or_default());
|
||||
match schema
|
||||
.create_subscription_stream(
|
||||
&request.query,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::parser::types::Name;
|
||||
use crate::{InputValueError, InputValueResult, Type, Value};
|
||||
|
||||
#[allow(missing_docs)]
|
||||
|
@ -35,7 +36,7 @@ pub trait EnumType: Type + Sized + Eq + Send + Copy + Sized + 'static {
|
|||
let items = Self::items();
|
||||
for item in items {
|
||||
if item.value == *self {
|
||||
return Value::Enum(item.name.into());
|
||||
return Value::Enum(Name::new_unchecked(item.name.to_owned()));
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
|
|
|
@ -8,7 +8,7 @@ mod utils;
|
|||
mod visitor;
|
||||
mod visitors;
|
||||
|
||||
use crate::parser::types::Document;
|
||||
use crate::parser::types::ExecutableDocument;
|
||||
use crate::registry::Registry;
|
||||
use crate::{CacheControl, Error, Result, Variables};
|
||||
use visitor::{visit, VisitorContext, VisitorNil};
|
||||
|
@ -31,7 +31,7 @@ pub enum ValidationMode {
|
|||
|
||||
pub fn check_rules(
|
||||
registry: &Registry,
|
||||
doc: &Document,
|
||||
doc: &ExecutableDocument,
|
||||
variables: Option<&Variables>,
|
||||
mode: ValidationMode,
|
||||
) -> Result<CheckResult> {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::context::QueryPathNode;
|
||||
use crate::parser::types::{Directive, Field};
|
||||
use crate::parser::types::{Directive, Field, Value, Name};
|
||||
use crate::registry::MetaInputValue;
|
||||
use crate::validation::utils::is_valid_input_value;
|
||||
use crate::validation::visitor::{Visitor, VisitorContext};
|
||||
use crate::{Positioned, QueryPathSegment, Value};
|
||||
use crate::{Positioned, QueryPathSegment};
|
||||
use indexmap::map::IndexMap;
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -20,7 +20,7 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
|
|||
self.current_args = ctx
|
||||
.registry
|
||||
.directives
|
||||
.get(&directive.node.name.node)
|
||||
.get(directive.node.name.node.as_str())
|
||||
.map(|d| &d.args);
|
||||
}
|
||||
|
||||
|
@ -35,22 +35,17 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
|
|||
fn enter_argument(
|
||||
&mut self,
|
||||
ctx: &mut VisitorContext<'a>,
|
||||
name: &'a Positioned<String>,
|
||||
name: &'a Positioned<Name>,
|
||||
value: &'a Positioned<Value>,
|
||||
) {
|
||||
if let Some(arg) = self
|
||||
.current_args
|
||||
.and_then(|args| args.get(&*name.node).map(|input| input))
|
||||
.and_then(|args| args.get(name.node.as_str()).map(|input| input))
|
||||
{
|
||||
if let Some(validator) = &arg.validator {
|
||||
let value = match &value.node {
|
||||
Value::Variable(var_name) => ctx
|
||||
.variables
|
||||
.and_then(|variables| variables.0.get(var_name)),
|
||||
_ => Some(&value.node),
|
||||
};
|
||||
let value = value.node.clone().into_const_with(|var_name| ctx.variables.and_then(|variables| variables.0.get(&var_name)).map(Clone::clone).ok_or(())).ok();
|
||||
|
||||
if let Some(value) = value {
|
||||
if let Some(validator) = &arg.validator {
|
||||
if let Some(value) = &value {
|
||||
if let Err(reason) = validator.is_valid(value) {
|
||||
ctx.report_error(
|
||||
vec![name.pos],
|
||||
|
@ -61,16 +56,16 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(reason) = is_valid_input_value(
|
||||
if let Some(reason) = value.and_then(|value| is_valid_input_value(
|
||||
ctx.registry,
|
||||
ctx.variables,
|
||||
&arg.ty,
|
||||
&value.node,
|
||||
&value,
|
||||
QueryPathNode {
|
||||
parent: None,
|
||||
segment: QueryPathSegment::Name(arg.name),
|
||||
},
|
||||
) {
|
||||
)) {
|
||||
ctx.report_error(
|
||||
vec![name.pos],
|
||||
format!("Invalid value for argument {}", reason),
|
||||
|
|
|
@ -19,7 +19,7 @@ impl<'a> Visitor<'a> for FieldsOnCorrectType {
|
|||
|
||||
if parent_type
|
||||
.fields()
|
||||
.and_then(|fields| fields.get(&field.node.name.node))
|
||||
.and_then(|fields| fields.get(field.node.name.node.as_str()))
|
||||
.is_none()
|
||||
&& !field
|
||||
.node
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::parser::types::{Directive, Field};
|
||||
use crate::parser::types::{Directive, Field, Value, Name};
|
||||
use crate::registry::MetaInputValue;
|
||||
use crate::validation::suggestion::make_suggestion;
|
||||
use crate::validation::visitor::{Visitor, VisitorContext};
|
||||
use crate::{Positioned, Value};
|
||||
use crate::Positioned;
|
||||
use indexmap::map::IndexMap;
|
||||
|
||||
enum ArgsType<'a> {
|
||||
|
@ -41,7 +41,7 @@ impl<'a> Visitor<'a> for KnownArgumentNames<'a> {
|
|||
self.current_args = ctx
|
||||
.registry
|
||||
.directives
|
||||
.get(&directive.node.name.node)
|
||||
.get(directive.node.name.node.as_str())
|
||||
.map(|d| (&d.args, ArgsType::Directive(&directive.node.name.node)));
|
||||
}
|
||||
|
||||
|
@ -56,11 +56,11 @@ impl<'a> Visitor<'a> for KnownArgumentNames<'a> {
|
|||
fn enter_argument(
|
||||
&mut self,
|
||||
ctx: &mut VisitorContext<'a>,
|
||||
name: &'a Positioned<String>,
|
||||
name: &'a Positioned<Name>,
|
||||
_value: &'a Positioned<Value>,
|
||||
) {
|
||||
if let Some((args, arg_type)) = &self.current_args {
|
||||
if !args.contains_key(&*name.node) {
|
||||
if !args.contains_key(name.node.as_str()) {
|
||||
match arg_type {
|
||||
ArgsType::Field {
|
||||
field_name,
|
||||
|
@ -73,7 +73,7 @@ impl<'a> Visitor<'a> for KnownArgumentNames<'a> {
|
|||
name,
|
||||
field_name,
|
||||
type_name,
|
||||
self.get_suggestion(&name.node)
|
||||
self.get_suggestion(name.node.as_str())
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ impl<'a> Visitor<'a> for KnownArgumentNames<'a> {
|
|||
"Unknown argument \"{}\" on directive \"{}\".{}",
|
||||
name,
|
||||
directive_name,
|
||||
self.get_suggestion(&name.node)
|
||||
self.get_suggestion(name.node.as_str())
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ impl<'a> Visitor<'a> for KnownDirectives {
|
|||
ctx: &mut VisitorContext<'a>,
|
||||
directive: &'a Positioned<Directive>,
|
||||
) {
|
||||
if let Some(schema_directive) = ctx.registry.directives.get(&directive.node.name.node) {
|
||||
if let Some(schema_directive) = ctx.registry.directives.get(directive.node.name.node.as_str()) {
|
||||
if let Some(current_location) = self.location_stack.last() {
|
||||
if !schema_directive.locations.contains(current_location) {
|
||||
ctx.report_error(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::parser::types::{Definition, Document, OperationDefinition};
|
||||
use crate::parser::types::{ExecutableDefinition, ExecutableDocument, OperationDefinition};
|
||||
use crate::validation::visitor::{Visitor, VisitorContext};
|
||||
use crate::Positioned;
|
||||
|
||||
|
@ -8,11 +8,11 @@ pub struct LoneAnonymousOperation {
|
|||
}
|
||||
|
||||
impl<'a> Visitor<'a> for LoneAnonymousOperation {
|
||||
fn enter_document(&mut self, _ctx: &mut VisitorContext<'a>, doc: &'a Document) {
|
||||
fn enter_document(&mut self, _ctx: &mut VisitorContext<'a>, doc: &'a ExecutableDocument) {
|
||||
self.operation_count = Some(
|
||||
doc.definitions
|
||||
.iter()
|
||||
.filter(|d| matches!(&d, Definition::Operation(_)))
|
||||
.filter(|d| matches!(&d, ExecutableDefinition::Operation(_)))
|
||||
.count(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::error::RuleError;
|
||||
use crate::parser::types::{Document, FragmentDefinition, FragmentSpread};
|
||||
use crate::parser::types::{ExecutableDocument, FragmentDefinition, FragmentSpread};
|
||||
use crate::validation::visitor::{Visitor, VisitorContext};
|
||||
use crate::{Pos, Positioned};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
@ -54,7 +54,7 @@ pub struct NoFragmentCycles<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Visitor<'a> for NoFragmentCycles<'a> {
|
||||
fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, _doc: &'a Document) {
|
||||
fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, _doc: &'a ExecutableDocument) {
|
||||
let mut detector = CycleDetector {
|
||||
visited: HashSet::new(),
|
||||
spreads: &self.spreads,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::parser::types::{
|
||||
Document, FragmentDefinition, FragmentSpread, OperationDefinition, VariableDefinition,
|
||||
ExecutableDocument, FragmentDefinition, FragmentSpread, OperationDefinition, VariableDefinition, Value, Name
|
||||
};
|
||||
use crate::validation::utils::{referenced_variables, Scope};
|
||||
use crate::validation::visitor::{Visitor, VisitorContext};
|
||||
use crate::{Pos, Positioned, Value};
|
||||
use crate::{Pos, Positioned};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -45,7 +45,7 @@ impl<'a> NoUndefinedVariables<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Visitor<'a> for NoUndefinedVariables<'a> {
|
||||
fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, _doc: &'a Document) {
|
||||
fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, _doc: &'a ExecutableDocument) {
|
||||
for (op_name, &(ref def_pos, ref def_vars)) in &self.defined_variables {
|
||||
let mut unused = Vec::new();
|
||||
let mut visited = HashSet::new();
|
||||
|
@ -110,7 +110,7 @@ impl<'a> Visitor<'a> for NoUndefinedVariables<'a> {
|
|||
fn enter_argument(
|
||||
&mut self,
|
||||
_ctx: &mut VisitorContext<'a>,
|
||||
name: &'a Positioned<String>,
|
||||
name: &'a Positioned<Name>,
|
||||
value: &'a Positioned<Value>,
|
||||
) {
|
||||
if let Some(ref scope) = self.current_scope {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::parser::types::{
|
||||
Definition, Document, FragmentDefinition, FragmentSpread, OperationDefinition,
|
||||
ExecutableDefinition, ExecutableDocument, FragmentDefinition, FragmentSpread, OperationDefinition,
|
||||
};
|
||||
use crate::validation::utils::Scope;
|
||||
use crate::validation::visitor::{Visitor, VisitorContext};
|
||||
|
@ -32,11 +32,11 @@ impl<'a> NoUnusedFragments<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Visitor<'a> for NoUnusedFragments<'a> {
|
||||
fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, doc: &'a Document) {
|
||||
fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, doc: &'a ExecutableDocument) {
|
||||
let mut reachable = HashSet::new();
|
||||
|
||||
for def in &doc.definitions {
|
||||
if let Definition::Operation(operation_definition) = def {
|
||||
if let ExecutableDefinition::Operation(operation_definition) = def {
|
||||
self.find_reachable_fragments(
|
||||
&Scope::Operation(
|
||||
operation_definition
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::parser::types::{
|
||||
Document, FragmentDefinition, FragmentSpread, OperationDefinition, VariableDefinition,
|
||||
ExecutableDocument, FragmentDefinition, FragmentSpread, OperationDefinition, VariableDefinition, Value, Name
|
||||
};
|
||||
use crate::validation::utils::{referenced_variables, Scope};
|
||||
use crate::validation::visitor::{Visitor, VisitorContext};
|
||||
use crate::{Pos, Positioned, Value};
|
||||
use crate::{Pos, Positioned};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -45,7 +45,7 @@ impl<'a> NoUnusedVariables<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Visitor<'a> for NoUnusedVariables<'a> {
|
||||
fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, _doc: &'a Document) {
|
||||
fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, _doc: &'a ExecutableDocument) {
|
||||
for (op_name, def_vars) in &self.defined_variables {
|
||||
let mut used = HashSet::new();
|
||||
let mut visited = HashSet::new();
|
||||
|
@ -109,7 +109,7 @@ impl<'a> Visitor<'a> for NoUnusedVariables<'a> {
|
|||
fn enter_argument(
|
||||
&mut self,
|
||||
_ctx: &mut VisitorContext<'a>,
|
||||
_name: &'a Positioned<String>,
|
||||
_name: &'a Positioned<Name>,
|
||||
value: &'a Positioned<Value>,
|
||||
) {
|
||||
if let Some(ref scope) = self.current_scope {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::parser::types::{Definition, Document, FragmentSpread, InlineFragment, TypeCondition};
|
||||
use crate::parser::types::{ExecutableDefinition, ExecutableDocument, FragmentSpread, InlineFragment, TypeCondition};
|
||||
use crate::validation::visitor::{Visitor, VisitorContext};
|
||||
use crate::Positioned;
|
||||
use std::collections::HashMap;
|
||||
|
@ -9,9 +9,9 @@ pub struct PossibleFragmentSpreads<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Visitor<'a> for PossibleFragmentSpreads<'a> {
|
||||
fn enter_document(&mut self, _ctx: &mut VisitorContext<'a>, doc: &'a Document) {
|
||||
fn enter_document(&mut self, _ctx: &mut VisitorContext<'a>, doc: &'a ExecutableDocument) {
|
||||
for d in &doc.definitions {
|
||||
if let Definition::Fragment(fragment) = &d {
|
||||
if let ExecutableDefinition::Fragment(fragment) = &d {
|
||||
let TypeCondition { on: type_name } = &fragment.node.type_condition.node;
|
||||
self.fragment_types
|
||||
.insert(&fragment.node.name.node, &type_name.node);
|
||||
|
@ -56,7 +56,7 @@ impl<'a> Visitor<'a> for PossibleFragmentSpreads<'a> {
|
|||
.as_ref()
|
||||
.map(|c| &c.node)
|
||||
{
|
||||
if let Some(on_type) = ctx.registry.types.get(&fragment_type.node) {
|
||||
if let Some(on_type) = ctx.registry.types.get(fragment_type.node.as_str()) {
|
||||
if !parent_type.type_overlap(&on_type) {
|
||||
ctx.report_error(
|
||||
vec![inline_fragment.pos],
|
||||
|
|
|
@ -12,7 +12,7 @@ impl<'a> Visitor<'a> for ProvidedNonNullArguments {
|
|||
ctx: &mut VisitorContext<'a>,
|
||||
directive: &'a Positioned<Directive>,
|
||||
) {
|
||||
if let Some(schema_directive) = ctx.registry.directives.get(&directive.node.name.node) {
|
||||
if let Some(schema_directive) = ctx.registry.directives.get(directive.node.name.node.as_str()) {
|
||||
for arg in schema_directive.args.values() {
|
||||
if MetaTypeName::create(&arg.ty).is_non_null()
|
||||
&& arg.default_value.is_none()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::parser::types::{Directive, Field};
|
||||
use crate::parser::types::{Directive, Field, Value, Name};
|
||||
use crate::validation::visitor::{Visitor, VisitorContext};
|
||||
use crate::{Positioned, Value};
|
||||
use crate::Positioned;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -20,10 +20,10 @@ impl<'a> Visitor<'a> for UniqueArgumentNames<'a> {
|
|||
fn enter_argument(
|
||||
&mut self,
|
||||
ctx: &mut VisitorContext<'a>,
|
||||
name: &'a Positioned<String>,
|
||||
name: &'a Positioned<Name>,
|
||||
_value: &'a Positioned<Value>,
|
||||
) {
|
||||
if !self.names.insert(&name.node) {
|
||||
if !self.names.insert(name.node.as_str()) {
|
||||
ctx.report_error(
|
||||
vec![name.pos],
|
||||
format!("There can only be one argument named \"{}\"", name),
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::parser::types::{
|
||||
Document, FragmentDefinition, FragmentSpread, OperationDefinition, VariableDefinition,
|
||||
ExecutableDocument, FragmentDefinition, FragmentSpread, OperationDefinition, VariableDefinition, Value,
|
||||
};
|
||||
use crate::registry::MetaTypeName;
|
||||
use crate::validation::utils::Scope;
|
||||
use crate::validation::visitor::{Visitor, VisitorContext};
|
||||
use crate::{Pos, Positioned, Value};
|
||||
use crate::{Pos, Positioned};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -62,7 +62,7 @@ impl<'a> VariableInAllowedPosition<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Visitor<'a> for VariableInAllowedPosition<'a> {
|
||||
fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, _doc: &'a Document) {
|
||||
fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, _doc: &'a ExecutableDocument) {
|
||||
for (op_scope, var_defs) in &self.variable_defs {
|
||||
self.collect_incorrect_usages(op_scope, var_defs, ctx, &mut HashSet::new());
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#![allow(dead_code)]
|
||||
#![allow(unreachable_code)]
|
||||
|
||||
use crate::parser::types::Document;
|
||||
use crate::parser::types::ExecutableDocument;
|
||||
use crate::validation::visitor::{visit, Visitor, VisitorContext};
|
||||
use crate::*;
|
||||
use once_cell::sync::Lazy;
|
||||
|
@ -333,7 +333,7 @@ impl MutationRoot {
|
|||
static TEST_HARNESS: Lazy<Schema<QueryRoot, MutationRoot, EmptySubscription>> =
|
||||
Lazy::new(|| Schema::new(QueryRoot, MutationRoot, EmptySubscription));
|
||||
|
||||
pub fn validate<'a, V, F>(doc: &'a Document, factory: F) -> Result<()>
|
||||
pub fn validate<'a, V, F>(doc: &'a ExecutableDocument, factory: F) -> Result<()>
|
||||
where
|
||||
V: Visitor<'a> + 'a,
|
||||
F: Fn() -> V,
|
||||
|
@ -349,7 +349,7 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn expect_passes_rule_<'a, V, F>(doc: &'a Document, factory: F)
|
||||
pub(crate) fn expect_passes_rule_<'a, V, F>(doc: &'a ExecutableDocument, factory: F)
|
||||
where
|
||||
V: Visitor<'a> + 'a,
|
||||
F: Fn() -> V,
|
||||
|
@ -374,7 +374,7 @@ macro_rules! expect_passes_rule {
|
|||
};
|
||||
}
|
||||
|
||||
pub(crate) fn expect_fails_rule_<'a, V, F>(doc: &'a Document, factory: F)
|
||||
pub(crate) fn expect_fails_rule_<'a, V, F>(doc: &'a ExecutableDocument, factory: F)
|
||||
where
|
||||
V: Visitor<'a> + 'a,
|
||||
F: Fn() -> V,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::parser::types::{Value, ConstValue};
|
||||
use crate::context::QueryPathNode;
|
||||
use crate::{registry, QueryPathSegment, Value, Variables};
|
||||
use crate::{registry, QueryPathSegment, Variables};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
|
@ -37,25 +38,23 @@ pub fn is_valid_input_value(
|
|||
registry: ®istry::Registry,
|
||||
variables: Option<&Variables>,
|
||||
type_name: &str,
|
||||
value: &Value,
|
||||
value: &ConstValue,
|
||||
path_node: QueryPathNode,
|
||||
) -> Option<String> {
|
||||
if let Value::Variable(_) = value {
|
||||
return None;
|
||||
}
|
||||
|
||||
match registry::MetaTypeName::create(type_name) {
|
||||
registry::MetaTypeName::NonNull(type_name) => match value {
|
||||
Value::Null => Some(valid_error(
|
||||
ConstValue::Null => Some(valid_error(
|
||||
&path_node,
|
||||
format!("expected type \"{}\"", type_name),
|
||||
)),
|
||||
_ => is_valid_input_value(registry, variables, type_name, value, path_node),
|
||||
},
|
||||
registry::MetaTypeName::List(type_name) => match value {
|
||||
Value::List(elems) => {
|
||||
for (idx, elem) in elems.iter().enumerate() {
|
||||
if let Some(reason) = is_valid_input_value(
|
||||
ConstValue::List(elems) => {
|
||||
elems
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(idx, elem)| is_valid_input_value(
|
||||
registry,
|
||||
variables,
|
||||
type_name,
|
||||
|
@ -63,133 +62,107 @@ pub fn is_valid_input_value(
|
|||
QueryPathNode {
|
||||
parent: Some(&path_node),
|
||||
segment: QueryPathSegment::Index(idx),
|
||||
},
|
||||
) {
|
||||
return Some(reason);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
))
|
||||
}
|
||||
_ => is_valid_input_value(registry, variables, type_name, value, path_node),
|
||||
},
|
||||
registry::MetaTypeName::Named(type_name) => {
|
||||
if let Value::Null = value {
|
||||
if let ConstValue::Null = value {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(ty) = registry.types.get(type_name) {
|
||||
match ty {
|
||||
registry::MetaType::Scalar { is_valid, .. } => {
|
||||
let value = match value {
|
||||
Value::Variable(var_name) => {
|
||||
variables.and_then(|variables| variables.0.get(var_name))
|
||||
}
|
||||
_ => Some(value),
|
||||
};
|
||||
if let Some(value) = value {
|
||||
if !is_valid(value) {
|
||||
Some(valid_error(
|
||||
&path_node,
|
||||
format!("expected type \"{}\"", type_name),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
match registry.types.get(type_name).unwrap() {
|
||||
registry::MetaType::Scalar { is_valid, .. } => {
|
||||
if is_valid(&value) {
|
||||
None
|
||||
} else {
|
||||
Some(valid_error(
|
||||
&path_node,
|
||||
format!("expected type \"{}\"", type_name),
|
||||
))
|
||||
}
|
||||
}
|
||||
registry::MetaType::Enum { enum_values, name: enum_name, .. } => match value {
|
||||
ConstValue::Enum(name) => {
|
||||
if !enum_values.contains_key(name.as_str()) {
|
||||
Some(valid_error(
|
||||
&path_node,
|
||||
format!(
|
||||
"enumeration type \"{}\" does not contain the value \"{}\"",
|
||||
enum_name,
|
||||
name
|
||||
),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
registry::MetaType::Enum { enum_values, .. } => match value {
|
||||
Value::Enum(name) => {
|
||||
if !enum_values.contains_key(name.as_str()) {
|
||||
Some(valid_error(
|
||||
&path_node,
|
||||
format!(
|
||||
"enumeration type \"{}\" does not contain the value \"{}\"",
|
||||
ty.name(),
|
||||
name
|
||||
),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => Some(valid_error(
|
||||
&path_node,
|
||||
format!("expected type \"{}\"", type_name),
|
||||
)),
|
||||
},
|
||||
registry::MetaType::InputObject { input_fields, .. } => match value {
|
||||
Value::Object(values) => {
|
||||
let mut input_names = values
|
||||
.keys()
|
||||
.map(|name| name.as_ref())
|
||||
.collect::<HashSet<_>>();
|
||||
_ => Some(valid_error(
|
||||
&path_node,
|
||||
format!("expected type \"{}\"", type_name),
|
||||
)),
|
||||
},
|
||||
registry::MetaType::InputObject { input_fields, name: object_name, .. } => match value {
|
||||
ConstValue::Object(values) => {
|
||||
let mut input_names = values
|
||||
.keys()
|
||||
.map(|name| name.as_ref())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
for field in input_fields.values() {
|
||||
input_names.remove(field.name);
|
||||
if let Some(value) = values.get(field.name) {
|
||||
if let Some(validator) = &field.validator {
|
||||
let value = match value {
|
||||
Value::Variable(var_name) => variables
|
||||
.and_then(|variables| variables.0.get(var_name)),
|
||||
_ => Some(value),
|
||||
};
|
||||
|
||||
if let Some(value) = value {
|
||||
if let Err(reason) = validator.is_valid(value) {
|
||||
return Some(valid_error(
|
||||
&QueryPathNode {
|
||||
parent: Some(&path_node),
|
||||
segment: QueryPathSegment::Name(field.name),
|
||||
},
|
||||
reason,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(reason) = is_valid_input_value(
|
||||
registry,
|
||||
variables,
|
||||
&field.ty,
|
||||
value,
|
||||
QueryPathNode {
|
||||
parent: Some(&path_node),
|
||||
segment: QueryPathSegment::Name(field.name),
|
||||
},
|
||||
) {
|
||||
return Some(reason);
|
||||
}
|
||||
} else if registry::MetaTypeName::create(&field.ty).is_non_null()
|
||||
&& field.default_value.is_none()
|
||||
{
|
||||
return Some(valid_error(
|
||||
&path_node,
|
||||
format!(
|
||||
"field \"{}\" of type \"{}\" is required but not provided",
|
||||
field.name,
|
||||
ty.name(),
|
||||
),
|
||||
for field in input_fields.values() {
|
||||
input_names.remove(field.name);
|
||||
if let Some(value) = values.get(field.name) {
|
||||
if let Some(validator) = &field.validator {
|
||||
if let Err(reason) = validator.is_valid(value) {
|
||||
return Some(valid_error(
|
||||
&QueryPathNode {
|
||||
parent: Some(&path_node),
|
||||
segment: QueryPathSegment::Name(field.name),
|
||||
},
|
||||
reason,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(name) = input_names.iter().next() {
|
||||
if let Some(reason) = is_valid_input_value(
|
||||
registry,
|
||||
variables,
|
||||
&field.ty,
|
||||
value,
|
||||
QueryPathNode {
|
||||
parent: Some(&path_node),
|
||||
segment: QueryPathSegment::Name(field.name),
|
||||
},
|
||||
) {
|
||||
return Some(reason);
|
||||
}
|
||||
} else if registry::MetaTypeName::create(&field.ty).is_non_null()
|
||||
&& field.default_value.is_none()
|
||||
{
|
||||
return Some(valid_error(
|
||||
&path_node,
|
||||
format!("unknown field \"{}\" of type \"{}\"", name, ty.name()),
|
||||
));
|
||||
&path_node,
|
||||
format!(
|
||||
"field \"{}\" of type \"{}\" is required but not provided",
|
||||
field.name,
|
||||
object_name,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
|
||||
if let Some(name) = input_names.iter().next() {
|
||||
return Some(valid_error(
|
||||
&path_node,
|
||||
format!("unknown field \"{}\" of type \"{}\"", name, object_name)
|
||||
));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user