async-graphql/parser/src/parse/service.rs

425 lines
14 KiB
Rust

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_none() => query = Some(name),
OperationType::Mutation if mutation.is_none() => mutation = Some(name),
OperationType::Subscription if subscription.is_none() => subscription = Some(name),
_ => {
return Err(Error::MultipleRoots {
root: operation_type.node,
schema: pos,
pos: operation_type.pos,
})
}
}
debug_assert_eq!(pairs.next(), None);
}
if !extend && query.is_none() {
return Err(Error::MissingQueryRoot { 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 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::Interface(InterfaceType {
implements: implements.unwrap_or_default(),
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,
"VARIABLE_DEFINITION" => DirectiveLocation::VariableDefinition,
"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 std::fs;
use super::*;
#[test]
fn test_parser() {
for entry in fs::read_dir("tests/services").unwrap() {
let entry = entry.unwrap();
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() {
let entry = entry.unwrap();
parse_schema(fs::read_to_string(entry.path()).unwrap()).unwrap();
}
}
}