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>, #[allow(dead_code)]
31 pub union_members: Vec<String>, }
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 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 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 pub fn parse_from_sdl_simple(&self, sdl: &str) -> anyhow::Result<ParsedSchema> {
101 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 }
143 }
144 }
145 graphql_parser::schema::Definition::SchemaDefinition(_)
146 | graphql_parser::schema::Definition::DirectiveDefinition(_) => {
147 }
149 graphql_parser::schema::Definition::TypeExtension(_) => {
150 }
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 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 }
212 }
213 }
214 }
215
216 Ok(ParsedSchema {
217 types,
218 enums,
219 scalars,
220 })
221 }
222
223 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 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![], 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 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 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 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![], 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)) }
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}