1use serde_json::Value;
38
39use super::{
40 enum_validator::EnumValidator,
41 ir::{
42 AuthoringIR, AutoParams, IRArgument, IRField, IRInputField, IRInputType, IRInterface,
43 IRMutation, IRQuery, IRScalar, IRSubscription, IRType, IRUnion, MutationOperation,
44 },
45};
46use crate::{
47 error::{FraiseQLError, Result},
48 schema::GraphQLValue,
49};
50
51pub struct SchemaParser {
55 }
57
58impl SchemaParser {
59 #[must_use]
61 pub const fn new() -> Self {
62 Self {}
63 }
64
65 pub fn parse(&self, schema_json: &str) -> Result<AuthoringIR> {
90 let value: Value = serde_json::from_str(schema_json).map_err(|e| FraiseQLError::Parse {
92 message: format!("Failed to parse schema JSON: {e}"),
93 location: "root".to_string(),
94 })?;
95
96 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
97 message: "Schema must be a JSON object".to_string(),
98 location: "root".to_string(),
99 })?;
100
101 let types = obj.get("types").map_or(Ok(vec![]), |v| self.parse_types(v))?;
102 let queries = obj.get("queries").map_or(Ok(vec![]), |v| self.parse_queries(v))?;
103 let mutations = obj.get("mutations").map_or(Ok(vec![]), |v| self.parse_mutations(v))?;
104 let subscriptions =
105 obj.get("subscriptions").map_or(Ok(vec![]), |v| self.parse_subscriptions(v))?;
106 let fact_tables = obj
107 .get("fact_tables")
108 .and_then(Value::as_object)
109 .map_or_else::<Result<_>, _, _>(
110 || Ok(std::collections::HashMap::new()),
111 |o| {
112 o.iter()
113 .map(|(k, v)| {
114 let meta: crate::compiler::fact_table::FactTableMetadata =
115 serde_json::from_value(v.clone()).map_err(|e| {
116 FraiseQLError::Parse {
117 message: format!(
118 "Invalid fact table metadata for '{}': {e}",
119 k
120 ),
121 location: format!("fact_tables.{}", k),
122 }
123 })?;
124 Ok((k.clone(), meta))
125 })
126 .collect()
127 },
128 )?;
129 let enums = obj.get("enums").map_or(Ok(vec![]), EnumValidator::parse_enums)?;
130 let interfaces = obj.get("interfaces").map_or(Ok(vec![]), |v| self.parse_interfaces(v))?;
131 let unions = obj.get("unions").map_or(Ok(vec![]), |v| self.parse_unions(v))?;
132 let input_types =
133 obj.get("input_types").map_or(Ok(vec![]), |v| self.parse_input_types(v))?;
134 let scalars = obj.get("scalars").map_or(Ok(vec![]), |v| self.parse_scalars(v))?;
135
136 if obj.contains_key("fragments") {
138 tracing::warn!(
139 "'fragments' feature in schema is not yet supported and will be ignored"
140 );
141 }
142
143 Ok(AuthoringIR {
144 types,
145 enums,
146 interfaces,
147 unions,
148 input_types,
149 scalars,
150 queries,
151 mutations,
152 subscriptions,
153 fact_tables,
154 })
155 }
156
157 fn parse_types(&self, value: &Value) -> Result<Vec<IRType>> {
158 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
159 message: "types must be an array".to_string(),
160 location: "types".to_string(),
161 })?;
162
163 array
164 .iter()
165 .enumerate()
166 .map(|(i, type_val)| self.parse_type(type_val, i))
167 .collect()
168 }
169
170 fn parse_type(&self, value: &Value, index: usize) -> Result<IRType> {
171 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
172 message: format!("Type at index {index} must be an object"),
173 location: format!("types[{index}]"),
174 })?;
175
176 let name = obj
177 .get("name")
178 .and_then(|v| v.as_str())
179 .ok_or_else(|| FraiseQLError::Parse {
180 message: format!("Type at index {index} missing 'name' field"),
181 location: format!("types[{index}].name"),
182 })?
183 .to_string();
184
185 let fields = if let Some(fields_val) = obj.get("fields") {
186 self.parse_fields(fields_val, &name)?
187 } else {
188 Vec::new()
189 };
190
191 Ok(IRType {
192 name,
193 fields,
194 sql_source: obj.get("sql_source").and_then(|v| v.as_str()).map(String::from),
195 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
196 })
197 }
198
199 fn parse_fields(&self, value: &Value, type_name: &str) -> Result<Vec<IRField>> {
200 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
201 message: format!("fields for type {type_name} must be an array"),
202 location: format!("{type_name}.fields"),
203 })?;
204
205 array
206 .iter()
207 .enumerate()
208 .map(|(i, field_val)| self.parse_field(field_val, type_name, i))
209 .collect()
210 }
211
212 fn parse_field(&self, value: &Value, type_name: &str, index: usize) -> Result<IRField> {
213 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
214 message: format!("Field at index {index} in type {type_name} must be an object"),
215 location: format!("{type_name}.fields[{index}]"),
216 })?;
217
218 let name = obj
219 .get("name")
220 .and_then(|v| v.as_str())
221 .ok_or_else(|| FraiseQLError::Parse {
222 message: format!("Field at index {index} in type {type_name} missing 'name'"),
223 location: format!("{type_name}.fields[{index}].name"),
224 })?
225 .to_string();
226
227 let field_type = obj
228 .get("type")
229 .and_then(|v| v.as_str())
230 .ok_or_else(|| FraiseQLError::Parse {
231 message: format!("Field '{name}' in type {type_name} missing 'type'"),
232 location: format!("{type_name}.fields.{name}.type"),
233 })?
234 .to_string();
235
236 let nullable = obj.get("nullable").and_then(|v| v.as_bool()).unwrap_or(true);
237
238 Ok(IRField {
239 name,
240 field_type,
241 nullable,
242 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
243 sql_column: obj.get("sql_column").and_then(|v| v.as_str()).map(String::from),
244 })
245 }
246
247 fn parse_queries(&self, value: &Value) -> Result<Vec<IRQuery>> {
248 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
249 message: "queries must be an array".to_string(),
250 location: "queries".to_string(),
251 })?;
252
253 array
254 .iter()
255 .enumerate()
256 .map(|(i, query_val)| self.parse_query(query_val, i))
257 .collect()
258 }
259
260 fn parse_query(&self, value: &Value, index: usize) -> Result<IRQuery> {
261 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
262 message: format!("Query at index {index} must be an object"),
263 location: format!("queries[{index}]"),
264 })?;
265
266 let name = obj
267 .get("name")
268 .and_then(|v| v.as_str())
269 .ok_or_else(|| FraiseQLError::Parse {
270 message: format!("Query at index {index} missing 'name'"),
271 location: format!("queries[{index}].name"),
272 })?
273 .to_string();
274
275 let return_type = obj
276 .get("return_type")
277 .and_then(|v| v.as_str())
278 .ok_or_else(|| FraiseQLError::Parse {
279 message: format!("Query '{name}' missing 'return_type'"),
280 location: format!("queries.{name}.return_type"),
281 })?
282 .to_string();
283
284 let returns_list = obj.get("returns_list").and_then(|v| v.as_bool()).unwrap_or(false);
285
286 let nullable = obj.get("nullable").and_then(|v| v.as_bool()).unwrap_or(false);
287
288 let arguments = if let Some(args_val) = obj.get("arguments") {
289 self.parse_arguments(args_val, &name)?
290 } else {
291 Vec::new()
292 };
293
294 let auto_params = if let Some(auto_val) = obj.get("auto_params") {
295 self.parse_auto_params(auto_val)?
296 } else {
297 AutoParams::default()
298 };
299
300 Ok(IRQuery {
301 name,
302 return_type,
303 returns_list,
304 nullable,
305 arguments,
306 sql_source: obj.get("sql_source").and_then(|v| v.as_str()).map(String::from),
307 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
308 auto_params,
309 })
310 }
311
312 fn parse_mutations(&self, value: &Value) -> Result<Vec<IRMutation>> {
313 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
314 message: "mutations must be an array".to_string(),
315 location: "mutations".to_string(),
316 })?;
317
318 array
319 .iter()
320 .enumerate()
321 .map(|(i, mutation_val)| self.parse_mutation(mutation_val, i))
322 .collect()
323 }
324
325 fn parse_mutation(&self, value: &Value, index: usize) -> Result<IRMutation> {
326 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
327 message: format!("Mutation at index {index} must be an object"),
328 location: format!("mutations[{index}]"),
329 })?;
330
331 let name = obj
332 .get("name")
333 .and_then(|v| v.as_str())
334 .ok_or_else(|| FraiseQLError::Parse {
335 message: format!("Mutation at index {index} missing 'name'"),
336 location: format!("mutations[{index}].name"),
337 })?
338 .to_string();
339
340 let return_type = obj
341 .get("return_type")
342 .and_then(|v| v.as_str())
343 .ok_or_else(|| FraiseQLError::Parse {
344 message: format!("Mutation '{name}' missing 'return_type'"),
345 location: format!("mutations.{name}.return_type"),
346 })?
347 .to_string();
348
349 let nullable = obj.get("nullable").and_then(|v| v.as_bool()).unwrap_or(false);
350
351 let arguments = if let Some(args_val) = obj.get("arguments") {
352 self.parse_arguments(args_val, &name)?
353 } else {
354 Vec::new()
355 };
356
357 let operation = if let Some(s) = obj.get("operation").and_then(|v| v.as_str()) {
358 match s.to_lowercase().as_str() {
359 "create" => MutationOperation::Create,
360 "update" => MutationOperation::Update,
361 "delete" => MutationOperation::Delete,
362 "custom" => MutationOperation::Custom,
363 other => {
364 return Err(FraiseQLError::Parse {
365 message: format!(
366 "Mutation '{name}' has unknown operation {other:?}. \
367 Valid values are: create, update, delete, custom"
368 ),
369 location: format!("mutations.{name}.operation"),
370 });
371 },
372 }
373 } else {
374 MutationOperation::Custom
375 };
376
377 Ok(IRMutation {
378 name,
379 return_type,
380 nullable,
381 arguments,
382 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
383 operation,
384 })
385 }
386
387 fn parse_subscriptions(&self, value: &Value) -> Result<Vec<IRSubscription>> {
388 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
389 message: "subscriptions must be an array".to_string(),
390 location: "subscriptions".to_string(),
391 })?;
392
393 array
394 .iter()
395 .enumerate()
396 .map(|(i, sub_val)| self.parse_subscription(sub_val, i))
397 .collect()
398 }
399
400 fn parse_subscription(&self, value: &Value, index: usize) -> Result<IRSubscription> {
401 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
402 message: format!("Subscription at index {index} must be an object"),
403 location: format!("subscriptions[{index}]"),
404 })?;
405
406 let name = obj
407 .get("name")
408 .and_then(|v| v.as_str())
409 .ok_or_else(|| FraiseQLError::Parse {
410 message: format!("Subscription at index {index} missing 'name'"),
411 location: format!("subscriptions[{index}].name"),
412 })?
413 .to_string();
414
415 let return_type = obj
416 .get("return_type")
417 .and_then(|v| v.as_str())
418 .ok_or_else(|| FraiseQLError::Parse {
419 message: format!("Subscription '{name}' missing 'return_type'"),
420 location: format!("subscriptions.{name}.return_type"),
421 })?
422 .to_string();
423
424 let arguments = if let Some(args_val) = obj.get("arguments") {
425 self.parse_arguments(args_val, &name)?
426 } else {
427 Vec::new()
428 };
429
430 Ok(IRSubscription {
431 name,
432 return_type,
433 arguments,
434 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
435 })
436 }
437
438 fn parse_arguments(&self, value: &Value, parent_name: &str) -> Result<Vec<IRArgument>> {
439 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
440 message: format!("arguments for '{parent_name}' must be an array"),
441 location: format!("{parent_name}.arguments"),
442 })?;
443
444 array
445 .iter()
446 .enumerate()
447 .map(|(i, arg_val)| self.parse_argument(arg_val, parent_name, i))
448 .collect()
449 }
450
451 fn parse_argument(&self, value: &Value, parent_name: &str, index: usize) -> Result<IRArgument> {
452 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
453 message: format!("Argument at index {index} for '{parent_name}' must be an object"),
454 location: format!("{parent_name}.arguments[{index}]"),
455 })?;
456
457 let name = obj
458 .get("name")
459 .and_then(|v| v.as_str())
460 .ok_or_else(|| FraiseQLError::Parse {
461 message: format!("Argument at index {index} for '{parent_name}' missing 'name'"),
462 location: format!("{parent_name}.arguments[{index}].name"),
463 })?
464 .to_string();
465
466 let arg_type = obj
467 .get("type")
468 .and_then(|v| v.as_str())
469 .ok_or_else(|| FraiseQLError::Parse {
470 message: format!("Argument '{name}' for '{parent_name}' missing 'type'"),
471 location: format!("{parent_name}.arguments.{name}.type"),
472 })?
473 .to_string();
474
475 let nullable = obj.get("nullable").and_then(|v| v.as_bool()).unwrap_or(true);
476
477 Ok(IRArgument {
478 name,
479 arg_type,
480 nullable,
481 default_value: obj.get("default_value").map(GraphQLValue::from_json).transpose()?,
482 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
483 })
484 }
485
486 fn parse_auto_params(&self, value: &Value) -> Result<AutoParams> {
487 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
488 message: "auto_params must be an object".to_string(),
489 location: "auto_params".to_string(),
490 })?;
491
492 Ok(AutoParams {
493 has_where: obj.get("has_where").and_then(|v| v.as_bool()).unwrap_or(false),
494 has_order_by: obj.get("has_order_by").and_then(|v| v.as_bool()).unwrap_or(false),
495 has_limit: obj.get("has_limit").and_then(|v| v.as_bool()).unwrap_or(false),
496 has_offset: obj.get("has_offset").and_then(|v| v.as_bool()).unwrap_or(false),
497 })
498 }
499
500 fn parse_interfaces(&self, value: &Value) -> Result<Vec<IRInterface>> {
501 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
502 message: "interfaces must be an array".to_string(),
503 location: "interfaces".to_string(),
504 })?;
505
506 array
507 .iter()
508 .enumerate()
509 .map(|(i, interface_val)| self.parse_interface(interface_val, i))
510 .collect()
511 }
512
513 fn parse_interface(&self, value: &Value, index: usize) -> Result<IRInterface> {
514 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
515 message: format!("Interface at index {index} must be an object"),
516 location: format!("interfaces[{index}]"),
517 })?;
518
519 let name = obj
520 .get("name")
521 .and_then(|v| v.as_str())
522 .ok_or_else(|| FraiseQLError::Parse {
523 message: format!("Interface at index {index} missing 'name' field"),
524 location: format!("interfaces[{index}].name"),
525 })?
526 .to_string();
527
528 let fields = if let Some(fields_val) = obj.get("fields") {
529 self.parse_fields(fields_val, &name)?
530 } else {
531 Vec::new()
532 };
533
534 Ok(IRInterface {
535 name,
536 fields,
537 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
538 })
539 }
540
541 fn parse_unions(&self, value: &Value) -> Result<Vec<IRUnion>> {
542 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
543 message: "unions must be an array".to_string(),
544 location: "unions".to_string(),
545 })?;
546
547 array
548 .iter()
549 .enumerate()
550 .map(|(i, union_val)| self.parse_union(union_val, i))
551 .collect()
552 }
553
554 fn parse_union(&self, value: &Value, index: usize) -> Result<IRUnion> {
555 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
556 message: format!("Union at index {index} must be an object"),
557 location: format!("unions[{index}]"),
558 })?;
559
560 let name = obj
561 .get("name")
562 .and_then(|v| v.as_str())
563 .ok_or_else(|| FraiseQLError::Parse {
564 message: format!("Union at index {index} missing 'name' field"),
565 location: format!("unions[{index}].name"),
566 })?
567 .to_string();
568
569 let types = if let Some(types_val) = obj.get("types") {
570 let array = types_val.as_array().ok_or_else(|| FraiseQLError::Parse {
571 message: format!("'types' for union {name} must be an array"),
572 location: format!("unions.{name}.types"),
573 })?;
574
575 array
576 .iter()
577 .enumerate()
578 .map(|(i, type_val)| {
579 type_val.as_str().ok_or_else(|| FraiseQLError::Parse {
580 message: format!("Type at index {i} in union {name} must be a string"),
581 location: format!("unions.{name}.types[{i}]"),
582 })
583 })
584 .collect::<Result<Vec<_>>>()?
585 .iter()
586 .map(|s| (*s).to_string())
587 .collect()
588 } else {
589 Vec::new()
590 };
591
592 Ok(IRUnion {
593 name,
594 types,
595 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
596 })
597 }
598
599 fn parse_input_types(&self, value: &Value) -> Result<Vec<IRInputType>> {
600 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
601 message: "input_types must be an array".to_string(),
602 location: "input_types".to_string(),
603 })?;
604
605 array
606 .iter()
607 .enumerate()
608 .map(|(i, input_type_val)| self.parse_input_type(input_type_val, i))
609 .collect()
610 }
611
612 fn parse_input_type(&self, value: &Value, index: usize) -> Result<IRInputType> {
613 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
614 message: format!("Input type at index {index} must be an object"),
615 location: format!("input_types[{index}]"),
616 })?;
617
618 let name = obj
619 .get("name")
620 .and_then(|v| v.as_str())
621 .ok_or_else(|| FraiseQLError::Parse {
622 message: format!("Input type at index {index} missing 'name' field"),
623 location: format!("input_types[{index}].name"),
624 })?
625 .to_string();
626
627 let fields = if let Some(fields_val) = obj.get("fields") {
628 self.parse_input_fields(fields_val, &name)?
629 } else {
630 Vec::new()
631 };
632
633 Ok(IRInputType {
634 name,
635 fields,
636 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
637 })
638 }
639
640 fn parse_input_fields(&self, value: &Value, type_name: &str) -> Result<Vec<IRInputField>> {
641 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
642 message: format!("fields for input type {type_name} must be an array"),
643 location: format!("{type_name}.fields"),
644 })?;
645
646 array
647 .iter()
648 .enumerate()
649 .map(|(i, field_val)| self.parse_input_field(field_val, type_name, i))
650 .collect()
651 }
652
653 fn parse_input_field(
654 &self,
655 value: &Value,
656 type_name: &str,
657 index: usize,
658 ) -> Result<IRInputField> {
659 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
660 message: format!("Input field at index {index} in type {type_name} must be an object"),
661 location: format!("{type_name}.fields[{index}]"),
662 })?;
663
664 let name = obj
665 .get("name")
666 .and_then(|v| v.as_str())
667 .ok_or_else(|| FraiseQLError::Parse {
668 message: format!(
669 "Input field at index {index} in type {type_name} missing 'name'"
670 ),
671 location: format!("{type_name}.fields[{index}].name"),
672 })?
673 .to_string();
674
675 let field_type = obj
676 .get("type")
677 .and_then(|v| v.as_str())
678 .ok_or_else(|| FraiseQLError::Parse {
679 message: format!("Input field '{name}' in type {type_name} missing 'type'"),
680 location: format!("{type_name}.fields.{name}.type"),
681 })?
682 .to_string();
683
684 let nullable = obj.get("nullable").and_then(|v| v.as_bool()).unwrap_or(true);
685
686 Ok(IRInputField {
687 name,
688 field_type,
689 nullable,
690 default_value: obj.get("default_value").map(GraphQLValue::from_json).transpose()?,
691 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
692 })
693 }
694
695 fn parse_scalars(&self, value: &Value) -> Result<Vec<IRScalar>> {
696 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
697 message: "scalars must be an array".to_string(),
698 location: "scalars".to_string(),
699 })?;
700
701 array
702 .iter()
703 .enumerate()
704 .map(|(i, scalar_val)| self.parse_scalar(scalar_val, i))
705 .collect()
706 }
707
708 fn parse_scalar(&self, value: &Value, index: usize) -> Result<IRScalar> {
709 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
710 message: format!("Scalar at index {index} must be an object"),
711 location: format!("scalars[{index}]"),
712 })?;
713
714 let name = obj
715 .get("name")
716 .and_then(|v| v.as_str())
717 .ok_or_else(|| FraiseQLError::Parse {
718 message: format!("Scalar at index {index} missing 'name' field"),
719 location: format!("scalars[{index}].name"),
720 })?
721 .to_string();
722
723 let description = obj.get("description").and_then(|v| v.as_str()).map(String::from);
724 let specified_by_url =
725 obj.get("specified_by_url").and_then(|v| v.as_str()).map(String::from);
726 let base_type = obj.get("base_type").and_then(|v| v.as_str()).map(String::from);
727
728 let validation_rules = if let Some(rules_val) = obj.get("validation_rules") {
730 serde_json::from_value(rules_val.clone()).unwrap_or_default()
731 } else {
732 Vec::new()
733 };
734
735 Ok(IRScalar {
736 name,
737 description,
738 specified_by_url,
739 validation_rules,
740 base_type,
741 })
742 }
743}
744
745impl Default for SchemaParser {
746 fn default() -> Self {
747 Self::new()
748 }
749}