graphql_codegen_rust/
parser.rs

1use std::collections::HashMap;
2
3use crate::introspection::{Introspector, Schema as IntrospectionSchema};
4
5#[derive(Debug, Clone)]
6#[allow(dead_code)]
7pub struct ParsedSchema {
8    pub types: HashMap<String, ParsedType>,
9    pub enums: HashMap<String, ParsedEnum>,
10    pub scalars: Vec<String>,
11}
12
13#[derive(Debug, Clone)]
14pub enum TypeKind {
15    Object,
16    Interface,
17    Union,
18}
19
20#[derive(Debug, Clone)]
21pub struct ParsedType {
22    #[allow(dead_code)]
23    pub name: String,
24    pub kind: TypeKind,
25    pub fields: Vec<ParsedField>,
26    #[allow(dead_code)]
27    pub description: Option<String>,
28    #[allow(dead_code)]
29    pub interfaces: Vec<String>, // For objects and interfaces: implemented interfaces
30    #[allow(dead_code)]
31    pub union_members: Vec<String>, // For unions: member types
32}
33
34#[derive(Debug, Clone)]
35#[allow(dead_code)]
36pub struct ParsedField {
37    pub name: String,
38    pub field_type: FieldType,
39    pub description: Option<String>,
40    pub is_nullable: bool,
41    pub is_list: bool,
42}
43
44#[derive(Debug, Clone)]
45#[allow(dead_code)]
46pub enum FieldType {
47    Scalar(String),
48    Reference(String),
49    Enum(String),
50}
51
52#[derive(Debug, Clone)]
53#[allow(dead_code)]
54pub struct ParsedEnum {
55    pub name: String,
56    pub values: Vec<String>,
57    pub description: Option<String>,
58}
59
60pub struct GraphQLParser {
61    introspector: Introspector,
62}
63
64#[allow(dead_code)]
65impl Default for GraphQLParser {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71#[allow(dead_code)]
72impl GraphQLParser {
73    pub fn new() -> Self {
74        Self {
75            introspector: Introspector::new(),
76        }
77    }
78
79    /// Parse schema from introspection
80    pub async fn parse_from_introspection(
81        &self,
82        url: &str,
83        headers: &HashMap<String, String>,
84    ) -> anyhow::Result<ParsedSchema> {
85        let schema = self.introspector.introspect_schema(url, headers).await?;
86        self.parse_schema(schema)
87    }
88
89    /// Parse schema from SDL string
90    pub fn parse_from_sdl(&self, sdl: &str) -> anyhow::Result<ParsedSchema> {
91        use graphql_parser::parse_schema;
92
93        let document =
94            parse_schema(sdl).map_err(|e| anyhow::anyhow!("Failed to parse SDL: {}", e))?;
95
96        self.parse_sdl_document(document)
97    }
98
99    /// Parse schema from SDL string (simplified version)
100    pub fn parse_from_sdl_simple(&self, sdl: &str) -> anyhow::Result<ParsedSchema> {
101        // Alias for parse_from_sdl for backward compatibility
102        self.parse_from_sdl(sdl)
103    }
104
105    fn parse_sdl_document<'a>(
106        &self,
107        document: graphql_parser::schema::Document<'a, &'a str>,
108    ) -> anyhow::Result<ParsedSchema> {
109        let mut types = HashMap::new();
110        let mut enums = HashMap::new();
111        let mut scalars = Vec::new();
112
113        for definition in document.definitions {
114            match definition {
115                graphql_parser::schema::Definition::TypeDefinition(type_def) => {
116                    match type_def {
117                        graphql_parser::schema::TypeDefinition::Object(obj) => {
118                            if let Some(parsed_type) = self.parse_sdl_object_type(&obj) {
119                                types.insert(obj.name.to_string(), parsed_type);
120                            }
121                        }
122                        graphql_parser::schema::TypeDefinition::Enum(enum_def) => {
123                            if let Some(parsed_enum) = self.parse_sdl_enum_type(&enum_def) {
124                                enums.insert(enum_def.name.to_string(), parsed_enum);
125                            }
126                        }
127                        graphql_parser::schema::TypeDefinition::Scalar(scalar) => {
128                            scalars.push(scalar.name.to_string());
129                        }
130                        graphql_parser::schema::TypeDefinition::Interface(interface) => {
131                            if let Some(parsed_type) = self.parse_sdl_interface_type(&interface) {
132                                types.insert(interface.name.to_string(), parsed_type);
133                            }
134                        }
135                        graphql_parser::schema::TypeDefinition::Union(union_def) => {
136                            if let Some(parsed_type) = self.parse_sdl_union_type(&union_def) {
137                                types.insert(union_def.name.to_string(), parsed_type);
138                            }
139                        }
140                        graphql_parser::schema::TypeDefinition::InputObject(_) => {
141                            // Skip input objects for now - they don't affect ORM generation
142                        }
143                    }
144                }
145                graphql_parser::schema::Definition::SchemaDefinition(_)
146                | graphql_parser::schema::Definition::DirectiveDefinition(_) => {
147                    // Skip schema and directive definitions for ORM generation
148                }
149                graphql_parser::schema::Definition::TypeExtension(_) => {
150                    // Skip type extensions for now
151                }
152            }
153        }
154
155        Ok(ParsedSchema {
156            types,
157            enums,
158            scalars,
159        })
160    }
161
162    fn parse_schema(&self, schema: IntrospectionSchema) -> anyhow::Result<ParsedSchema> {
163        let mut types = HashMap::new();
164        let mut enums = HashMap::new();
165        let mut scalars = Vec::new();
166
167        for type_def in schema.types {
168            if let Some(name) = &type_def.name {
169                // Skip introspection types and built-in scalars
170                if name.starts_with("__")
171                    || name == "String"
172                    || name == "Int"
173                    || name == "Float"
174                    || name == "Boolean"
175                    || name == "ID"
176                {
177                    if matches!(type_def.kind, crate::introspection::TypeKind::Scalar)
178                        && !name.starts_with("__")
179                    {
180                        scalars.push(name.clone());
181                    }
182                    continue;
183                }
184
185                match type_def.kind {
186                    crate::introspection::TypeKind::Object => {
187                        if let Some(parsed_type) = self.parse_object_type(&type_def) {
188                            types.insert(name.clone(), parsed_type);
189                        }
190                    }
191                    crate::introspection::TypeKind::Interface => {
192                        if let Some(parsed_type) = self.parse_interface_type(&type_def) {
193                            types.insert(name.clone(), parsed_type);
194                        }
195                    }
196                    crate::introspection::TypeKind::Union => {
197                        if let Some(parsed_type) = self.parse_union_type(&type_def) {
198                            types.insert(name.clone(), parsed_type);
199                        }
200                    }
201                    crate::introspection::TypeKind::Enum => {
202                        if let Some(parsed_enum) = self.parse_enum_type(&type_def) {
203                            enums.insert(name.clone(), parsed_enum);
204                        }
205                    }
206                    crate::introspection::TypeKind::Scalar => {
207                        scalars.push(name.clone());
208                    }
209                    _ => {
210                        // Skip input objects and other types for ORM generation
211                    }
212                }
213            }
214        }
215
216        Ok(ParsedSchema {
217            types,
218            enums,
219            scalars,
220        })
221    }
222
223    // fn parse_document is removed for now - focusing on introspection
224    // TODO: Re-implement SDL parsing when needed
225
226    fn parse_object_type(&self, type_def: &crate::introspection::Type) -> Option<ParsedType> {
227        let name = type_def.name.as_ref()?;
228        let mut fields = Vec::new();
229
230        if let Some(introspection_fields) = &type_def.fields {
231            for field in introspection_fields {
232                if let Some(parsed_field) = self.parse_field(field) {
233                    fields.push(parsed_field);
234                }
235            }
236        }
237
238        let interfaces = type_def
239            .interfaces
240            .as_ref()
241            .map(|interfaces| interfaces.iter().filter_map(|i| i.name.clone()).collect())
242            .unwrap_or_default();
243
244        Some(ParsedType {
245            name: name.clone(),
246            kind: TypeKind::Object,
247            fields,
248            description: type_def.description.clone(),
249            interfaces,
250            union_members: vec![],
251        })
252    }
253
254    fn parse_interface_type(&self, type_def: &crate::introspection::Type) -> Option<ParsedType> {
255        let name = type_def.name.as_ref()?;
256        let mut fields = Vec::new();
257
258        if let Some(type_fields) = &type_def.fields {
259            for field in type_fields {
260                if let Some(parsed_field) = self.parse_field(field) {
261                    fields.push(parsed_field);
262                }
263            }
264        }
265
266        let interfaces = type_def
267            .interfaces
268            .as_ref()
269            .map(|interfaces| interfaces.iter().filter_map(|i| i.name.clone()).collect())
270            .unwrap_or_default();
271
272        Some(ParsedType {
273            name: name.clone(),
274            kind: TypeKind::Interface,
275            fields,
276            description: type_def.description.clone(),
277            interfaces,
278            union_members: vec![],
279        })
280    }
281
282    fn parse_union_type(&self, type_def: &crate::introspection::Type) -> Option<ParsedType> {
283        let name = type_def.name.as_ref()?;
284
285        // For unions, get the possible types (union members)
286        let union_members = type_def
287            .possible_types
288            .as_ref()
289            .map(|types| types.iter().filter_map(|t| t.name.clone()).collect())
290            .unwrap_or_default();
291
292        Some(ParsedType {
293            name: name.clone(),
294            kind: TypeKind::Union,
295            fields: vec![], // Union types don't have fields
296            description: type_def.description.clone(),
297            interfaces: vec![],
298            union_members,
299        })
300    }
301
302    fn parse_field(&self, field: &crate::introspection::Field) -> Option<ParsedField> {
303        let (field_type, is_nullable, is_list) = self.parse_type_ref(&field.type_)?;
304
305        Some(ParsedField {
306            name: field.name.clone(),
307            field_type,
308            description: field.description.clone(),
309            is_nullable,
310            is_list,
311        })
312    }
313
314    #[allow(clippy::only_used_in_recursion)]
315    fn parse_type_ref(
316        &self,
317        type_ref: &crate::introspection::TypeRef,
318    ) -> Option<(FieldType, bool, bool)> {
319        match type_ref.kind {
320            Some(crate::introspection::TypeKind::NonNull) => {
321                if let Some(of_type) = &type_ref.of_type {
322                    let (field_type, _, is_list) = self.parse_type_ref(of_type)?;
323                    Some((field_type, false, is_list))
324                } else {
325                    None
326                }
327            }
328            Some(crate::introspection::TypeKind::List) => {
329                if let Some(of_type) = &type_ref.of_type {
330                    let (field_type, is_nullable, _) = self.parse_type_ref(of_type)?;
331                    Some((field_type, is_nullable, true))
332                } else {
333                    None
334                }
335            }
336            _ => {
337                if let Some(name) = &type_ref.name {
338                    let field_type = match name.as_str() {
339                        "String" | "Int" | "Float" | "Boolean" => FieldType::Scalar(name.clone()),
340                        "ID" => FieldType::Scalar("ID".to_string()),
341                        _ => {
342                            // Check if it's an enum (this is a simplification)
343                            // In a real implementation, we'd need to check the schema
344                            FieldType::Reference(name.clone())
345                        }
346                    };
347                    Some((field_type, true, false))
348                } else {
349                    None
350                }
351            }
352        }
353    }
354
355    fn parse_enum_type(&self, type_def: &crate::introspection::Type) -> Option<ParsedEnum> {
356        let name = type_def.name.as_ref()?;
357        let mut values = Vec::new();
358
359        if let Some(enum_values) = &type_def.enum_values {
360            for value in enum_values {
361                values.push(value.name.clone());
362            }
363        }
364
365        Some(ParsedEnum {
366            name: name.clone(),
367            values,
368            description: type_def.description.clone(),
369        })
370    }
371
372    // SDL parsing helper methods
373    fn parse_sdl_object_type<'a>(
374        &self,
375        obj: &graphql_parser::schema::ObjectType<'a, &'a str>,
376    ) -> Option<ParsedType> {
377        let mut fields = Vec::new();
378
379        for field in &obj.fields {
380            if let Some(parsed_field) = self.parse_sdl_field(field) {
381                fields.push(parsed_field);
382            }
383        }
384
385        let interfaces = obj
386            .implements_interfaces
387            .iter()
388            .map(|name| name.to_string())
389            .collect();
390
391        Some(ParsedType {
392            name: obj.name.to_string(),
393            kind: TypeKind::Object,
394            fields,
395            description: obj.description.as_ref().map(|s| s.to_string()),
396            interfaces,
397            union_members: vec![],
398        })
399    }
400
401    fn parse_sdl_interface_type<'a>(
402        &self,
403        interface: &graphql_parser::schema::InterfaceType<'a, &'a str>,
404    ) -> Option<ParsedType> {
405        let mut fields = Vec::new();
406
407        for field in &interface.fields {
408            if let Some(parsed_field) = self.parse_sdl_field(field) {
409                fields.push(parsed_field);
410            }
411        }
412
413        let interfaces = interface
414            .implements_interfaces
415            .iter()
416            .map(|name| name.to_string())
417            .collect();
418
419        Some(ParsedType {
420            name: interface.name.to_string(),
421            kind: TypeKind::Interface,
422            fields,
423            description: interface.description.as_ref().map(|s| s.to_string()),
424            interfaces,
425            union_members: vec![],
426        })
427    }
428
429    fn parse_sdl_union_type<'a>(
430        &self,
431        union_def: &graphql_parser::schema::UnionType<'a, &'a str>,
432    ) -> Option<ParsedType> {
433        // For unions, we store union members separately
434        let union_members = union_def
435            .types
436            .iter()
437            .map(|name| name.to_string())
438            .collect();
439
440        Some(ParsedType {
441            name: union_def.name.to_string(),
442            kind: TypeKind::Union,
443            fields: vec![], // Union types don't have fields in GraphQL
444            description: union_def.description.as_ref().map(|s| s.to_string()),
445            interfaces: vec![],
446            union_members,
447        })
448    }
449
450    fn parse_sdl_enum_type<'a>(
451        &self,
452        enum_def: &graphql_parser::schema::EnumType<'a, &'a str>,
453    ) -> Option<ParsedEnum> {
454        let values = enum_def
455            .values
456            .iter()
457            .map(|value| value.name.to_string())
458            .collect();
459
460        Some(ParsedEnum {
461            name: enum_def.name.to_string(),
462            values,
463            description: enum_def.description.as_ref().map(|s| s.to_string()),
464        })
465    }
466
467    fn parse_sdl_field<'a>(
468        &self,
469        field: &graphql_parser::schema::Field<'a, &'a str>,
470    ) -> Option<ParsedField> {
471        let (field_type, is_nullable, is_list) = self.parse_sdl_type(&field.field_type)?;
472
473        Some(ParsedField {
474            name: field.name.to_string(),
475            field_type,
476            description: field.description.as_ref().map(|s| s.to_string()),
477            is_nullable,
478            is_list,
479        })
480    }
481
482    #[allow(clippy::only_used_in_recursion)]
483    fn parse_sdl_type<'a>(
484        &self,
485        field_type: &graphql_parser::schema::Type<'a, &'a str>,
486    ) -> Option<(FieldType, bool, bool)> {
487        match field_type {
488            graphql_parser::schema::Type::NamedType(name) => {
489                let field_type = match *name {
490                    "ID" | "String" | "Int" | "Float" | "Boolean" => {
491                        FieldType::Scalar(name.to_string())
492                    }
493                    _ => FieldType::Reference(name.to_string()),
494                };
495                Some((field_type, true, false)) // Named types are nullable by default
496            }
497            graphql_parser::schema::Type::ListType(inner_type) => {
498                if let Some((inner_field_type, _, _)) = self.parse_sdl_type(inner_type) {
499                    Some((inner_field_type, true, true))
500                } else {
501                    None
502                }
503            }
504            graphql_parser::schema::Type::NonNullType(inner_type) => {
505                if let Some((inner_field_type, _, is_list)) = self.parse_sdl_type(inner_type) {
506                    Some((inner_field_type, false, is_list))
507                } else {
508                    None
509                }
510            }
511        }
512    }
513}