gurkle_codegen/
schema.rs

1mod graphql_parser_conversion;
2mod json_conversion;
3
4#[cfg(test)]
5mod tests;
6
7use crate::query::UsedTypes;
8use crate::type_qualifiers::GraphqlTypeQualifier;
9use std::collections::HashMap;
10
11pub(crate) const DEFAULT_SCALARS: &[&str] = &["ID", "String", "Int", "Float", "Boolean"];
12
13#[derive(Debug, PartialEq, Clone)]
14struct StoredObjectField {
15    name: String,
16    object: ObjectId,
17}
18
19#[derive(Debug, PartialEq, Clone)]
20pub(crate) struct StoredObject {
21    pub(crate) name: String,
22    pub(crate) fields: Vec<StoredFieldId>,
23    pub(crate) implements_interfaces: Vec<InterfaceId>,
24}
25
26#[derive(Debug, PartialEq, Clone)]
27pub(crate) struct StoredField {
28    pub(crate) name: String,
29    pub(crate) r#type: StoredFieldType,
30    pub(crate) parent: StoredFieldParent,
31    /// `Some(None)` should be interpreted as "deprecated, without reason"
32    pub(crate) deprecation: Option<Option<String>>,
33}
34
35impl StoredField {
36    pub(crate) fn deprecation(&self) -> Option<Option<&str>> {
37        self.deprecation.as_ref().map(|inner| inner.as_deref())
38    }
39}
40
41#[derive(Debug, PartialEq, Clone)]
42pub(crate) enum StoredFieldParent {
43    Object(ObjectId),
44    Interface(InterfaceId),
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
48pub(crate) struct ObjectId(u32);
49
50#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
51pub(crate) struct ObjectFieldId(usize);
52
53#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
54pub(crate) struct InterfaceId(usize);
55
56#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
57pub(crate) struct ScalarId(usize);
58
59#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
60pub(crate) struct UnionId(usize);
61
62#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
63pub(crate) struct EnumId(usize);
64
65#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
66pub(crate) struct InputId(u32);
67
68#[derive(Debug, Clone, Copy, PartialEq)]
69pub(crate) struct StoredFieldId(usize);
70
71#[derive(Debug, Clone, Copy, PartialEq)]
72struct InputFieldId(usize);
73
74#[derive(Debug, Clone, PartialEq)]
75pub(crate) struct StoredInterface {
76    name: String,
77    fields: Vec<StoredFieldId>,
78}
79
80#[derive(Debug, Clone, PartialEq)]
81pub(crate) struct StoredFieldType {
82    pub(crate) id: TypeId,
83    /// An ordered list of qualifiers, from outer to inner.
84    ///
85    /// e.g. `[Int]!` would have `vec![List, Optional]`, but `[Int!]` would have `vec![Optional,
86    /// List]`.
87    pub(crate) qualifiers: Vec<GraphqlTypeQualifier>,
88}
89
90#[derive(Debug, Clone, PartialEq)]
91pub(crate) struct StoredUnion {
92    pub(crate) name: String,
93    pub(crate) variants: Vec<TypeId>,
94}
95
96#[derive(Debug, Clone, PartialEq)]
97pub(crate) struct StoredScalar {
98    pub(crate) name: String,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
102pub(crate) enum TypeId {
103    Object(ObjectId),
104    Scalar(ScalarId),
105    Interface(InterfaceId),
106    Union(UnionId),
107    Enum(EnumId),
108    Input(InputId),
109}
110
111impl TypeId {
112    fn r#enum(id: usize) -> Self {
113        TypeId::Enum(EnumId(id))
114    }
115
116    fn interface(id: usize) -> Self {
117        TypeId::Interface(InterfaceId(id))
118    }
119
120    fn union(id: usize) -> Self {
121        TypeId::Union(UnionId(id))
122    }
123
124    fn object(id: u32) -> Self {
125        TypeId::Object(ObjectId(id))
126    }
127
128    fn input(id: u32) -> Self {
129        TypeId::Input(InputId(id))
130    }
131
132    fn as_interface_id(&self) -> Option<InterfaceId> {
133        match self {
134            TypeId::Interface(id) => Some(*id),
135            _ => None,
136        }
137    }
138
139    fn as_object_id(&self) -> Option<ObjectId> {
140        match self {
141            TypeId::Object(id) => Some(*id),
142            _ => None,
143        }
144    }
145
146    pub(crate) fn as_input_id(&self) -> Option<InputId> {
147        match self {
148            TypeId::Input(id) => Some(*id),
149            _ => None,
150        }
151    }
152
153    pub(crate) fn as_scalar_id(&self) -> Option<ScalarId> {
154        match self {
155            TypeId::Scalar(id) => Some(*id),
156            _ => None,
157        }
158    }
159
160    pub(crate) fn as_enum_id(&self) -> Option<EnumId> {
161        match self {
162            TypeId::Enum(id) => Some(*id),
163            _ => None,
164        }
165    }
166
167    pub(crate) fn name<'a>(&self, schema: &'a Schema) -> &'a str {
168        match self {
169            TypeId::Object(obj) => schema.get_object(*obj).name.as_str(),
170            TypeId::Scalar(s) => schema.get_scalar(*s).name.as_str(),
171            TypeId::Interface(s) => schema.get_interface(*s).name.as_str(),
172            TypeId::Union(s) => schema.get_union(*s).name.as_str(),
173            TypeId::Enum(s) => schema.get_enum(*s).name.as_str(),
174            TypeId::Input(s) => schema.get_input(*s).name.as_str(),
175        }
176    }
177}
178
179#[derive(Debug, Clone, PartialEq)]
180pub(crate) struct StoredEnum {
181    pub(crate) name: String,
182    pub(crate) variants: Vec<String>,
183}
184
185#[derive(Debug, Clone, PartialEq)]
186pub(crate) struct StoredInputFieldType {
187    pub(crate) id: TypeId,
188    pub(crate) qualifiers: Vec<GraphqlTypeQualifier>,
189}
190
191impl StoredInputFieldType {
192    /// A type is indirected if it is a (flat or nested) list type, optional or not.
193    ///
194    /// We use this to determine whether a type needs to be boxed for recursion.
195    pub(crate) fn is_indirected(&self) -> bool {
196        self.qualifiers
197            .iter()
198            .any(|qualifier| qualifier == &GraphqlTypeQualifier::List)
199    }
200
201    pub(crate) fn is_optional(&self) -> bool {
202        self.qualifiers
203            .get(0)
204            .map(|qualifier| !qualifier.is_required())
205            .unwrap_or(true)
206    }
207}
208
209#[derive(Debug, Clone, PartialEq)]
210pub(crate) struct StoredInputType {
211    pub(crate) name: String,
212    pub(crate) fields: Vec<(String, StoredInputFieldType)>,
213}
214
215/// Intermediate representation for a parsed GraphQL schema used during code generation.
216#[derive(Debug, Clone, PartialEq)]
217pub(crate) struct Schema {
218    stored_objects: Vec<StoredObject>,
219    stored_fields: Vec<StoredField>,
220    stored_interfaces: Vec<StoredInterface>,
221    stored_unions: Vec<StoredUnion>,
222    stored_scalars: Vec<StoredScalar>,
223    stored_enums: Vec<StoredEnum>,
224    stored_inputs: Vec<StoredInputType>,
225    names: HashMap<String, TypeId>,
226
227    pub(crate) query_type: Option<ObjectId>,
228    pub(crate) mutation_type: Option<ObjectId>,
229    pub(crate) subscription_type: Option<ObjectId>,
230}
231
232impl Schema {
233    pub(crate) fn new() -> Schema {
234        let mut schema = Schema {
235            stored_objects: Vec::new(),
236            stored_interfaces: Vec::new(),
237            stored_fields: Vec::new(),
238            stored_unions: Vec::new(),
239            stored_scalars: Vec::with_capacity(DEFAULT_SCALARS.len()),
240            stored_enums: Vec::new(),
241            stored_inputs: Vec::new(),
242            names: HashMap::new(),
243            query_type: None,
244            mutation_type: None,
245            subscription_type: None,
246        };
247
248        schema.push_default_scalars();
249
250        schema
251    }
252
253    fn push_default_scalars(&mut self) {
254        for scalar in DEFAULT_SCALARS {
255            let id = self.push_scalar(StoredScalar {
256                name: (*scalar).to_owned(),
257            });
258
259            self.names.insert((*scalar).to_owned(), TypeId::Scalar(id));
260        }
261    }
262
263    fn push_object(&mut self, object: StoredObject) -> ObjectId {
264        let id = ObjectId(self.stored_objects.len() as u32);
265        self.stored_objects.push(object);
266
267        id
268    }
269
270    fn push_interface(&mut self, interface: StoredInterface) -> InterfaceId {
271        let id = InterfaceId(self.stored_interfaces.len());
272
273        self.stored_interfaces.push(interface);
274
275        id
276    }
277
278    fn push_scalar(&mut self, scalar: StoredScalar) -> ScalarId {
279        let id = ScalarId(self.stored_scalars.len());
280
281        self.stored_scalars.push(scalar);
282
283        id
284    }
285
286    fn push_enum(&mut self, enm: StoredEnum) -> EnumId {
287        let id = EnumId(self.stored_enums.len());
288
289        self.stored_enums.push(enm);
290
291        id
292    }
293
294    fn push_field(&mut self, field: StoredField) -> StoredFieldId {
295        let id = StoredFieldId(self.stored_fields.len());
296
297        self.stored_fields.push(field);
298
299        id
300    }
301
302    pub(crate) fn query_type(&self) -> ObjectId {
303        self.query_type
304            .expect("Query operation type must be defined")
305    }
306
307    pub(crate) fn mutation_type(&self) -> Option<ObjectId> {
308        self.mutation_type
309    }
310
311    pub(crate) fn subscription_type(&self) -> Option<ObjectId> {
312        self.subscription_type
313    }
314
315    pub(crate) fn get_interface(&self, interface_id: InterfaceId) -> &StoredInterface {
316        self.stored_interfaces.get(interface_id.0).unwrap()
317    }
318
319    pub(crate) fn get_input(&self, input_id: InputId) -> &StoredInputType {
320        self.stored_inputs.get(input_id.0 as usize).unwrap()
321    }
322
323    pub(crate) fn get_object(&self, object_id: ObjectId) -> &StoredObject {
324        self.stored_objects
325            .get(object_id.0 as usize)
326            .expect("Schema::get_object")
327    }
328
329    pub(crate) fn get_field(&self, field_id: StoredFieldId) -> &StoredField {
330        self.stored_fields.get(field_id.0).unwrap()
331    }
332
333    pub(crate) fn get_enum(&self, enum_id: EnumId) -> &StoredEnum {
334        self.stored_enums.get(enum_id.0).unwrap()
335    }
336
337    pub(crate) fn get_scalar(&self, scalar_id: ScalarId) -> &StoredScalar {
338        self.stored_scalars.get(scalar_id.0).unwrap()
339    }
340
341    pub(crate) fn get_union(&self, union_id: UnionId) -> &StoredUnion {
342        self.stored_unions
343            .get(union_id.0)
344            .expect("Schema::get_union")
345    }
346
347    fn find_interface(&self, interface_name: &str) -> InterfaceId {
348        self.find_type_id(interface_name).as_interface_id().unwrap()
349    }
350
351    pub(crate) fn find_type(&self, type_name: &str) -> Option<TypeId> {
352        self.names.get(type_name).copied()
353    }
354
355    pub(crate) fn objects(&self) -> impl Iterator<Item = (ObjectId, &StoredObject)> {
356        self.stored_objects
357            .iter()
358            .enumerate()
359            .map(|(idx, obj)| (ObjectId(idx as u32), obj))
360    }
361
362    pub(crate) fn inputs(&self) -> impl Iterator<Item = (InputId, &StoredInputType)> {
363        self.stored_inputs
364            .iter()
365            .enumerate()
366            .map(|(idx, obj)| (InputId(idx as u32), obj))
367    }
368
369    fn find_type_id(&self, type_name: &str) -> TypeId {
370        match self.names.get(type_name) {
371            Some(id) => *id,
372            None => {
373                panic!(
374                    "graphql-client-codegen internal error: failed to resolve TypeId for `{}°.",
375                    type_name
376                );
377            }
378        }
379    }
380}
381
382impl StoredInputType {
383    pub(crate) fn used_input_ids_recursive(&self, used_types: &mut UsedTypes, schema: &Schema) {
384        for type_id in self.fields.iter().map(|(_name, ty)| ty.id) {
385            match type_id {
386                TypeId::Input(input_id) => {
387                    if used_types.types.contains(&type_id) {
388                        continue;
389                    } else {
390                        used_types.types.insert(type_id);
391                        let input = schema.get_input(input_id);
392                        input.used_input_ids_recursive(used_types, schema);
393                    }
394                }
395                TypeId::Enum(_) | TypeId::Scalar(_) => {
396                    used_types.types.insert(type_id);
397                }
398                _ => (),
399            }
400        }
401    }
402
403    fn contains_type_without_indirection(&self, input_id: InputId, schema: &Schema) -> bool {
404        // The input type is recursive if any of its members contains it, without indirection
405        self.fields.iter().any(|(_name, field_type)| {
406            // the field is indirected, so no boxing is needed
407            if field_type.is_indirected() {
408                return false;
409            }
410
411            let field_input_id = field_type.id.as_input_id();
412
413            if let Some(field_input_id) = field_input_id {
414                if field_input_id == input_id {
415                    return true;
416                }
417
418                let input = schema.get_input(field_input_id);
419
420                // we check if the other input contains this one (without indirection)
421                input.contains_type_without_indirection(input_id, schema)
422            } else {
423                // the field is not referring to an input type
424                false
425            }
426        })
427    }
428}
429
430pub(crate) fn input_is_recursive_without_indirection(input_id: InputId, schema: &Schema) -> bool {
431    let input = schema.get_input(input_id);
432    input.contains_type_without_indirection(input_id, schema)
433}
434
435impl std::convert::From<gurkle_parser::schema::Document> for Schema {
436    fn from(ast: gurkle_parser::schema::Document) -> Schema {
437        graphql_parser_conversion::build_schema(ast)
438    }
439}
440
441impl std::convert::From<crate::introspection_response::IntrospectionResponse> for Schema {
442    fn from(src: crate::introspection_response::IntrospectionResponse) -> Self {
443        json_conversion::build_schema(src)
444    }
445}
446
447pub(crate) fn resolve_field_type(
448    schema: &Schema,
449    inner: &gurkle_parser::schema::Type,
450) -> StoredFieldType {
451    use crate::type_qualifiers::graphql_parser_depth;
452    use gurkle_parser::schema::Type::*;
453
454    let qualifiers_depth = graphql_parser_depth(inner);
455    let mut qualifiers = Vec::with_capacity(qualifiers_depth);
456
457    let mut inner = inner;
458
459    loop {
460        match inner {
461            ListType(new_inner) => {
462                qualifiers.push(GraphqlTypeQualifier::List);
463                inner = new_inner;
464            }
465            NonNullType(new_inner) => {
466                qualifiers.push(GraphqlTypeQualifier::Required);
467                inner = new_inner;
468            }
469            NamedType(name) => {
470                return StoredFieldType {
471                    id: schema.find_type_id(name),
472                    qualifiers,
473                }
474            }
475        }
476    }
477}
478
479pub(crate) trait ObjectLike {
480    fn name(&self) -> &str;
481
482    fn get_field_by_name<'a>(
483        &'a self,
484        name: &str,
485        schema: &'a Schema,
486    ) -> Option<(StoredFieldId, &'a StoredField)>;
487}
488
489impl ObjectLike for StoredObject {
490    fn name(&self) -> &str {
491        &self.name
492    }
493
494    fn get_field_by_name<'a>(
495        &'a self,
496        name: &str,
497        schema: &'a Schema,
498    ) -> Option<(StoredFieldId, &'a StoredField)> {
499        self.fields
500            .iter()
501            .map(|field_id| (*field_id, schema.get_field(*field_id)))
502            .find(|(_, f)| f.name == name)
503    }
504}
505
506impl ObjectLike for StoredInterface {
507    fn name(&self) -> &str {
508        &self.name
509    }
510
511    fn get_field_by_name<'a>(
512        &'a self,
513        name: &str,
514        schema: &'a Schema,
515    ) -> Option<(StoredFieldId, &'a StoredField)> {
516        self.fields
517            .iter()
518            .map(|field_id| (*field_id, schema.get_field(*field_id)))
519            .find(|(_, field)| field.name == name)
520    }
521}