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:
Koxiaet 2020-09-08 09:21:27 +01:00
parent 26f7b60bbd
commit 47259548c4
106 changed files with 2518 additions and 1459 deletions

View File

@ -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())
}
}
};

View File

@ -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);

View File

@ -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())
}
}
};

View File

@ -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 | "_")* }

View File

@ -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)

View 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());
}
}

View 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)))
}

View 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();
}
}
}
}

View File

@ -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(|| {

View File

@ -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());
}
}

View File

@ -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,
}
}
}

View File

@ -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)
}
}

View 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>,
}

View 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"));
}

View 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,
}

View File

@ -1,7 +0,0 @@
type Type1 implements IOne
type Type1 implements IOne & ITwo
interface Type1 implements IOne
interface Type1 implements IOne & ITwo

View File

@ -1,5 +0,0 @@
type Type1 implements & IOne & ITwo
type Type2 implements & IOne
interface Type1 implements & IOne & ITwo
interface Type2 implements & IOne

View File

@ -1,7 +0,0 @@
type Type1 implements IOne & ITwo
type Type2 implements IOne
interface Type1 implements IOne & ITwo
interface Type2 implements IOne

View File

@ -0,0 +1,3 @@
type Type1 implements IOne
type Type1 implements IOne & ITwo

View File

@ -0,0 +1,2 @@
type Type1 implements & IOne & ITwo
type Type2 implements & IOne

View File

@ -0,0 +1,3 @@
type Type1 implements IOne & ITwo
type Type2 implements IOne

View File

@ -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()))

View File

@ -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")));

View File

@ -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));
}

View File

@ -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)
}

View File

@ -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};

View File

@ -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> {

View File

@ -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(),
},
});
}

View File

@ -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 {

View File

@ -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(),
},
});

View File

@ -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())
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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(),

View File

@ -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,

View File

@ -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!()

View File

@ -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> {

View File

@ -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),

View File

@ -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

View File

@ -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())
),
);
}

View File

@ -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(

View File

@ -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(),
);
}

View File

@ -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,

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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],

View File

@ -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()

View File

@ -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),

View File

@ -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());
}

View File

@ -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,

View File

@ -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: &registry::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