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#[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#[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 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 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 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 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#[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}