apollo_smith/
directive.rs

1use crate::argument::Argument;
2use crate::argument::ArgumentsDef;
3use crate::description::Description;
4use crate::name::Name;
5use crate::DocumentBuilder;
6use apollo_compiler::ast;
7use apollo_compiler::Node;
8use arbitrary::Arbitrary;
9use arbitrary::Result as ArbitraryResult;
10use indexmap::IndexMap;
11use indexmap::IndexSet;
12
13/// The `__DirectiveDef` type represents a Directive definition.
14///
15/// *DirectiveDefinition*:
16///     Description? **directive @** Name Arguments Definition? **repeatable**? **on** DirectiveLocations
17///
18/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Type-System.Directives).
19#[derive(Debug, Clone, PartialEq)]
20pub struct DirectiveDef {
21    pub(crate) description: Option<Description>,
22    pub(crate) name: Name,
23    pub(crate) arguments_definition: Option<ArgumentsDef>,
24    pub(crate) repeatable: bool,
25    pub(crate) directive_locations: IndexSet<DirectiveLocation>,
26}
27
28impl From<DirectiveDef> for ast::Definition {
29    fn from(dir_def: DirectiveDef) -> Self {
30        ast::DirectiveDefinition {
31            description: dir_def.description.map(Into::into),
32            name: dir_def.name.into(),
33            arguments: dir_def
34                .arguments_definition
35                .map(Into::into)
36                .unwrap_or_default(),
37            repeatable: dir_def.repeatable,
38            locations: dir_def
39                .directive_locations
40                .into_iter()
41                .map(Into::into)
42                .collect(),
43        }
44        .into()
45    }
46}
47
48impl TryFrom<apollo_parser::cst::DirectiveDefinition> for DirectiveDef {
49    type Error = crate::FromError;
50
51    fn try_from(
52        directive_def: apollo_parser::cst::DirectiveDefinition,
53    ) -> Result<Self, Self::Error> {
54        Ok(Self {
55            description: directive_def
56                .description()
57                .and_then(|d| d.string_value())
58                .map(|s| Description::from(Into::<String>::into(s))),
59            name: directive_def.name().unwrap().into(),
60            arguments_definition: directive_def
61                .arguments_definition()
62                .map(ArgumentsDef::try_from)
63                .transpose()?,
64            repeatable: directive_def.repeatable_token().is_some(),
65            directive_locations: directive_def
66                .directive_locations()
67                .map(|dls| {
68                    dls.directive_locations()
69                        .map(|dl| DirectiveLocation::from(dl.text().unwrap().to_string()))
70                        .collect()
71                })
72                .unwrap_or_default(),
73        })
74    }
75}
76
77/// The `__Directive` type represents a Directive, it provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.
78///
79/// *Directive*:
80///     @ Name Arguments?
81///
82/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Language.Directives).
83#[derive(Debug, Clone, PartialEq)]
84pub struct Directive {
85    pub(crate) name: Name,
86    pub(crate) arguments: Vec<Argument>,
87}
88
89impl From<Directive> for ast::Directive {
90    fn from(directive: Directive) -> Self {
91        Self {
92            name: directive.name.into(),
93            arguments: directive
94                .arguments
95                .into_iter()
96                .map(|a| Node::new(a.into()))
97                .collect(),
98        }
99    }
100}
101
102impl TryFrom<apollo_parser::cst::Directive> for Directive {
103    type Error = crate::FromError;
104
105    fn try_from(directive: apollo_parser::cst::Directive) -> Result<Self, Self::Error> {
106        Ok(Self {
107            name: directive.name().unwrap().into(),
108            arguments: directive
109                .arguments()
110                .map(|args| {
111                    args.arguments()
112                        .map(Argument::try_from)
113                        .collect::<Result<_, _>>()
114                })
115                .transpose()?
116                .unwrap_or_default(),
117        })
118    }
119}
120
121impl Directive {
122    pub(crate) fn convert_directives(
123        directives: apollo_parser::cst::Directives,
124    ) -> Result<IndexMap<Name, Directive>, crate::FromError> {
125        directives
126            .directives()
127            .map(|d| Ok((d.name().unwrap().into(), Directive::try_from(d)?)))
128            .collect()
129    }
130
131    pub(crate) fn to_ast(map: IndexMap<Name, Directive>) -> ast::DirectiveList {
132        map.into_values().map(ast::Directive::from).collect()
133    }
134}
135
136impl DocumentBuilder<'_> {
137    /// Create an arbitrary vector of `Directive`
138    pub fn directives(
139        &mut self,
140        directive_location: DirectiveLocation,
141    ) -> ArbitraryResult<IndexMap<Name, Directive>> {
142        if self.directive_defs.is_empty() {
143            return Ok(IndexMap::new());
144        }
145
146        let num_directives = self.u.int_in_range(0..=(self.directive_defs.len() - 1))?;
147        let directives = (0..num_directives)
148            .map(|_| self.directive(directive_location))
149            .collect::<ArbitraryResult<Vec<_>>>()?
150            .into_iter()
151            .flat_map(|d| d.map(|d| (d.name.clone(), d)))
152            .collect();
153
154        Ok(directives)
155    }
156
157    /// Create an arbitrary `Directive` given a directive location
158    pub fn directive(
159        &mut self,
160        directive_location: DirectiveLocation,
161    ) -> ArbitraryResult<Option<Directive>> {
162        let available_directive_defs: Vec<&DirectiveDef> = self
163            .directive_defs
164            .iter()
165            .filter(|dd| {
166                dd.directive_locations.is_empty()
167                    || dd.directive_locations.contains(&directive_location)
168            })
169            .collect();
170        if available_directive_defs.is_empty() {
171            return Ok(None);
172        }
173        let directive_def = self.u.choose(&available_directive_defs)?;
174
175        let name = directive_def.name.clone();
176        let arguments = directive_def
177            .arguments_definition
178            .clone()
179            .map(|args_def| self.arguments_with_def(&args_def))
180            .unwrap_or_else(|| Ok(vec![]))?;
181
182        Ok(Some(Directive { name, arguments }))
183    }
184
185    /// Create an arbitrary `DirectiveDef`
186    pub fn directive_def(&mut self) -> ArbitraryResult<DirectiveDef> {
187        let description = self
188            .u
189            .arbitrary()
190            .unwrap_or(false)
191            .then(|| self.description())
192            .transpose()?;
193        let name = self.type_name()?;
194        let arguments_definition = self
195            .u
196            .arbitrary()
197            .unwrap_or(false)
198            .then(|| self.arguments_definition())
199            .transpose()?;
200        let repeatable = self.u.arbitrary().unwrap_or(false);
201        let directive_locations = self.directive_locations()?;
202
203        Ok(DirectiveDef {
204            description,
205            name,
206            arguments_definition,
207            repeatable,
208            directive_locations,
209        })
210    }
211
212    /// Create an arbitrary `IndexSet` of `DirectiveLocation`
213    pub fn directive_locations(&mut self) -> ArbitraryResult<IndexSet<DirectiveLocation>> {
214        (1..self.u.int_in_range(2..=5usize)?)
215            .map(|_| self.u.arbitrary())
216            .collect::<ArbitraryResult<IndexSet<_>>>()
217    }
218}
219
220/// The `__DirectiveLocation` type represents a Directive location.
221#[derive(Debug, Clone, PartialEq, Hash, Eq, Arbitrary, Copy)]
222pub enum DirectiveLocation {
223    Query,
224    Mutation,
225    Subscription,
226    Field,
227    FragmentDefinition,
228    FragmentSpread,
229    InlineFragment,
230    VariableDefinition,
231    Schema,
232    Scalar,
233    Object,
234    FieldDefinition,
235    ArgumentDefinition,
236    Interface,
237    Union,
238    Enum,
239    EnumValue,
240    InputObject,
241    InputFieldDefinition,
242}
243
244impl From<DirectiveLocation> for ast::DirectiveLocation {
245    fn from(dl: DirectiveLocation) -> Self {
246        match dl {
247            DirectiveLocation::Query => Self::Query,
248            DirectiveLocation::Mutation => Self::Mutation,
249            DirectiveLocation::Subscription => Self::Subscription,
250            DirectiveLocation::Field => Self::Field,
251            DirectiveLocation::FragmentDefinition => Self::FragmentDefinition,
252            DirectiveLocation::FragmentSpread => Self::FragmentSpread,
253            DirectiveLocation::InlineFragment => Self::InlineFragment,
254            DirectiveLocation::VariableDefinition => Self::VariableDefinition,
255            DirectiveLocation::Schema => Self::Schema,
256            DirectiveLocation::Scalar => Self::Scalar,
257            DirectiveLocation::Object => Self::Object,
258            DirectiveLocation::FieldDefinition => Self::FieldDefinition,
259            DirectiveLocation::ArgumentDefinition => Self::ArgumentDefinition,
260            DirectiveLocation::Interface => Self::Interface,
261            DirectiveLocation::Union => Self::Union,
262            DirectiveLocation::Enum => Self::Enum,
263            DirectiveLocation::EnumValue => Self::EnumValue,
264            DirectiveLocation::InputObject => Self::InputObject,
265            DirectiveLocation::InputFieldDefinition => Self::InputFieldDefinition,
266        }
267    }
268}
269
270impl From<DirectiveLocation> for String {
271    fn from(dl: DirectiveLocation) -> Self {
272        match dl {
273            DirectiveLocation::Query => String::from("QUERY"),
274            DirectiveLocation::Mutation => String::from("MUTATION"),
275            DirectiveLocation::Subscription => String::from("SUBSCRIPTION"),
276            DirectiveLocation::Field => String::from("FIELD"),
277            DirectiveLocation::FragmentDefinition => String::from("FRAGMENT_DEFINITION"),
278            DirectiveLocation::FragmentSpread => String::from("FRAGMENT_SPREAD"),
279            DirectiveLocation::InlineFragment => String::from("INLINE_FRAGMENT"),
280            DirectiveLocation::VariableDefinition => String::from("VARIABLE_DEFINITION"),
281            DirectiveLocation::Schema => String::from("SCHEMA"),
282            DirectiveLocation::Scalar => String::from("SCALAR"),
283            DirectiveLocation::Object => String::from("OBJECT"),
284            DirectiveLocation::FieldDefinition => String::from("FIELD_DEFINITION"),
285            DirectiveLocation::ArgumentDefinition => String::from("ARGUMENT_DEFINITION"),
286            DirectiveLocation::Interface => String::from("INTERFACE"),
287            DirectiveLocation::Union => String::from("UNION"),
288            DirectiveLocation::Enum => String::from("ENUM"),
289            DirectiveLocation::EnumValue => String::from("ENUM_VALUE"),
290            DirectiveLocation::InputObject => String::from("INPUT_OBJECT"),
291            DirectiveLocation::InputFieldDefinition => String::from("INPUT_FIELD_DEFINITION"),
292        }
293    }
294}
295
296impl From<String> for DirectiveLocation {
297    fn from(dl: String) -> Self {
298        match dl.as_str() {
299            "QUERY" => DirectiveLocation::Query,
300            "MUTATION" => DirectiveLocation::Mutation,
301            "SUBSCRIPTION" => DirectiveLocation::Subscription,
302            "FIELD" => DirectiveLocation::Field,
303            "FRAGMENT_DEFINITION" => DirectiveLocation::FragmentDefinition,
304            "FRAGMENT_SPREAD" => DirectiveLocation::FragmentSpread,
305            "INLINE_FRAGMENT" => DirectiveLocation::InlineFragment,
306            "VARIABLE_DEFINITION" => DirectiveLocation::VariableDefinition,
307            "SCHEMA" => DirectiveLocation::Schema,
308            "SCALAR" => DirectiveLocation::Scalar,
309            "OBJECT" => DirectiveLocation::Object,
310            "FIELD_DEFINITION" => DirectiveLocation::FieldDefinition,
311            "ARGUMENT_DEFINITION" => DirectiveLocation::ArgumentDefinition,
312            "INTERFACE" => DirectiveLocation::Interface,
313            "UNION" => DirectiveLocation::Union,
314            "ENUM" => DirectiveLocation::Enum,
315            "ENUM_VALUE" => DirectiveLocation::EnumValue,
316            "INPUT_OBJECT" => DirectiveLocation::InputObject,
317            "INPUT_FIELD_DEFINITION" => DirectiveLocation::InputFieldDefinition,
318            other => unreachable!(
319                "cannot have {} as a directive location. Documentation: https://spec.graphql.org/October2021/#DirectiveLocation",
320                other
321            ),
322        }
323    }
324}