1use std::{borrow::Cow, collections::HashSet, marker::PhantomData, sync::mpsc::Sender};
2
3use crate::{coercions::CoercesTo, schema, variables::VariableDefinition, QueryVariableLiterals};
4
5use super::{ast::*, to_input_literal, FlattensInto, IsFieldType, Recursable};
6
7const MAX_DEPTH: u16 = 4096;
10
11pub struct SelectionBuilder<'a, SchemaType, VariablesFields> {
13 phantom: PhantomData<fn() -> (SchemaType, VariablesFields)>,
14 selection_set: &'a mut SelectionSet,
15 has_typename: bool,
16 context: BuilderContext<'a>,
17}
18
19impl<'a, T, U> SelectionBuilder<'a, Vec<T>, U> {
20 pub(crate) fn into_inner(self) -> SelectionBuilder<'a, T, U> {
21 SelectionBuilder {
22 selection_set: self.selection_set,
23 has_typename: self.has_typename,
24 phantom: PhantomData,
25 context: self.context,
26 }
27 }
28}
29
30impl<'a, T, U> SelectionBuilder<'a, Option<T>, U> {
31 pub(crate) fn into_inner(self) -> SelectionBuilder<'a, T, U> {
32 SelectionBuilder {
33 selection_set: self.selection_set,
34 has_typename: self.has_typename,
35 phantom: PhantomData,
36 context: self.context,
37 }
38 }
39}
40
41impl<'a, SchemaType, VariablesFields> SelectionBuilder<'a, SchemaType, VariablesFields> {
42 pub(crate) fn new(
43 selection_set: &'a mut SelectionSet,
44 variables_used: &'a Sender<&'static str>,
45 features_enabled: &'a HashSet<String>,
46 inline_variables: Option<&'a dyn QueryVariableLiterals>,
47 ) -> Self {
48 SelectionBuilder::private_new(
49 selection_set,
50 BuilderContext {
51 recurse_depth: None,
52 overall_depth: 0,
53 features_enabled,
54 variables_used,
55 inline_variables,
56 },
57 )
58 }
59
60 fn private_new(selection_set: &'a mut SelectionSet, context: BuilderContext<'a>) -> Self {
61 SelectionBuilder {
62 phantom: PhantomData,
63 has_typename: false,
64 selection_set,
65 context,
66 }
67 }
68
69 pub fn select_flattened_field<FieldMarker, Flattened, FieldType>(
74 &'_ mut self,
75 ) -> FieldSelectionBuilder<'_, FieldMarker, Flattened, VariablesFields>
76 where
77 FieldMarker: schema::Field,
78 FieldType: FlattensInto<Flattened>,
79 SchemaType: schema::HasField<FieldMarker>,
80 FieldType: IsFieldType<SchemaType::Type>,
81 {
82 FieldSelectionBuilder {
83 context: self.context,
84 field: self.push_selection(FieldMarker::NAME),
85 phantom: PhantomData,
86 }
87 }
88
89 pub fn select_field<FieldMarker, FieldType>(
96 &'_ mut self,
97 ) -> FieldSelectionBuilder<'_, FieldMarker, FieldType, VariablesFields>
98 where
99 FieldMarker: schema::Field,
100 SchemaType: schema::HasField<FieldMarker>,
101 FieldType: IsFieldType<SchemaType::Type>,
102 {
103 FieldSelectionBuilder {
104 context: self.context,
105 field: self.push_selection(FieldMarker::NAME),
106 phantom: PhantomData,
107 }
108 }
109
110 pub fn recurse<FieldMarker, FieldType>(
115 &'_ mut self,
116 max_depth: u8,
117 ) -> Option<FieldSelectionBuilder<'_, FieldMarker, FieldType, VariablesFields>>
118 where
119 FieldMarker: schema::Field,
120 SchemaType: schema::HasField<FieldMarker>,
121 FieldType: Recursable<FieldMarker::Type>,
122 {
123 let context = self.context.recurse();
124 let new_depth = context.recurse_depth.unwrap();
125 if new_depth >= max_depth {
126 return None;
127 }
128
129 Some(FieldSelectionBuilder {
130 context,
131 field: self.push_selection(FieldMarker::NAME),
132 phantom: PhantomData,
133 })
134 }
135
136 fn push_selection(&'_ mut self, name: &'static str) -> &mut FieldSelection {
137 self.selection_set
138 .selections
139 .push(Selection::Field(FieldSelection::new(name)));
140
141 match self.selection_set.selections.last_mut() {
142 Some(Selection::Field(field_selection)) => field_selection,
143 _ => panic!("This should not be possible"),
144 }
145 }
146
147 pub fn inline_fragment(&'_ mut self) -> InlineFragmentBuilder<'_, SchemaType, VariablesFields> {
149 if !self.has_typename {
150 self.selection_set
151 .selections
152 .push(Selection::Field(FieldSelection::new("__typename")));
153 self.has_typename = true;
154 }
155
156 self.selection_set
157 .selections
158 .push(Selection::InlineFragment(InlineFragment::default()));
159
160 let inline_fragment = match self.selection_set.selections.last_mut() {
161 Some(Selection::InlineFragment(inline_fragment)) => inline_fragment,
162 _ => panic!("This should not be possible"),
163 };
164
165 InlineFragmentBuilder {
166 inline_fragment,
167 phantom: PhantomData,
168 context: self.context,
169 }
170 }
171
172 pub fn is_feature_enabled(&self, feature: &str) -> bool {
177 self.context.features_enabled.contains(feature)
178 }
179}
180
181pub struct FieldSelectionBuilder<'a, Field, SchemaType, VariablesFields> {
183 #[allow(clippy::type_complexity)]
184 phantom: PhantomData<fn() -> (Field, SchemaType, VariablesFields)>,
185 field: &'a mut FieldSelection,
186 context: BuilderContext<'a>,
187}
188
189impl<Field, FieldSchemaType, VariablesFields>
190 FieldSelectionBuilder<'_, Field, FieldSchemaType, VariablesFields>
191{
192 pub fn alias(&mut self, alias: impl Into<Cow<'static, str>>) {
196 self.field.alias = Some(alias.into())
197 }
198
199 pub fn argument<ArgumentName>(
204 &'_ mut self,
205 ) -> InputBuilder<'_, Field::ArgumentType, VariablesFields>
206 where
207 Field: schema::HasArgument<ArgumentName>,
208 {
209 InputBuilder {
210 destination: InputLiteralContainer::object(Field::NAME, &mut self.field.arguments),
211 context: self.context,
212 phantom: PhantomData,
213 }
214 }
215
216 pub fn directive<DirectiveMarker>(
221 &'_ mut self,
222 ) -> DirectiveBuilder<'_, DirectiveMarker, VariablesFields>
223 where
224 DirectiveMarker: schema::FieldDirective,
225 {
226 self.field.directives.push(Directive {
227 name: Cow::Borrowed(DirectiveMarker::NAME),
228 arguments: vec![],
229 });
230 let directive = self.field.directives.last_mut().unwrap();
231
232 DirectiveBuilder {
233 arguments: &mut directive.arguments,
234 context: self.context,
235 phantom: PhantomData,
236 }
237 }
238
239 pub fn select_children<InnerVariables>(
242 &'_ mut self,
243 ) -> SelectionBuilder<'_, FieldSchemaType, InnerVariables>
244 where
245 VariablesFields: VariableMatch<InnerVariables>,
246 {
247 SelectionBuilder::private_new(&mut self.field.children, self.context.descend())
248 }
249}
250
251pub struct InlineFragmentBuilder<'a, SchemaType, VariablesFields> {
253 phantom: PhantomData<fn() -> (SchemaType, VariablesFields)>,
254 inline_fragment: &'a mut InlineFragment,
255 context: BuilderContext<'a>,
256}
257
258impl<'a, SchemaType, VariablesFields> InlineFragmentBuilder<'a, SchemaType, VariablesFields> {
259 pub fn on<Subtype>(self) -> InlineFragmentBuilder<'a, Subtype, VariablesFields>
264 where
265 Subtype: crate::schema::NamedType,
266 SchemaType: crate::schema::HasSubtype<Subtype>,
267 {
268 self.inline_fragment.on_clause = Some(Subtype::NAME);
269 InlineFragmentBuilder {
270 inline_fragment: self.inline_fragment,
271 phantom: PhantomData,
272 context: self.context,
273 }
274 }
275
276 pub fn select_children<InnerVariablesFields>(
279 &'_ mut self,
280 ) -> SelectionBuilder<'_, SchemaType, InnerVariablesFields>
281 where
282 VariablesFields: VariableMatch<InnerVariablesFields>,
283 {
284 SelectionBuilder::private_new(&mut self.inline_fragment.children, self.context.descend())
285 }
286}
287
288pub struct InputBuilder<'a, SchemaType, VariablesFields> {
289 destination: InputLiteralContainer<'a>,
290 context: BuilderContext<'a>,
291
292 phantom: PhantomData<fn() -> (SchemaType, VariablesFields)>,
293}
294
295impl<SchemaType, VariablesFields> InputBuilder<'_, SchemaType, VariablesFields> {
296 pub fn variable<Type>(self, def: VariableDefinition<VariablesFields, Type>)
298 where
299 Type: CoercesTo<SchemaType>,
300 {
301 match &self.context.inline_variables {
302 None => {
303 self.context
304 .variables_used
305 .send(def.name)
306 .expect("the variables_used channel to be open");
307
308 self.destination.push(InputLiteral::Variable(def.name));
309 }
310 Some(variables) => {
311 if let Some(literal) = variables.get(def.name) {
313 self.destination.push(literal);
314 }
315 }
316 }
317 }
318}
319
320impl<'a, SchemaType, ArgumentStruct> InputBuilder<'a, Option<SchemaType>, ArgumentStruct> {
321 pub fn null(self) {
323 self.destination.push(InputLiteral::Null);
324 }
325
326 pub fn value(self) -> InputBuilder<'a, SchemaType, ArgumentStruct> {
328 InputBuilder {
329 destination: self.destination,
330 context: self.context,
331 phantom: PhantomData,
332 }
333 }
334}
335
336impl<T, ArgStruct> InputBuilder<'_, T, ArgStruct> {
337 pub fn literal(self, l: impl serde::Serialize + CoercesTo<T>) {
339 self.destination
340 .push(to_input_literal(&l).expect("could not convert to InputLiteral"));
341 }
342}
343
344impl<'a, SchemaType, VariablesFields> InputBuilder<'a, SchemaType, VariablesFields>
345where
346 SchemaType: schema::InputObjectMarker,
347{
348 pub fn object(self) -> ObjectArgumentBuilder<'a, SchemaType, VariablesFields> {
350 let fields = match self.destination.push(InputLiteral::Object(Vec::new())) {
351 InputLiteral::Object(fields) => fields,
352 _ => panic!("This should be impossible"),
353 };
354
355 ObjectArgumentBuilder {
356 fields,
357 context: self.context.descend(),
358 phantom: PhantomData,
359 }
360 }
361}
362
363pub struct ObjectArgumentBuilder<'a, ItemType, VariablesFields> {
365 fields: &'a mut Vec<Argument>,
366 context: BuilderContext<'a>,
367 phantom: PhantomData<fn() -> (ItemType, VariablesFields)>,
368}
369
370impl<SchemaType, ArgStruct> ObjectArgumentBuilder<'_, SchemaType, ArgStruct> {
371 pub fn field<FieldMarker, F>(self, field_fn: F) -> Self
374 where
375 FieldMarker: schema::Field,
376 SchemaType: schema::HasInputField<FieldMarker, FieldMarker::Type>,
377 F: FnOnce(InputBuilder<'_, FieldMarker::Type, ArgStruct>),
378 {
379 field_fn(InputBuilder {
380 destination: InputLiteralContainer::object(FieldMarker::NAME, self.fields),
381 context: self.context,
382 phantom: PhantomData,
383 });
384
385 self
386 }
387}
388
389impl<'a, SchemaType, VariablesFields> InputBuilder<'a, Vec<SchemaType>, VariablesFields> {
390 pub fn list(self) -> ListArgumentBuilder<'a, SchemaType, VariablesFields> {
392 let items = match self.destination.push(InputLiteral::List(Vec::new())) {
393 InputLiteral::List(items) => items,
394 _ => panic!("This should be impossible"),
395 };
396
397 ListArgumentBuilder {
398 items,
399 context: self.context.descend(),
400 phantom: PhantomData,
401 }
402 }
403}
404
405pub struct ListArgumentBuilder<'a, ItemType, VariablesFields> {
406 items: &'a mut Vec<InputLiteral>,
407 context: BuilderContext<'a>,
408 phantom: PhantomData<fn() -> (ItemType, VariablesFields)>,
409}
410
411impl<ItemType, VariablesFields> ListArgumentBuilder<'_, ItemType, VariablesFields> {
412 pub fn item(self, item_fn: impl FnOnce(InputBuilder<'_, ItemType, VariablesFields>)) -> Self {
415 item_fn(InputBuilder {
416 destination: InputLiteralContainer::list(self.items),
417 context: self.context,
418 phantom: PhantomData,
419 });
420
421 self
422 }
423}
424
425enum InputLiteralContainer<'a> {
426 Object {
427 argument_name: &'static str,
429
430 arguments: &'a mut Vec<Argument>,
432 },
433 List(&'a mut Vec<InputLiteral>),
434}
435
436impl<'a> InputLiteralContainer<'a> {
437 fn list(list: &'a mut Vec<InputLiteral>) -> Self {
438 InputLiteralContainer::List(list)
439 }
440
441 fn object(argument_name: &'static str, arguments: &'a mut Vec<Argument>) -> Self {
442 InputLiteralContainer::Object {
443 argument_name,
444 arguments,
445 }
446 }
447
448 fn push(self, value: InputLiteral) -> &'a mut InputLiteral {
449 match self {
450 InputLiteralContainer::Object {
451 argument_name: name,
452 arguments,
453 } => {
454 arguments.push(Argument::new(name, value));
455
456 &mut arguments.last_mut().unwrap().value
457 }
458 InputLiteralContainer::List(arguments) => {
459 arguments.push(value);
460
461 arguments.last_mut().unwrap()
462 }
463 }
464 }
465}
466
467pub struct DirectiveBuilder<'a, DirectiveMarker, VariablesFields> {
468 arguments: &'a mut Vec<Argument>,
469 context: BuilderContext<'a>,
470 phantom: PhantomData<fn() -> (DirectiveMarker, VariablesFields)>,
471}
472
473impl<'a, DirectiveMarker, VariablesFields> DirectiveBuilder<'a, DirectiveMarker, VariablesFields> {
474 pub fn argument<ArgumentName>(
479 &'_ mut self,
480 ) -> InputBuilder<'_, DirectiveMarker::ArgumentType, VariablesFields>
481 where
482 DirectiveMarker: schema::HasArgument<ArgumentName>,
483 {
484 InputBuilder {
485 destination: InputLiteralContainer::object(
486 <DirectiveMarker as schema::HasArgument<ArgumentName>>::NAME,
487 self.arguments,
488 ),
489 context: self.context,
490 phantom: PhantomData,
491 }
492 }
493}
494
495pub trait VariableMatch<T> {}
500
501impl<T> VariableMatch<()> for T where T: crate::QueryVariablesFields {}
502
503#[derive(Clone, Copy)]
504struct BuilderContext<'a> {
505 features_enabled: &'a HashSet<String>,
506 variables_used: &'a Sender<&'static str>,
507 recurse_depth: Option<u8>,
508 overall_depth: u16,
509 inline_variables: Option<&'a dyn QueryVariableLiterals>,
510}
511
512impl BuilderContext<'_> {
513 pub fn descend(&self) -> Self {
514 let overall_depth = self.overall_depth + 1;
515
516 assert!(
517 overall_depth < MAX_DEPTH,
518 "Maximum query depth exceeded. Have you forgotten to mark a query as recursive?",
519 );
520
521 Self {
522 overall_depth,
523 ..*self
524 }
525 }
526
527 pub fn recurse(&self) -> Self {
528 Self {
529 recurse_depth: self.recurse_depth.map(|d| d + 1).or(Some(0)),
530 ..self.descend()
531 }
532 }
533}