use std::{
collections::{BTreeMap, HashSet},
rc::Rc,
};
use syn::LitStr;
use crate::schema::{Schema, Unvalidated};
use {proc_macro2::Span, syn::Lit};
use {
super::parsing,
crate::{
error::Errors,
schema::types::{
self as schema, EnumType, InputObjectType, InputType, InputValue, TypeRef,
},
},
};
#[derive(Debug, PartialEq)]
pub struct AnalysedFieldArguments<'a> {
pub schema_field: schema::Field<'a>,
pub arguments: Vec<Field<'a>>,
pub variants: Vec<Rc<VariantDetails<'a>>>,
}
#[derive(Debug, PartialEq)]
pub struct AnalysedDirectiveArguments<'a> {
pub arguments: Vec<Field<'a>>,
pub variants: Vec<Rc<VariantDetails<'a>>>,
}
#[derive(Debug, PartialEq)]
pub struct Object<'a> {
pub schema_obj: InputObjectType<'a>,
pub fields: Vec<Field<'a>>,
}
#[derive(Debug, PartialEq)]
pub struct Field<'a> {
pub schema_field: InputValue<'a>,
pub value: ArgumentValue<'a>,
pub requires_feature: Option<LitStr>,
}
#[derive(Debug, PartialEq)]
pub enum ArgumentValue<'a> {
Object(Object<'a>),
List(Vec<ArgumentValue<'a>>),
Literal(syn::Lit),
Variant(Rc<VariantDetails<'a>>),
Variable(Variable<'a>),
Some(Box<ArgumentValue<'a>>),
Expression(syn::Expr),
Null,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Variable<'a> {
pub ident: proc_macro2::Ident,
pub value_type: TypeRef<'a, InputType<'a>>,
pub variables_fields_struct: syn::Path,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct VariantDetails<'a> {
pub(super) en: EnumType<'a>,
pub(super) variant: String,
}
pub fn analyse_field_arguments<'a>(
schema: &'a Schema<'a, Unvalidated>,
literals: Vec<parsing::FieldArgument>,
field: &schema::Field<'a>,
variables_fields: Option<&syn::Path>,
span: Span,
) -> Result<AnalysedFieldArguments<'a>, Errors> {
let mut analysis = Analysis {
variables_fields,
variants: HashSet::new(),
};
let arguments = analyse_fields(&mut analysis, literals, &field.arguments, span, schema)?;
let mut variants = analysis.variants.into_iter().collect::<Vec<_>>();
variants.sort_by_key(|v| (v.en.name.clone(), v.variant.clone()));
Ok(AnalysedFieldArguments {
schema_field: field.clone(),
arguments,
variants,
})
}
pub fn analyse_directive_arguments<'a>(
schema: &'a Schema<'a, Unvalidated>,
literals: Vec<parsing::FieldArgument>,
directive: &schema::Directive<'a>,
variables_fields: Option<&syn::Path>,
span: Span,
) -> Result<AnalysedDirectiveArguments<'a>, Errors> {
let mut analysis = Analysis {
variables_fields,
variants: HashSet::new(),
};
let arguments = analyse_fields(&mut analysis, literals, &directive.arguments, span, schema)?;
let mut variants = analysis.variants.into_iter().collect::<Vec<_>>();
variants.sort_by_key(|v| (v.en.name.clone(), v.variant.clone()));
Ok(AnalysedDirectiveArguments {
arguments,
variants,
})
}
struct Analysis<'schema, 'a> {
variables_fields: Option<&'a syn::Path>,
variants: HashSet<Rc<VariantDetails<'schema>>>,
}
impl<'schema> Analysis<'schema, '_> {
fn enum_variant(
&mut self,
en: schema::EnumType<'schema>,
variant: String,
span: Span,
) -> Result<Rc<VariantDetails<'schema>>, Errors> {
en.values
.iter()
.find(|v| v.name == variant)
.ok_or_else(|| {
syn::Error::new(
span,
format!("{} does not have a variant {variant}", en.name),
)
})?;
let variant = Rc::new(VariantDetails { en, variant });
if let Some(variant) = self.variants.get(&variant) {
return Ok(Rc::clone(variant));
}
self.variants.insert(Rc::clone(&variant));
Ok(variant)
}
}
fn analyse_fields<'a>(
analysis: &mut Analysis<'a, '_>,
literals: Vec<parsing::FieldArgument>,
arguments: &[InputValue<'a>],
span: Span,
schema: &'a Schema<'a, Unvalidated>,
) -> Result<Vec<Field<'a>>, Errors> {
validate(&literals, arguments, span)?;
let mut fields = Vec::new();
let mut errors = Vec::new();
for arg in literals {
let schema_field = arguments
.iter()
.find(|a| a.name == arg.argument_name)
.unwrap();
let requires_feature = arg.requires_feature.clone();
match analyse_argument(analysis, arg, schema_field, schema) {
Ok(value) => fields.push(Field {
schema_field: schema_field.clone(),
value,
requires_feature,
}),
Err(e) => errors.push(e),
}
}
if !errors.is_empty() {
return Err(errors.into_iter().collect());
}
Ok(fields)
}
fn analyse_argument<'a>(
analysis: &mut Analysis<'a, '_>,
parsed_arg: parsing::FieldArgument,
argument: &InputValue<'a>,
schema: &'a Schema<'a, Unvalidated>,
) -> Result<ArgumentValue<'a>, Errors> {
match parsed_arg.value {
parsing::FieldArgumentValue::Literal(lit) => {
analyse_value_type(analysis, lit, &argument.value_type, schema)
}
parsing::FieldArgumentValue::Expression(e) => Ok(ArgumentValue::Expression(*e)),
}
}
fn analyse_value_type<'a>(
analysis: &mut Analysis<'a, '_>,
literal: parsing::ArgumentLiteral,
value_type: &TypeRef<'a, InputType<'a>>,
schema: &'a Schema<'a, Unvalidated>,
) -> Result<ArgumentValue<'a>, Errors> {
use parsing::ArgumentLiteral;
if let ArgumentLiteral::Variable(ident, _) = literal {
if analysis.variables_fields.is_none() {
return Err(syn::Error::new(
ident.span(),
"You've provided a variable here, but this QueryFragment does not take any variables. Please add the variables attribute to the struct."
).into());
}
return Ok(ArgumentValue::Variable(Variable {
ident,
value_type: value_type.clone(),
variables_fields_struct: analysis.variables_fields.unwrap().clone(),
}));
}
match &value_type {
TypeRef::Named(_, _) => match (value_type.inner_type(schema), literal) {
(_, ArgumentLiteral::Variable(_, _)) => {
panic!("This should not happen");
}
(InputType::Scalar(_), ArgumentLiteral::Object(_, span)) => {
Err(syn::Error::new(span, "Expected a scalar here but found an object").into())
}
(InputType::Scalar(_), ArgumentLiteral::List(_, span)) => {
Err(syn::Error::new(span, "Expected a scalar here but found a list").into())
}
(InputType::Scalar(_), ArgumentLiteral::Null(span)) => {
Err(syn::Error::new(span, "Expected a scalar here but found a null").into())
}
(InputType::Scalar(_), ArgumentLiteral::Enum(i)) => Err(syn::Error::new(
i.span(),
"Expected a scalar here but found an unquoted string",
)
.into()),
(InputType::Scalar(_), ArgumentLiteral::Literal(lit)) => {
Ok(ArgumentValue::Literal(lit))
}
(InputType::Enum(en), ArgumentLiteral::Enum(i)) => Ok(ArgumentValue::Variant(
analysis.enum_variant(en, i.to_string(), i.span())?,
)),
(InputType::Enum(en), ArgumentLiteral::Literal(Lit::Str(s))) => Ok(
ArgumentValue::Variant(analysis.enum_variant(en, s.value(), s.span())?),
),
(InputType::Enum(_), lit) => {
Err(syn::Error::new(lit.span(), "Expected an enum here").into())
}
(InputType::InputObject(def), ArgumentLiteral::Object(fields, span)) => {
let literals = fields.into_iter().collect::<Vec<_>>();
let fields = analyse_fields(analysis, literals, &def.fields, span, schema)?;
Ok(ArgumentValue::Object(Object {
schema_obj: def,
fields,
}))
}
(InputType::InputObject(_), lit) => {
Err(syn::Error::new(lit.span(), "Expected an object here").into())
}
},
TypeRef::List(element_type) => match literal {
ArgumentLiteral::List(values, _) => {
let mut output_values = Vec::new();
let mut errors = Vec::new();
for value in values {
match analyse_value_type(analysis, value, element_type.as_ref(), schema) {
Ok(v) => output_values.push(v),
Err(e) => errors.push(e),
}
}
if !errors.is_empty() {
return Err(errors.into_iter().collect());
}
Ok(ArgumentValue::List(output_values))
}
other => {
Ok(ArgumentValue::List(vec![analyse_value_type(
analysis,
other,
element_type.as_ref(),
schema,
)?]))
}
},
TypeRef::Nullable(inner_typeref) => match literal {
ArgumentLiteral::Null(_) => Ok(ArgumentValue::Null),
other => Ok(ArgumentValue::Some(Box::new(analyse_value_type(
analysis,
other,
inner_typeref.as_ref(),
schema,
)?))),
},
}
}
fn validate(
literals: &[parsing::FieldArgument],
arguments: &[InputValue<'_>],
span: Span,
) -> Result<(), crate::Errors> {
let all_args = arguments
.iter()
.map(|a| a.name.as_str().to_string())
.collect::<HashSet<_>>();
let required_args = arguments
.iter()
.filter(|v| v.is_required())
.map(|a| a.name.as_str().to_string())
.collect::<HashSet<_>>();
let provided_args = literals
.iter()
.map(|a| a.argument_name.to_string())
.collect::<HashSet<_>>();
let missing_args = required_args
.difference(&provided_args)
.cloned()
.collect::<Vec<_>>();
let unknown_args = provided_args.difference(&all_args).collect::<Vec<_>>();
let counts = literals.iter().map(|lit| &lit.argument_name).fold(
BTreeMap::<_, usize>::new(),
|mut counts, name| {
*counts.entry(name).or_default() += 1;
counts
},
);
let mut errors = Vec::new();
if !missing_args.is_empty() {
let fields = missing_args.join(", ");
errors.push(syn::Error::new(
span,
format!("missing fields - {}", fields),
));
}
for unknown_arg in unknown_args {
let literal = literals
.iter()
.find(|a| a.argument_name == unknown_arg)
.unwrap();
errors.push(syn::Error::new(
literal.argument_name.span(),
"no such field",
));
}
for (ident, count) in counts {
if count > 1 {
errors.push(syn::Error::new(
ident.span(),
format!("duplicate field: {ident}"),
))
}
}
if errors.is_empty() {
return Ok(());
}
Err(errors.into_iter().collect())
}