1use crate::parse::{
46 ParsedArraySchema, ParsedExtTypeSchema, ParsedFloatSchema, ParsedIntegerSchema,
47 ParsedMapSchema, ParsedRecordSchema, ParsedSchemaMetadata, ParsedSchemaNode,
48 ParsedSchemaNodeContent, ParsedTupleSchema, ParsedUnionSchema, ParsedUnknownFieldsPolicy,
49};
50use crate::{
51 ArraySchema, Bound, ExtTypeSchema, FloatPrecision, FloatSchema, IntegerSchema, MapSchema,
52 RecordFieldSchema, RecordSchema, SchemaDocument, SchemaMetadata, SchemaNodeContent,
53 SchemaNodeId, TupleSchema, UnionSchema, UnknownFieldsPolicy,
54};
55use eure_document::document::node::{Node, NodeValue};
56use eure_document::document::{EureDocument, NodeId};
57use eure_document::identifier::Identifier;
58use eure_document::parse::ParseError;
59use eure_document::value::ObjectKey;
60use indexmap::IndexMap;
61use num_bigint::BigInt;
62use thiserror::Error;
63
64#[derive(Debug, Error, Clone, PartialEq)]
66pub enum ConversionError {
67 #[error("Invalid type name: {0}")]
68 InvalidTypeName(ObjectKey),
69
70 #[error("Unsupported schema construct at path: {0}")]
71 UnsupportedConstruct(String),
72
73 #[error("Invalid extension value: {extension} at path {path}")]
74 InvalidExtensionValue { extension: String, path: String },
75
76 #[error("Invalid range string: {0}")]
77 InvalidRangeString(String),
78
79 #[error("Invalid precision: {0} (expected \"f32\" or \"f64\")")]
80 InvalidPrecision(String),
81
82 #[error("Undefined type reference: {0}")]
83 UndefinedTypeReference(String),
84
85 #[error("Parse error: {0}")]
86 ParseError(#[from] ParseError),
87}
88
89pub type SchemaSourceMap = IndexMap<SchemaNodeId, NodeId>;
92
93struct Converter<'a> {
95 doc: &'a EureDocument,
96 schema: SchemaDocument,
97 source_map: SchemaSourceMap,
99}
100
101impl<'a> Converter<'a> {
102 fn new(doc: &'a EureDocument) -> Self {
103 Self {
104 doc,
105 schema: SchemaDocument::new(),
106 source_map: IndexMap::new(),
107 }
108 }
109
110 fn convert(mut self) -> Result<(SchemaDocument, SchemaSourceMap), ConversionError> {
112 let root_id = self.doc.get_root_id();
113 let root_node = self.doc.node(root_id);
114
115 self.convert_types(root_node)?;
117
118 self.schema.root = self.convert_node(root_id)?;
120
121 self.validate_type_references()?;
123
124 Ok((self.schema, self.source_map))
125 }
126
127 fn convert_types(&mut self, node: &Node) -> Result<(), ConversionError> {
129 let types_ident: Identifier = "types".parse().unwrap();
130 if let Some(types_node_id) = node.extensions.get(&types_ident) {
131 let types_node = self.doc.node(*types_node_id);
132 if let NodeValue::Map(map) = &types_node.content {
133 for (key, &node_id) in map.0.iter() {
134 if let ObjectKey::String(name) = key {
135 let type_name: Identifier = name
136 .parse()
137 .map_err(|_| ConversionError::InvalidTypeName(key.clone()))?;
138 let schema_id = self.convert_node(node_id)?;
139 self.schema.types.insert(type_name, schema_id);
140 } else {
141 return Err(ConversionError::InvalidTypeName(key.clone()));
142 }
143 }
144 } else {
145 return Err(ConversionError::InvalidExtensionValue {
146 extension: "types".to_string(),
147 path: "$types must be a map".to_string(),
148 });
149 }
150 }
151 Ok(())
152 }
153
154 fn validate_type_references(&self) -> Result<(), ConversionError> {
156 for node in &self.schema.nodes {
157 if let SchemaNodeContent::Reference(type_ref) = &node.content
158 && type_ref.namespace.is_none()
159 && !self.schema.types.contains_key(&type_ref.name)
160 {
161 return Err(ConversionError::UndefinedTypeReference(
162 type_ref.name.to_string(),
163 ));
164 }
165 }
166 Ok(())
167 }
168
169 fn convert_node(&mut self, node_id: NodeId) -> Result<SchemaNodeId, ConversionError> {
171 let parsed: ParsedSchemaNode = self.doc.parse(node_id)?;
173
174 let content = self.convert_content(parsed.content)?;
176 let metadata = self.convert_metadata(parsed.metadata)?;
177 let ext_types = self.convert_ext_types(parsed.ext_types)?;
178
179 let schema_id = self.schema.create_node(content);
181 self.schema.node_mut(schema_id).metadata = metadata;
182 self.schema.node_mut(schema_id).ext_types = ext_types;
183
184 self.source_map.insert(schema_id, node_id);
186 Ok(schema_id)
187 }
188
189 fn convert_content(
191 &mut self,
192 content: ParsedSchemaNodeContent,
193 ) -> Result<SchemaNodeContent, ConversionError> {
194 match content {
195 ParsedSchemaNodeContent::Any => Ok(SchemaNodeContent::Any),
196 ParsedSchemaNodeContent::Boolean => Ok(SchemaNodeContent::Boolean),
197 ParsedSchemaNodeContent::Null => Ok(SchemaNodeContent::Null),
198 ParsedSchemaNodeContent::Text(schema) => Ok(SchemaNodeContent::Text(schema)),
199 ParsedSchemaNodeContent::Reference(type_ref) => {
200 Ok(SchemaNodeContent::Reference(type_ref))
201 }
202
203 ParsedSchemaNodeContent::Integer(parsed) => Ok(SchemaNodeContent::Integer(
204 self.convert_integer_schema(parsed)?,
205 )),
206 ParsedSchemaNodeContent::Float(parsed) => {
207 Ok(SchemaNodeContent::Float(self.convert_float_schema(parsed)?))
208 }
209 ParsedSchemaNodeContent::Literal(node_id) => {
210 Ok(SchemaNodeContent::Literal(self.node_to_document(node_id)?))
211 }
212 ParsedSchemaNodeContent::Array(parsed) => {
213 Ok(SchemaNodeContent::Array(self.convert_array_schema(parsed)?))
214 }
215 ParsedSchemaNodeContent::Map(parsed) => {
216 Ok(SchemaNodeContent::Map(self.convert_map_schema(parsed)?))
217 }
218 ParsedSchemaNodeContent::Record(parsed) => Ok(SchemaNodeContent::Record(
219 self.convert_record_schema(parsed)?,
220 )),
221 ParsedSchemaNodeContent::Tuple(parsed) => {
222 Ok(SchemaNodeContent::Tuple(self.convert_tuple_schema(parsed)?))
223 }
224 ParsedSchemaNodeContent::Union(parsed) => {
225 Ok(SchemaNodeContent::Union(self.convert_union_schema(parsed)?))
226 }
227 }
228 }
229
230 fn convert_integer_schema(
232 &self,
233 parsed: ParsedIntegerSchema,
234 ) -> Result<IntegerSchema, ConversionError> {
235 let (min, max) = if let Some(range_str) = &parsed.range {
236 parse_integer_range(range_str)?
237 } else {
238 (Bound::Unbounded, Bound::Unbounded)
239 };
240
241 Ok(IntegerSchema {
242 min,
243 max,
244 multiple_of: parsed.multiple_of,
245 })
246 }
247
248 fn convert_float_schema(
250 &self,
251 parsed: ParsedFloatSchema,
252 ) -> Result<FloatSchema, ConversionError> {
253 let (min, max) = if let Some(range_str) = &parsed.range {
254 parse_float_range(range_str)?
255 } else {
256 (Bound::Unbounded, Bound::Unbounded)
257 };
258
259 let precision = match parsed.precision.as_deref() {
260 Some("f32") => FloatPrecision::F32,
261 Some("f64") | None => FloatPrecision::F64,
262 Some(other) => {
263 return Err(ConversionError::InvalidPrecision(other.to_string()));
264 }
265 };
266
267 Ok(FloatSchema {
268 min,
269 max,
270 multiple_of: parsed.multiple_of,
271 precision,
272 })
273 }
274
275 fn convert_array_schema(
277 &mut self,
278 parsed: ParsedArraySchema,
279 ) -> Result<ArraySchema, ConversionError> {
280 let item = self.convert_node(parsed.item)?;
281 let contains = parsed
282 .contains
283 .map(|id| self.convert_node(id))
284 .transpose()?;
285
286 Ok(ArraySchema {
287 item,
288 min_length: parsed.min_length,
289 max_length: parsed.max_length,
290 unique: parsed.unique,
291 contains,
292 binding_style: parsed.binding_style,
293 })
294 }
295
296 fn convert_map_schema(
298 &mut self,
299 parsed: ParsedMapSchema,
300 ) -> Result<MapSchema, ConversionError> {
301 let key = self.convert_node(parsed.key)?;
302 let value = self.convert_node(parsed.value)?;
303
304 Ok(MapSchema {
305 key,
306 value,
307 min_size: parsed.min_size,
308 max_size: parsed.max_size,
309 })
310 }
311
312 fn convert_tuple_schema(
314 &mut self,
315 parsed: ParsedTupleSchema,
316 ) -> Result<TupleSchema, ConversionError> {
317 let elements: Vec<SchemaNodeId> = parsed
318 .elements
319 .iter()
320 .map(|&id| self.convert_node(id))
321 .collect::<Result<_, _>>()?;
322
323 Ok(TupleSchema {
324 elements,
325 binding_style: parsed.binding_style,
326 })
327 }
328
329 fn convert_record_schema(
331 &mut self,
332 parsed: ParsedRecordSchema,
333 ) -> Result<RecordSchema, ConversionError> {
334 let mut properties = IndexMap::new();
335
336 for (field_name, field_parsed) in parsed.properties {
337 let schema = self.convert_node(field_parsed.schema)?;
338 properties.insert(
339 field_name,
340 RecordFieldSchema {
341 schema,
342 optional: field_parsed.optional,
343 binding_style: field_parsed.binding_style,
344 },
345 );
346 }
347
348 let unknown_fields = self.convert_unknown_fields_policy(parsed.unknown_fields)?;
349
350 Ok(RecordSchema {
351 properties,
352 unknown_fields,
353 })
354 }
355
356 fn convert_union_schema(
358 &mut self,
359 parsed: ParsedUnionSchema,
360 ) -> Result<UnionSchema, ConversionError> {
361 let mut variants = IndexMap::new();
362
363 for (variant_name, variant_node_id) in parsed.variants {
364 let schema = self.convert_node(variant_node_id)?;
365 variants.insert(variant_name, schema);
366 }
367
368 Ok(UnionSchema {
369 variants,
370 unambiguous: parsed.unambiguous,
371 repr: parsed.repr,
372 deny_untagged: parsed.deny_untagged,
373 })
374 }
375
376 fn convert_unknown_fields_policy(
378 &mut self,
379 parsed: ParsedUnknownFieldsPolicy,
380 ) -> Result<UnknownFieldsPolicy, ConversionError> {
381 match parsed {
382 ParsedUnknownFieldsPolicy::Deny => Ok(UnknownFieldsPolicy::Deny),
383 ParsedUnknownFieldsPolicy::Allow => Ok(UnknownFieldsPolicy::Allow),
384 ParsedUnknownFieldsPolicy::Schema(node_id) => {
385 let schema = self.convert_node(node_id)?;
386 Ok(UnknownFieldsPolicy::Schema(schema))
387 }
388 }
389 }
390
391 fn convert_metadata(
393 &mut self,
394 parsed: ParsedSchemaMetadata,
395 ) -> Result<SchemaMetadata, ConversionError> {
396 let default = parsed
397 .default
398 .map(|id| self.node_to_document(id))
399 .transpose()?;
400
401 let examples = parsed
402 .examples
403 .map(|ids| {
404 ids.into_iter()
405 .map(|id| self.node_to_document(id))
406 .collect::<Result<Vec<_>, _>>()
407 })
408 .transpose()?;
409
410 Ok(SchemaMetadata {
411 description: parsed.description,
412 deprecated: parsed.deprecated,
413 default,
414 examples,
415 })
416 }
417
418 fn convert_ext_types(
420 &mut self,
421 parsed: IndexMap<Identifier, ParsedExtTypeSchema>,
422 ) -> Result<IndexMap<Identifier, ExtTypeSchema>, ConversionError> {
423 let mut result = IndexMap::new();
424
425 for (name, parsed_schema) in parsed {
426 let schema = self.convert_node(parsed_schema.schema)?;
427 result.insert(
428 name,
429 ExtTypeSchema {
430 schema,
431 optional: parsed_schema.optional,
432 },
433 );
434 }
435
436 Ok(result)
437 }
438
439 fn node_to_document(&self, node_id: NodeId) -> Result<EureDocument, ConversionError> {
441 let mut new_doc = EureDocument::new();
442 let root_id = new_doc.get_root_id();
443 self.copy_node_to(&mut new_doc, root_id, node_id)?;
444 Ok(new_doc)
445 }
446
447 fn copy_node_to(
449 &self,
450 dest: &mut EureDocument,
451 dest_node_id: NodeId,
452 src_node_id: NodeId,
453 ) -> Result<(), ConversionError> {
454 let src_node = self.doc.node(src_node_id);
455
456 let children_to_copy: Vec<_> = match &src_node.content {
458 NodeValue::Primitive(prim) => {
459 dest.set_content(dest_node_id, NodeValue::Primitive(prim.clone()));
460 vec![]
461 }
462 NodeValue::Array(arr) => {
463 dest.set_content(dest_node_id, NodeValue::empty_array());
464 arr.0.to_vec()
465 }
466 NodeValue::Tuple(tup) => {
467 dest.set_content(dest_node_id, NodeValue::empty_tuple());
468 tup.0.to_vec()
469 }
470 NodeValue::Map(map) => {
471 dest.set_content(dest_node_id, NodeValue::empty_map());
472 map.0
473 .iter()
474 .map(|(k, &v)| (k.clone(), v))
475 .collect::<Vec<_>>()
476 .into_iter()
477 .map(|(_, v)| v)
478 .collect()
479 }
480 NodeValue::Hole(_) => {
481 return Err(ConversionError::UnsupportedConstruct(
482 "Hole node".to_string(),
483 ));
484 }
485 };
486
487 let src_node = self.doc.node(src_node_id);
494 match &src_node.content {
495 NodeValue::Array(_) => {
496 for child_id in children_to_copy {
497 let new_child_id = dest
498 .add_array_element(None, dest_node_id)
499 .map_err(|e| ConversionError::UnsupportedConstruct(e.to_string()))?
500 .node_id;
501 self.copy_node_to(dest, new_child_id, child_id)?;
502 }
503 }
504 NodeValue::Tuple(_) => {
505 for (index, child_id) in children_to_copy.into_iter().enumerate() {
506 let new_child_id = dest
507 .add_tuple_element(index as u8, dest_node_id)
508 .map_err(|e| ConversionError::UnsupportedConstruct(e.to_string()))?
509 .node_id;
510 self.copy_node_to(dest, new_child_id, child_id)?;
511 }
512 }
513 NodeValue::Map(map) => {
514 for (key, &child_id) in map.0.iter() {
515 let new_child_id = dest
516 .add_map_child(key.clone(), dest_node_id)
517 .map_err(|e| ConversionError::UnsupportedConstruct(e.to_string()))?
518 .node_id;
519 self.copy_node_to(dest, new_child_id, child_id)?;
520 }
521 }
522 _ => {}
523 }
524
525 Ok(())
526 }
527}
528
529fn parse_integer_range(s: &str) -> Result<(Bound<BigInt>, Bound<BigInt>), ConversionError> {
531 let s = s.trim();
532
533 if s.starts_with('[') || s.starts_with('(') {
535 return parse_interval_integer(s);
536 }
537
538 if let Some(eq_pos) = s.find("..=") {
540 let left = &s[..eq_pos];
541 let right = &s[eq_pos + 3..];
542 let min = if left.is_empty() {
543 Bound::Unbounded
544 } else {
545 Bound::Inclusive(parse_bigint(left)?)
546 };
547 let max = if right.is_empty() {
548 Bound::Unbounded
549 } else {
550 Bound::Inclusive(parse_bigint(right)?)
551 };
552 Ok((min, max))
553 } else if let Some(dot_pos) = s.find("..") {
554 let left = &s[..dot_pos];
555 let right = &s[dot_pos + 2..];
556 let min = if left.is_empty() {
557 Bound::Unbounded
558 } else {
559 Bound::Inclusive(parse_bigint(left)?)
560 };
561 let max = if right.is_empty() {
562 Bound::Unbounded
563 } else {
564 Bound::Exclusive(parse_bigint(right)?)
565 };
566 Ok((min, max))
567 } else {
568 Err(ConversionError::InvalidRangeString(s.to_string()))
569 }
570}
571
572fn parse_interval_integer(s: &str) -> Result<(Bound<BigInt>, Bound<BigInt>), ConversionError> {
574 let left_inclusive = s.starts_with('[');
575 let right_inclusive = s.ends_with(']');
576
577 let inner = &s[1..s.len() - 1];
578 let parts: Vec<&str> = inner.split(',').map(|p| p.trim()).collect();
579 if parts.len() != 2 {
580 return Err(ConversionError::InvalidRangeString(s.to_string()));
581 }
582
583 let min = if parts[0].is_empty() {
584 Bound::Unbounded
585 } else if left_inclusive {
586 Bound::Inclusive(parse_bigint(parts[0])?)
587 } else {
588 Bound::Exclusive(parse_bigint(parts[0])?)
589 };
590
591 let max = if parts[1].is_empty() {
592 Bound::Unbounded
593 } else if right_inclusive {
594 Bound::Inclusive(parse_bigint(parts[1])?)
595 } else {
596 Bound::Exclusive(parse_bigint(parts[1])?)
597 };
598
599 Ok((min, max))
600}
601
602fn parse_float_range(s: &str) -> Result<(Bound<f64>, Bound<f64>), ConversionError> {
604 let s = s.trim();
605
606 if s.starts_with('[') || s.starts_with('(') {
608 return parse_interval_float(s);
609 }
610
611 if let Some(eq_pos) = s.find("..=") {
613 let left = &s[..eq_pos];
614 let right = &s[eq_pos + 3..];
615 let min = if left.is_empty() {
616 Bound::Unbounded
617 } else {
618 Bound::Inclusive(parse_f64(left)?)
619 };
620 let max = if right.is_empty() {
621 Bound::Unbounded
622 } else {
623 Bound::Inclusive(parse_f64(right)?)
624 };
625 Ok((min, max))
626 } else if let Some(dot_pos) = s.find("..") {
627 let left = &s[..dot_pos];
628 let right = &s[dot_pos + 2..];
629 let min = if left.is_empty() {
630 Bound::Unbounded
631 } else {
632 Bound::Inclusive(parse_f64(left)?)
633 };
634 let max = if right.is_empty() {
635 Bound::Unbounded
636 } else {
637 Bound::Exclusive(parse_f64(right)?)
638 };
639 Ok((min, max))
640 } else {
641 Err(ConversionError::InvalidRangeString(s.to_string()))
642 }
643}
644
645fn parse_interval_float(s: &str) -> Result<(Bound<f64>, Bound<f64>), ConversionError> {
647 let left_inclusive = s.starts_with('[');
648 let right_inclusive = s.ends_with(']');
649
650 let inner = &s[1..s.len() - 1];
651 let parts: Vec<&str> = inner.split(',').map(|p| p.trim()).collect();
652 if parts.len() != 2 {
653 return Err(ConversionError::InvalidRangeString(s.to_string()));
654 }
655
656 let min = if parts[0].is_empty() {
657 Bound::Unbounded
658 } else if left_inclusive {
659 Bound::Inclusive(parse_f64(parts[0])?)
660 } else {
661 Bound::Exclusive(parse_f64(parts[0])?)
662 };
663
664 let max = if parts[1].is_empty() {
665 Bound::Unbounded
666 } else if right_inclusive {
667 Bound::Inclusive(parse_f64(parts[1])?)
668 } else {
669 Bound::Exclusive(parse_f64(parts[1])?)
670 };
671
672 Ok((min, max))
673}
674
675fn parse_bigint(s: &str) -> Result<BigInt, ConversionError> {
676 s.parse()
677 .map_err(|_| ConversionError::InvalidRangeString(format!("Invalid integer: {}", s)))
678}
679
680fn parse_f64(s: &str) -> Result<f64, ConversionError> {
681 s.parse()
682 .map_err(|_| ConversionError::InvalidRangeString(format!("Invalid float: {}", s)))
683}
684
685pub fn document_to_schema(
719 doc: &EureDocument,
720) -> Result<(SchemaDocument, SchemaSourceMap), ConversionError> {
721 Converter::new(doc).convert()
722}
723
724#[cfg(test)]
725mod tests {
726 use super::*;
727 use crate::identifiers::{EXT_TYPE, OPTIONAL};
728 use eure_document::document::node::NodeMap;
729 use eure_document::eure;
730 use eure_document::text::Text;
731 use eure_document::value::PrimitiveValue;
732
733 fn create_schema_with_field_ext_type(ext_type_content: NodeValue) -> EureDocument {
735 let mut doc = EureDocument::new();
736 let root_id = doc.get_root_id();
737
738 let field_value_id = doc.create_node(NodeValue::Primitive(PrimitiveValue::Text(
740 Text::inline_implicit("text"),
741 )));
742
743 let ext_type_id = doc.create_node(ext_type_content);
745 doc.node_mut(field_value_id)
746 .extensions
747 .insert(EXT_TYPE.clone(), ext_type_id);
748
749 let mut root_map = NodeMap::default();
751 root_map
752 .0
753 .insert(ObjectKey::String("name".to_string()), field_value_id);
754 doc.node_mut(root_id).content = NodeValue::Map(root_map);
755
756 doc
757 }
758
759 #[test]
760 fn extract_ext_types_not_map() {
761 let doc = create_schema_with_field_ext_type(NodeValue::Primitive(PrimitiveValue::Integer(
764 1.into(),
765 )));
766
767 let err = document_to_schema(&doc).unwrap_err();
768 use eure_document::parse::ParseErrorKind;
769 use eure_document::value::ValueKind;
770 assert_eq!(
771 err,
772 ConversionError::ParseError(ParseError {
773 node_id: NodeId(2),
774 kind: ParseErrorKind::TypeMismatch {
775 expected: ValueKind::Map,
776 actual: ValueKind::Integer,
777 }
778 })
779 );
780 }
781
782 #[test]
783 fn extract_ext_types_invalid_key() {
784 let mut doc = EureDocument::new();
787 let root_id = doc.get_root_id();
788
789 let field_value_id = doc.create_node(NodeValue::Primitive(PrimitiveValue::Text(
791 Text::inline_implicit("text"),
792 )));
793
794 let ext_type_value_id = doc.create_node(NodeValue::Primitive(PrimitiveValue::Text(
796 Text::inline_implicit("text"),
797 )));
798 let mut ext_type_map = NodeMap::default();
799 ext_type_map
800 .0
801 .insert(ObjectKey::Number(0.into()), ext_type_value_id);
802
803 let ext_type_id = doc.create_node(NodeValue::Map(ext_type_map));
804 doc.node_mut(field_value_id)
805 .extensions
806 .insert(EXT_TYPE.clone(), ext_type_id);
807
808 let mut root_map = NodeMap::default();
810 root_map
811 .0
812 .insert(ObjectKey::String("name".to_string()), field_value_id);
813 doc.node_mut(root_id).content = NodeValue::Map(root_map);
814
815 let err = document_to_schema(&doc).unwrap_err();
816 use eure_document::parse::ParseErrorKind;
817 assert_eq!(
818 err,
819 ConversionError::ParseError(ParseError {
820 node_id: NodeId(3),
821 kind: ParseErrorKind::InvalidKeyType(ObjectKey::Number(0.into()))
822 })
823 );
824 }
825
826 #[test]
827 fn extract_ext_types_invalid_optional() {
828 let mut doc = EureDocument::new();
831 let root_id = doc.get_root_id();
832
833 let field_value_id = doc.create_node(NodeValue::Primitive(PrimitiveValue::Text(
835 Text::inline_implicit("text"),
836 )));
837
838 let ext_type_value_id = doc.create_node(NodeValue::Primitive(PrimitiveValue::Text(
840 Text::inline_implicit("text"),
841 )));
842 let optional_node_id =
843 doc.create_node(NodeValue::Primitive(PrimitiveValue::Integer(1.into())));
844 doc.node_mut(ext_type_value_id)
845 .extensions
846 .insert(OPTIONAL.clone(), optional_node_id);
847
848 let mut ext_type_map = NodeMap::default();
850 ext_type_map
851 .0
852 .insert(ObjectKey::String("desc".to_string()), ext_type_value_id);
853
854 let ext_type_id = doc.create_node(NodeValue::Map(ext_type_map));
855 doc.node_mut(field_value_id)
856 .extensions
857 .insert(EXT_TYPE.clone(), ext_type_id);
858
859 let mut root_map = NodeMap::default();
861 root_map
862 .0
863 .insert(ObjectKey::String("name".to_string()), field_value_id);
864 doc.node_mut(root_id).content = NodeValue::Map(root_map);
865
866 let err = document_to_schema(&doc).unwrap_err();
867 use eure_document::parse::ParseErrorKind;
868 use eure_document::value::ValueKind;
869 assert_eq!(
870 err,
871 ConversionError::ParseError(ParseError {
872 node_id: NodeId(3),
873 kind: ParseErrorKind::TypeMismatch {
874 expected: ValueKind::Bool,
875 actual: ValueKind::Integer,
876 }
877 })
878 );
879 }
880
881 #[test]
882 fn literal_variant_with_inline_code() {
883 let mut doc = EureDocument::new();
887 let root_id = doc.get_root_id();
888
889 let variant_value_id = doc.create_node(NodeValue::Primitive(PrimitiveValue::Text(
891 Text::plaintext("literal"),
892 )));
893
894 doc.node_mut(root_id).content =
897 NodeValue::Primitive(PrimitiveValue::Text(Text::inline_implicit("any")));
898
899 doc.node_mut(root_id)
901 .extensions
902 .insert("variant".parse().unwrap(), variant_value_id);
903
904 let (schema, _source_map) =
905 document_to_schema(&doc).expect("Schema conversion should succeed");
906
907 let root_content = &schema.node(schema.root).content;
909 match root_content {
910 SchemaNodeContent::Literal(doc) => {
911 match &doc.root().content {
913 NodeValue::Primitive(PrimitiveValue::Text(t)) => {
914 assert_eq!(t.as_str(), "any", "Literal should contain 'any'");
915 }
916 _ => panic!("Expected Literal with Text primitive, got {:?}", doc),
917 }
918 }
919 SchemaNodeContent::Any => {
920 panic!("BUG: Got Any instead of Literal - $variant extension not detected!");
921 }
922 other => panic!("Expected Literal, got {:?}", other),
923 }
924 }
925
926 #[test]
927 fn literal_variant_parsed_from_eure() {
928 let doc = eure!({
929 = @code("any")
930 %variant = "literal"
931 });
932
933 let (schema, _source_map) =
934 document_to_schema(&doc).expect("Schema conversion should succeed");
935
936 let root_content = &schema.node(schema.root).content;
937 match root_content {
938 SchemaNodeContent::Literal(doc) => match &doc.root().content {
939 NodeValue::Primitive(PrimitiveValue::Text(t)) => {
940 assert_eq!(t.as_str(), "any", "Literal should contain 'any'");
941 }
942 _ => panic!("Expected Literal with Text primitive, got {:?}", doc),
943 },
944 SchemaNodeContent::Any => {
945 panic!(
946 "BUG: Got Any instead of Literal - $variant extension not respected for primitive"
947 );
948 }
949 other => panic!("Expected Literal, got {:?}", other),
950 }
951 }
952
953 #[test]
954 fn union_with_literal_any_variant() {
955 let mut doc = EureDocument::new();
960 let root_id = doc.get_root_id();
961
962 let any_variant_node = doc.create_node(NodeValue::Primitive(PrimitiveValue::Text(
965 Text::inline_implicit("any"),
966 )));
967 let literal_ext = doc.create_node(NodeValue::Primitive(PrimitiveValue::Text(
969 Text::plaintext("literal"),
970 )));
971 doc.node_mut(any_variant_node)
972 .extensions
973 .insert("variant".parse().unwrap(), literal_ext);
974
975 let literal_variant_node = doc.create_node(NodeValue::Primitive(PrimitiveValue::Text(
977 Text::inline_implicit("any"),
978 )));
979
980 let mut variants_map = NodeMap::default();
982 variants_map
983 .0
984 .insert(ObjectKey::String("any".to_string()), any_variant_node);
985 variants_map.0.insert(
986 ObjectKey::String("literal".to_string()),
987 literal_variant_node,
988 );
989 let variants_node = doc.create_node(NodeValue::Map(variants_map));
990
991 let union_ext = doc.create_node(NodeValue::Primitive(PrimitiveValue::Text(
993 Text::plaintext("union"),
994 )));
995 let untagged_ext = doc.create_node(NodeValue::Primitive(PrimitiveValue::Text(
996 Text::plaintext("untagged"),
997 )));
998
999 let mut root_map = NodeMap::default();
1001 root_map
1002 .0
1003 .insert(ObjectKey::String("variants".to_string()), variants_node);
1004
1005 doc.node_mut(root_id).content = NodeValue::Map(root_map);
1006 doc.node_mut(root_id)
1007 .extensions
1008 .insert("variant".parse().unwrap(), union_ext);
1009 doc.node_mut(root_id)
1010 .extensions
1011 .insert("variant-repr".parse().unwrap(), untagged_ext);
1012
1013 let (schema, _source_map) =
1014 document_to_schema(&doc).expect("Schema conversion should succeed");
1015
1016 let root_content = &schema.node(schema.root).content;
1018 match root_content {
1019 SchemaNodeContent::Union(union_schema) => {
1020 let any_variant_id = union_schema
1022 .variants
1023 .get("any")
1024 .expect("'any' variant missing");
1025 let any_content = &schema.node(*any_variant_id).content;
1026 match any_content {
1027 SchemaNodeContent::Literal(doc) => match &doc.root().content {
1028 NodeValue::Primitive(PrimitiveValue::Text(t)) => {
1029 assert_eq!(
1030 t.as_str(),
1031 "any",
1032 "'any' variant should be Literal(\"any\")"
1033 );
1034 }
1035 _ => panic!("'any' variant: expected Text, got {:?}", doc),
1036 },
1037 SchemaNodeContent::Any => {
1038 panic!(
1039 "BUG: 'any' variant is Any instead of Literal(\"any\") - $variant extension not detected!"
1040 );
1041 }
1042 other => panic!("'any' variant: expected Literal, got {:?}", other),
1043 }
1044
1045 let literal_variant_id = union_schema
1047 .variants
1048 .get("literal")
1049 .expect("'literal' variant missing");
1050 let literal_content = &schema.node(*literal_variant_id).content;
1051 match literal_content {
1052 SchemaNodeContent::Any => {
1053 }
1055 other => panic!("'literal' variant: expected Any, got {:?}", other),
1056 }
1057 }
1058 other => panic!("Expected Union, got {:?}", other),
1059 }
1060 }
1061}