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 })
244 }
245
246 fn parse_queries(&self, value: &Value) -> Result<Vec<IRQuery>> {
247 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
248 message: "queries must be an array".to_string(),
249 location: "queries".to_string(),
250 })?;
251
252 array
253 .iter()
254 .enumerate()
255 .map(|(i, query_val)| self.parse_query(query_val, i))
256 .collect()
257 }
258
259 fn parse_query(&self, value: &Value, index: usize) -> Result<IRQuery> {
260 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
261 message: format!("Query at index {index} must be an object"),
262 location: format!("queries[{index}]"),
263 })?;
264
265 let name = obj
266 .get("name")
267 .and_then(|v| v.as_str())
268 .ok_or_else(|| FraiseQLError::Parse {
269 message: format!("Query at index {index} missing 'name'"),
270 location: format!("queries[{index}].name"),
271 })?
272 .to_string();
273
274 let return_type = obj
275 .get("return_type")
276 .and_then(|v| v.as_str())
277 .ok_or_else(|| FraiseQLError::Parse {
278 message: format!("Query '{name}' missing 'return_type'"),
279 location: format!("queries.{name}.return_type"),
280 })?
281 .to_string();
282
283 let returns_list = obj.get("returns_list").and_then(|v| v.as_bool()).unwrap_or(false);
284
285 let nullable = obj.get("nullable").and_then(|v| v.as_bool()).unwrap_or(false);
286
287 let arguments = if let Some(args_val) = obj.get("arguments") {
288 self.parse_arguments(args_val, &name)?
289 } else {
290 Vec::new()
291 };
292
293 let auto_params = if let Some(auto_val) = obj.get("auto_params") {
294 self.parse_auto_params(auto_val)?
295 } else {
296 AutoParams::default()
297 };
298
299 Ok(IRQuery {
300 name,
301 return_type,
302 returns_list,
303 nullable,
304 arguments,
305 sql_source: obj.get("sql_source").and_then(|v| v.as_str()).map(String::from),
306 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
307 auto_params,
308 })
309 }
310
311 fn parse_mutations(&self, value: &Value) -> Result<Vec<IRMutation>> {
312 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
313 message: "mutations must be an array".to_string(),
314 location: "mutations".to_string(),
315 })?;
316
317 array
318 .iter()
319 .enumerate()
320 .map(|(i, mutation_val)| self.parse_mutation(mutation_val, i))
321 .collect()
322 }
323
324 fn parse_mutation(&self, value: &Value, index: usize) -> Result<IRMutation> {
325 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
326 message: format!("Mutation at index {index} must be an object"),
327 location: format!("mutations[{index}]"),
328 })?;
329
330 let name = obj
331 .get("name")
332 .and_then(|v| v.as_str())
333 .ok_or_else(|| FraiseQLError::Parse {
334 message: format!("Mutation at index {index} missing 'name'"),
335 location: format!("mutations[{index}].name"),
336 })?
337 .to_string();
338
339 let return_type = obj
340 .get("return_type")
341 .and_then(|v| v.as_str())
342 .ok_or_else(|| FraiseQLError::Parse {
343 message: format!("Mutation '{name}' missing 'return_type'"),
344 location: format!("mutations.{name}.return_type"),
345 })?
346 .to_string();
347
348 let nullable = obj.get("nullable").and_then(|v| v.as_bool()).unwrap_or(false);
349
350 let arguments = if let Some(args_val) = obj.get("arguments") {
351 self.parse_arguments(args_val, &name)?
352 } else {
353 Vec::new()
354 };
355
356 let operation = if let Some(s) = obj.get("operation").and_then(|v| v.as_str()) {
357 match s.to_lowercase().as_str() {
358 "create" => MutationOperation::Create,
359 "update" => MutationOperation::Update,
360 "delete" => MutationOperation::Delete,
361 "custom" => MutationOperation::Custom,
362 other => {
363 return Err(FraiseQLError::Parse {
364 message: format!(
365 "Mutation '{name}' has unknown operation {other:?}. \
366 Valid values are: create, update, delete, custom"
367 ),
368 location: format!("mutations.{name}.operation"),
369 });
370 },
371 }
372 } else {
373 MutationOperation::Custom
374 };
375
376 Ok(IRMutation {
377 name,
378 return_type,
379 nullable,
380 arguments,
381 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
382 operation,
383 })
384 }
385
386 fn parse_subscriptions(&self, value: &Value) -> Result<Vec<IRSubscription>> {
387 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
388 message: "subscriptions must be an array".to_string(),
389 location: "subscriptions".to_string(),
390 })?;
391
392 array
393 .iter()
394 .enumerate()
395 .map(|(i, sub_val)| self.parse_subscription(sub_val, i))
396 .collect()
397 }
398
399 fn parse_subscription(&self, value: &Value, index: usize) -> Result<IRSubscription> {
400 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
401 message: format!("Subscription at index {index} must be an object"),
402 location: format!("subscriptions[{index}]"),
403 })?;
404
405 let name = obj
406 .get("name")
407 .and_then(|v| v.as_str())
408 .ok_or_else(|| FraiseQLError::Parse {
409 message: format!("Subscription at index {index} missing 'name'"),
410 location: format!("subscriptions[{index}].name"),
411 })?
412 .to_string();
413
414 let return_type = obj
415 .get("return_type")
416 .and_then(|v| v.as_str())
417 .ok_or_else(|| FraiseQLError::Parse {
418 message: format!("Subscription '{name}' missing 'return_type'"),
419 location: format!("subscriptions.{name}.return_type"),
420 })?
421 .to_string();
422
423 let arguments = if let Some(args_val) = obj.get("arguments") {
424 self.parse_arguments(args_val, &name)?
425 } else {
426 Vec::new()
427 };
428
429 Ok(IRSubscription {
430 name,
431 return_type,
432 arguments,
433 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
434 })
435 }
436
437 fn parse_arguments(&self, value: &Value, parent_name: &str) -> Result<Vec<IRArgument>> {
438 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
439 message: format!("arguments for '{parent_name}' must be an array"),
440 location: format!("{parent_name}.arguments"),
441 })?;
442
443 array
444 .iter()
445 .enumerate()
446 .map(|(i, arg_val)| self.parse_argument(arg_val, parent_name, i))
447 .collect()
448 }
449
450 fn parse_argument(&self, value: &Value, parent_name: &str, index: usize) -> Result<IRArgument> {
451 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
452 message: format!("Argument at index {index} for '{parent_name}' must be an object"),
453 location: format!("{parent_name}.arguments[{index}]"),
454 })?;
455
456 let name = obj
457 .get("name")
458 .and_then(|v| v.as_str())
459 .ok_or_else(|| FraiseQLError::Parse {
460 message: format!("Argument at index {index} for '{parent_name}' missing 'name'"),
461 location: format!("{parent_name}.arguments[{index}].name"),
462 })?
463 .to_string();
464
465 let arg_type = obj
466 .get("type")
467 .and_then(|v| v.as_str())
468 .ok_or_else(|| FraiseQLError::Parse {
469 message: format!("Argument '{name}' for '{parent_name}' missing 'type'"),
470 location: format!("{parent_name}.arguments.{name}.type"),
471 })?
472 .to_string();
473
474 let nullable = obj.get("nullable").and_then(|v| v.as_bool()).unwrap_or(true);
475
476 Ok(IRArgument {
477 name,
478 arg_type,
479 nullable,
480 default_value: obj.get("default_value").map(GraphQLValue::from_json).transpose()?,
481 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
482 })
483 }
484
485 fn parse_auto_params(&self, value: &Value) -> Result<AutoParams> {
486 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
487 message: "auto_params must be an object".to_string(),
488 location: "auto_params".to_string(),
489 })?;
490
491 Ok(AutoParams {
492 has_where: obj.get("has_where").and_then(|v| v.as_bool()).unwrap_or(false),
493 has_order_by: obj.get("has_order_by").and_then(|v| v.as_bool()).unwrap_or(false),
494 has_limit: obj.get("has_limit").and_then(|v| v.as_bool()).unwrap_or(false),
495 has_offset: obj.get("has_offset").and_then(|v| v.as_bool()).unwrap_or(false),
496 })
497 }
498
499 fn parse_interfaces(&self, value: &Value) -> Result<Vec<IRInterface>> {
500 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
501 message: "interfaces must be an array".to_string(),
502 location: "interfaces".to_string(),
503 })?;
504
505 array
506 .iter()
507 .enumerate()
508 .map(|(i, interface_val)| self.parse_interface(interface_val, i))
509 .collect()
510 }
511
512 fn parse_interface(&self, value: &Value, index: usize) -> Result<IRInterface> {
513 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
514 message: format!("Interface at index {index} must be an object"),
515 location: format!("interfaces[{index}]"),
516 })?;
517
518 let name = obj
519 .get("name")
520 .and_then(|v| v.as_str())
521 .ok_or_else(|| FraiseQLError::Parse {
522 message: format!("Interface at index {index} missing 'name' field"),
523 location: format!("interfaces[{index}].name"),
524 })?
525 .to_string();
526
527 let fields = if let Some(fields_val) = obj.get("fields") {
528 self.parse_fields(fields_val, &name)?
529 } else {
530 Vec::new()
531 };
532
533 Ok(IRInterface {
534 name,
535 fields,
536 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
537 })
538 }
539
540 fn parse_unions(&self, value: &Value) -> Result<Vec<IRUnion>> {
541 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
542 message: "unions must be an array".to_string(),
543 location: "unions".to_string(),
544 })?;
545
546 array
547 .iter()
548 .enumerate()
549 .map(|(i, union_val)| self.parse_union(union_val, i))
550 .collect()
551 }
552
553 fn parse_union(&self, value: &Value, index: usize) -> Result<IRUnion> {
554 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
555 message: format!("Union at index {index} must be an object"),
556 location: format!("unions[{index}]"),
557 })?;
558
559 let name = obj
560 .get("name")
561 .and_then(|v| v.as_str())
562 .ok_or_else(|| FraiseQLError::Parse {
563 message: format!("Union at index {index} missing 'name' field"),
564 location: format!("unions[{index}].name"),
565 })?
566 .to_string();
567
568 let types = if let Some(types_val) = obj.get("types") {
569 let array = types_val.as_array().ok_or_else(|| FraiseQLError::Parse {
570 message: format!("'types' for union {name} must be an array"),
571 location: format!("unions.{name}.types"),
572 })?;
573
574 array
575 .iter()
576 .enumerate()
577 .map(|(i, type_val)| {
578 type_val.as_str().ok_or_else(|| FraiseQLError::Parse {
579 message: format!("Type at index {i} in union {name} must be a string"),
580 location: format!("unions.{name}.types[{i}]"),
581 })
582 })
583 .collect::<Result<Vec<_>>>()?
584 .iter()
585 .map(|s| (*s).to_string())
586 .collect()
587 } else {
588 Vec::new()
589 };
590
591 Ok(IRUnion {
592 name,
593 types,
594 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
595 })
596 }
597
598 fn parse_input_types(&self, value: &Value) -> Result<Vec<IRInputType>> {
599 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
600 message: "input_types must be an array".to_string(),
601 location: "input_types".to_string(),
602 })?;
603
604 array
605 .iter()
606 .enumerate()
607 .map(|(i, input_type_val)| self.parse_input_type(input_type_val, i))
608 .collect()
609 }
610
611 fn parse_input_type(&self, value: &Value, index: usize) -> Result<IRInputType> {
612 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
613 message: format!("Input type at index {index} must be an object"),
614 location: format!("input_types[{index}]"),
615 })?;
616
617 let name = obj
618 .get("name")
619 .and_then(|v| v.as_str())
620 .ok_or_else(|| FraiseQLError::Parse {
621 message: format!("Input type at index {index} missing 'name' field"),
622 location: format!("input_types[{index}].name"),
623 })?
624 .to_string();
625
626 let fields = if let Some(fields_val) = obj.get("fields") {
627 self.parse_input_fields(fields_val, &name)?
628 } else {
629 Vec::new()
630 };
631
632 Ok(IRInputType {
633 name,
634 fields,
635 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
636 })
637 }
638
639 fn parse_input_fields(&self, value: &Value, type_name: &str) -> Result<Vec<IRInputField>> {
640 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
641 message: format!("fields for input type {type_name} must be an array"),
642 location: format!("{type_name}.fields"),
643 })?;
644
645 array
646 .iter()
647 .enumerate()
648 .map(|(i, field_val)| self.parse_input_field(field_val, type_name, i))
649 .collect()
650 }
651
652 fn parse_input_field(
653 &self,
654 value: &Value,
655 type_name: &str,
656 index: usize,
657 ) -> Result<IRInputField> {
658 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
659 message: format!("Input field at index {index} in type {type_name} must be an object"),
660 location: format!("{type_name}.fields[{index}]"),
661 })?;
662
663 let name = obj
664 .get("name")
665 .and_then(|v| v.as_str())
666 .ok_or_else(|| FraiseQLError::Parse {
667 message: format!(
668 "Input field at index {index} in type {type_name} missing 'name'"
669 ),
670 location: format!("{type_name}.fields[{index}].name"),
671 })?
672 .to_string();
673
674 let field_type = obj
675 .get("type")
676 .and_then(|v| v.as_str())
677 .ok_or_else(|| FraiseQLError::Parse {
678 message: format!("Input field '{name}' in type {type_name} missing 'type'"),
679 location: format!("{type_name}.fields.{name}.type"),
680 })?
681 .to_string();
682
683 let nullable = obj.get("nullable").and_then(|v| v.as_bool()).unwrap_or(true);
684
685 Ok(IRInputField {
686 name,
687 field_type,
688 nullable,
689 default_value: obj.get("default_value").map(GraphQLValue::from_json).transpose()?,
690 description: obj.get("description").and_then(|v| v.as_str()).map(String::from),
691 })
692 }
693
694 fn parse_scalars(&self, value: &Value) -> Result<Vec<IRScalar>> {
695 let array = value.as_array().ok_or_else(|| FraiseQLError::Parse {
696 message: "scalars must be an array".to_string(),
697 location: "scalars".to_string(),
698 })?;
699
700 array
701 .iter()
702 .enumerate()
703 .map(|(i, scalar_val)| self.parse_scalar(scalar_val, i))
704 .collect()
705 }
706
707 fn parse_scalar(&self, value: &Value, index: usize) -> Result<IRScalar> {
708 let obj = value.as_object().ok_or_else(|| FraiseQLError::Parse {
709 message: format!("Scalar at index {index} must be an object"),
710 location: format!("scalars[{index}]"),
711 })?;
712
713 let name = obj
714 .get("name")
715 .and_then(|v| v.as_str())
716 .ok_or_else(|| FraiseQLError::Parse {
717 message: format!("Scalar at index {index} missing 'name' field"),
718 location: format!("scalars[{index}].name"),
719 })?
720 .to_string();
721
722 let description = obj.get("description").and_then(|v| v.as_str()).map(String::from);
723 let specified_by_url =
724 obj.get("specified_by_url").and_then(|v| v.as_str()).map(String::from);
725 let base_type = obj.get("base_type").and_then(|v| v.as_str()).map(String::from);
726
727 let validation_rules = match obj.get("validation_rules") {
732 Some(rules_val) => {
733 serde_json::from_value(rules_val.clone()).map_err(|e| FraiseQLError::Parse {
734 message: format!(
735 "Scalar '{name}' has malformed 'validation_rules': {e}. Refusing to \
736 compile a scalar whose validation would silently not run."
737 ),
738 location: format!("scalars[{index}].validation_rules"),
739 })?
740 },
741 None => Vec::new(),
742 };
743
744 Ok(IRScalar {
745 name,
746 description,
747 specified_by_url,
748 validation_rules,
749 base_type,
750 })
751 }
752}
753
754impl Default for SchemaParser {
755 fn default() -> Self {
756 Self::new()
757 }
758}