Skip to main content

bluejay_validator/executable/document/rules/
required_arguments.rs

1use crate::executable::{
2    document::{ArgumentError, Error, Path, Rule, Visitor},
3    Cache,
4};
5use bluejay_core::definition::{
6    DirectiveDefinition, FieldDefinition, InputType, InputValueDefinition, SchemaDefinition,
7};
8use bluejay_core::executable::{ExecutableDocument, Field};
9use bluejay_core::{Argument, AsIter, Directive};
10
11pub struct RequiredArguments<'a, E: ExecutableDocument, S: SchemaDefinition> {
12    schema_definition: &'a S,
13    errors: Vec<Error<'a, E, S>>,
14}
15
16impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> RequiredArguments<'a, E, S> {
17    fn visit_directive<
18        const CONST: bool,
19        F: Fn(ArgumentError<'a, CONST, E, S>) -> Error<'a, E, S>,
20    >(
21        &mut self,
22        directive: &'a <E as ExecutableDocument>::Directive<CONST>,
23        build_error: F,
24    ) {
25        if let Some(directive_definition) = self
26            .schema_definition
27            .get_directive_definition(directive.name())
28        {
29            self.visit_arguments(
30                directive.arguments(),
31                directive_definition.arguments_definition(),
32                |missing_argument_definitions| {
33                    build_error(ArgumentError::DirectiveMissingRequiredArguments {
34                        directive,
35                        directive_definition,
36                        missing_argument_definitions,
37                    })
38                },
39            )
40        }
41    }
42
43    fn visit_arguments<
44        const CONST: bool,
45        F: Fn(Vec<&'a S::InputValueDefinition>) -> Error<'a, E, S>,
46    >(
47        &mut self,
48        arguments: Option<&'a E::Arguments<CONST>>,
49        arguments_definition: Option<&'a S::ArgumentsDefinition>,
50        build_error: F,
51    ) {
52        if let Some(arguments_definition) = arguments_definition {
53            // Use linear scan instead of HashMap — argument lists are typically small (1-5 items)
54            let missing_argument_definitions: Vec<_> = arguments_definition
55                .iter()
56                .filter(|ivd| {
57                    ivd.r#type().is_required()
58                        && ivd.default_value().is_none()
59                        && !arguments
60                            .map(|args| args.iter().any(|arg| arg.name() == ivd.name()))
61                            .unwrap_or(false)
62                })
63                .collect();
64            if !missing_argument_definitions.is_empty() {
65                self.errors.push(build_error(missing_argument_definitions));
66            }
67        }
68    }
69}
70
71impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> Visitor<'a, E, S>
72    for RequiredArguments<'a, E, S>
73{
74    fn new(_: &'a E, schema_definition: &'a S, _: &'a Cache<'a, E, S>) -> Self {
75        Self {
76            schema_definition,
77            errors: Vec::new(),
78        }
79    }
80
81    fn visit_field(
82        &mut self,
83        field: &'a <E as ExecutableDocument>::Field,
84        field_definition: &'a S::FieldDefinition,
85        _: &Path<'a, E>,
86    ) {
87        self.visit_arguments(
88            field.arguments(),
89            field_definition.arguments_definition(),
90            |missing_argument_definitions| {
91                Error::InvalidVariableArgument(ArgumentError::FieldMissingRequiredArguments {
92                    field,
93                    field_definition,
94                    missing_argument_definitions,
95                })
96            },
97        )
98    }
99
100    fn visit_variable_directive(
101        &mut self,
102        directive: &'a <E as ExecutableDocument>::Directive<false>,
103        _: bluejay_core::definition::DirectiveLocation,
104    ) {
105        self.visit_directive(directive, Error::InvalidVariableArgument)
106    }
107
108    fn visit_const_directive(
109        &mut self,
110        directive: &'a <E as ExecutableDocument>::Directive<true>,
111        _: bluejay_core::definition::DirectiveLocation,
112    ) {
113        self.visit_directive(directive, Error::InvalidConstArgument)
114    }
115}
116
117impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> Rule<'a, E, S>
118    for RequiredArguments<'a, E, S>
119{
120    type Error = Error<'a, E, S>;
121    type Errors = std::vec::IntoIter<Error<'a, E, S>>;
122
123    fn into_errors(self) -> Self::Errors {
124        self.errors.into_iter()
125    }
126}