1use eure_document::document::NodeId;
22use eure_document::identifier::Identifier;
23use eure_document::parse::{FromEure, ParseContext, ParseError, ParseErrorKind};
24use indexmap::{IndexMap, IndexSet};
25use num_bigint::BigInt;
26
27use crate::interop::UnionInterop;
28use crate::{BindingStyle, Description, FieldCodegen, TextSchema, TypeReference};
29
30impl FromEure<'_> for TypeReference {
31 type Error = ParseError;
32 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
33 let path: &str = ctx.parse()?;
36
37 let path = path.strip_prefix("$types.").ok_or_else(|| ParseError {
39 node_id: ctx.node_id(),
40 kind: ParseErrorKind::InvalidPattern {
41 kind: "type reference".to_string(),
42 reason: format!(
43 "expected '$types.<name>' or '$types.<namespace>.<name>', got '{}'",
44 path
45 ),
46 },
47 })?;
48
49 let parts: Vec<&str> = path.split('.').collect();
51 match parts.as_slice() {
52 [name] => {
53 let name: Identifier = name.parse().map_err(|e| ParseError {
54 node_id: ctx.node_id(),
55 kind: ParseErrorKind::InvalidIdentifier(e),
56 })?;
57 Ok(TypeReference {
58 namespace: None,
59 name,
60 })
61 }
62 [namespace, name] => {
63 let name: Identifier = name.parse().map_err(|e| ParseError {
64 node_id: ctx.node_id(),
65 kind: ParseErrorKind::InvalidIdentifier(e),
66 })?;
67 Ok(TypeReference {
68 namespace: Some((*namespace).to_string()),
69 name,
70 })
71 }
72 _ => Err(ParseError {
73 node_id: ctx.node_id(),
74 kind: ParseErrorKind::InvalidPattern {
75 kind: "type reference".to_string(),
76 reason: format!(
77 "expected '$types.<name>' or '$types.<namespace>.<name>', got '$types.{}'",
78 path
79 ),
80 },
81 }),
82 }
83 }
84}
85
86impl FromEure<'_> for crate::SchemaRef {
87 type Error = ParseError;
88
89 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
90 let schema_ctx = ctx.ext("schema")?;
91
92 let path: String = schema_ctx.parse()?;
93 Ok(crate::SchemaRef {
94 path,
95 node_id: schema_ctx.node_id(),
96 })
97 }
98}
99
100#[derive(Debug, Clone, eure_macros::FromEure)]
106#[eure(crate = eure_document, rename_all = "kebab-case")]
107pub struct ParsedIntegerSchema {
108 #[eure(default)]
110 pub range: Option<String>,
111 #[eure(default)]
113 pub multiple_of: Option<BigInt>,
114}
115
116#[derive(Debug, Clone, eure_macros::FromEure)]
118#[eure(crate = eure_document, rename_all = "kebab-case")]
119pub struct ParsedFloatSchema {
120 #[eure(default)]
122 pub range: Option<String>,
123 #[eure(default)]
125 pub multiple_of: Option<f64>,
126 #[eure(default)]
128 pub precision: Option<String>,
129}
130
131#[derive(Debug, Clone, eure_macros::FromEure)]
133#[eure(crate = eure_document, rename_all = "kebab-case")]
134pub struct ParsedArraySchema {
135 pub item: NodeId,
137 #[eure(default)]
139 pub min_length: Option<u32>,
140 #[eure(default)]
142 pub max_length: Option<u32>,
143 #[eure(default)]
145 pub unique: bool,
146 #[eure(default)]
148 pub contains: Option<NodeId>,
149 #[eure(ext, default)]
151 pub binding_style: Option<BindingStyle>,
152}
153
154#[derive(Debug, Clone, eure_macros::FromEure)]
156#[eure(crate = eure_document, rename_all = "kebab-case")]
157pub struct ParsedMapSchema {
158 pub key: NodeId,
160 pub value: NodeId,
162 #[eure(default)]
164 pub min_size: Option<u32>,
165 #[eure(default)]
167 pub max_size: Option<u32>,
168}
169
170#[derive(Debug, Clone, eure_macros::FromEure)]
172#[eure(crate = eure_document, parse_ext, rename_all = "kebab-case")]
173pub struct ParsedRecordFieldSchema {
174 #[eure(flatten_ext)]
176 pub schema: NodeId,
177 #[eure(default)]
179 pub optional: bool,
180 #[eure(default)]
182 pub binding_style: Option<BindingStyle>,
183 #[eure(default)]
185 pub codegen: Option<FieldCodegen>,
186}
187
188#[derive(Debug, Clone, Default)]
190pub enum ParsedUnknownFieldsPolicy {
191 #[default]
193 Deny,
194 Allow,
196 Schema(NodeId),
198}
199
200impl FromEure<'_> for ParsedUnknownFieldsPolicy {
201 type Error = ParseError;
202 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
203 let node = ctx.node();
204 let node_id = ctx.node_id();
205
206 if let NodeValue::Primitive(PrimitiveValue::Text(text)) = &node.content {
208 if text.language == Language::Plaintext {
210 return match text.as_str() {
211 "deny" => Ok(ParsedUnknownFieldsPolicy::Deny),
212 "allow" => Ok(ParsedUnknownFieldsPolicy::Allow),
213 _ => Err(ParseError {
214 node_id,
215 kind: ParseErrorKind::UnknownVariant(text.as_str().to_string()),
216 }),
217 };
218 }
219 }
220
221 Ok(ParsedUnknownFieldsPolicy::Schema(node_id))
223 }
224}
225
226#[derive(Debug, Clone, Default)]
228pub struct ParsedRecordSchema {
229 pub properties: IndexMap<String, ParsedRecordFieldSchema>,
231 pub flatten: Vec<NodeId>,
233 pub unknown_fields: ParsedUnknownFieldsPolicy,
235}
236
237impl FromEure<'_> for ParsedRecordSchema {
238 type Error = ParseError;
239 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
240 let unknown_fields = ctx
242 .parse_ext_optional::<ParsedUnknownFieldsPolicy>("unknown-fields")?
243 .unwrap_or_default();
244
245 let flatten = ctx
247 .parse_ext_optional::<Vec<NodeId>>("flatten")?
248 .unwrap_or_default();
249
250 let rec = ctx.parse_record()?;
252 let mut properties = IndexMap::new();
253
254 for result in rec.unknown_fields() {
255 let (field_name, field_ctx) = result.map_err(|(key, ctx)| ParseError {
256 node_id: ctx.node_id(),
257 kind: ParseErrorKind::InvalidKeyType(key.clone()),
258 })?;
259 let field_schema = ParsedRecordFieldSchema::parse(&field_ctx)?;
260 properties.insert(field_name.to_string(), field_schema);
261 }
262
263 Ok(ParsedRecordSchema {
264 properties,
265 flatten,
266 unknown_fields,
267 })
268 }
269}
270
271#[derive(Debug, Clone, eure_macros::FromEure)]
273#[eure(crate = eure_document, rename_all = "kebab-case")]
274pub struct ParsedTupleSchema {
275 pub elements: Vec<NodeId>,
277 #[eure(ext, default)]
279 pub binding_style: Option<BindingStyle>,
280}
281
282#[derive(Debug, Clone)]
284pub struct ParsedUnionSchema {
285 pub variants: IndexMap<String, NodeId>,
287 pub unambiguous: IndexSet<String>,
290 pub interop: UnionInterop,
292 pub deny_untagged: IndexSet<String>,
294}
295
296impl FromEure<'_> for ParsedUnionSchema {
297 type Error = ParseError;
298 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
299 let rec = ctx.parse_record()?;
300 let mut variants = IndexMap::new();
301 let mut unambiguous = IndexSet::new();
302 let mut deny_untagged = IndexSet::new();
303
304 if let Some(variants_ctx) = rec.field_optional("variants") {
306 let variants_rec = variants_ctx.parse_record()?;
307 for result in variants_rec.unknown_fields() {
308 let (name, var_ctx) = result.map_err(|(key, ctx)| ParseError {
309 node_id: ctx.node_id(),
310 kind: ParseErrorKind::InvalidKeyType(key.clone()),
311 })?;
312 variants.insert(name.to_string(), var_ctx.node_id());
313
314 if var_ctx
316 .parse_ext_optional::<bool>("deny-untagged")?
317 .unwrap_or(false)
318 {
319 deny_untagged.insert(name.to_string());
320 }
321 if var_ctx
322 .parse_ext_optional::<bool>("unambiguous")?
323 .unwrap_or(false)
324 {
325 unambiguous.insert(name.to_string());
326 }
327 }
328 }
329
330 rec.allow_unknown_fields()?;
331
332 if ctx.ext_optional("variant-repr").is_some() {
334 return Err(ParseError {
335 node_id: ctx.node_id(),
336 kind: ParseErrorKind::InvalidPattern {
337 kind: "legacy extension".to_string(),
338 reason: "`$variant-repr` is removed; use `$interop.variant-repr`".to_string(),
339 },
340 });
341 }
342
343 let interop = ctx
344 .parse_ext_optional::<UnionInterop>("interop")?
345 .unwrap_or_default();
346
347 Ok(ParsedUnionSchema {
348 variants,
349 unambiguous,
350 interop,
351 deny_untagged,
352 })
353 }
354}
355
356#[derive(Debug, Clone, eure_macros::FromEure)]
358#[eure(crate = eure_document, parse_ext)]
359pub struct ParsedExtTypeSchema {
360 #[eure(flatten_ext)]
362 pub schema: NodeId,
363 #[eure(default)]
365 pub optional: bool,
366 #[eure(default)]
368 pub binding_style: Option<BindingStyle>,
369}
370
371#[derive(Debug, Clone, Default)]
373pub struct ParsedSchemaMetadata {
374 pub description: Option<Description>,
376 pub deprecated: bool,
378 pub default: Option<NodeId>,
380 pub examples: Option<Vec<NodeId>>,
382}
383
384impl ParsedSchemaMetadata {
385 pub fn parse_from_extensions(ctx: &ParseContext<'_>) -> Result<Self, ParseError> {
387 let description = ctx.parse_ext_optional::<Description>("description")?;
388 let deprecated = ctx
389 .parse_ext_optional::<bool>("deprecated")?
390 .unwrap_or(false);
391 let default = ctx.ext_optional("default").map(|ctx| ctx.node_id());
392 let examples = ctx.parse_ext_optional::<Vec<NodeId>>("examples")?;
393
394 Ok(ParsedSchemaMetadata {
395 description,
396 deprecated,
397 default,
398 examples,
399 })
400 }
401}
402
403#[derive(Debug, Clone)]
405pub enum ParsedSchemaNodeContent {
406 Any,
408 Text(TextSchema),
410 Integer(ParsedIntegerSchema),
412 Float(ParsedFloatSchema),
414 Boolean,
416 Null,
418 Literal(NodeId),
420 Array(ParsedArraySchema),
422 Map(ParsedMapSchema),
424 Record(ParsedRecordSchema),
426 Tuple(ParsedTupleSchema),
428 Union(ParsedUnionSchema),
430 Reference(TypeReference),
432}
433
434#[derive(Debug, Clone)]
436pub struct ParsedSchemaNode {
437 pub content: ParsedSchemaNodeContent,
439 pub metadata: ParsedSchemaMetadata,
441 pub ext_types: IndexMap<Identifier, ParsedExtTypeSchema>,
443 pub codegen: Option<NodeId>,
445}
446
447use eure_document::document::node::NodeValue;
452use eure_document::text::Language;
453use eure_document::value::{PrimitiveValue, ValueKind};
454
455fn get_variant_string(ctx: &ParseContext<'_>) -> Result<Option<String>, ParseError> {
457 let variant_ctx = ctx.ext_optional("variant");
458
459 match variant_ctx {
460 Some(var_ctx) => {
461 let node = var_ctx.node();
462 match &node.content {
463 NodeValue::Primitive(PrimitiveValue::Text(t)) => Ok(Some(t.as_str().to_string())),
464 _ => Err(ParseError {
465 node_id: var_ctx.node_id(),
466 kind: ParseErrorKind::TypeMismatch {
467 expected: ValueKind::Text,
468 actual: node.content.value_kind(),
469 },
470 }),
471 }
472 }
473 None => Ok(None),
474 }
475}
476
477fn parse_type_reference_string(
480 node_id: NodeId,
481 s: &str,
482) -> Result<ParsedSchemaNodeContent, ParseError> {
483 if s.is_empty() {
484 return Err(ParseError {
485 node_id,
486 kind: ParseErrorKind::InvalidPattern {
487 kind: "type reference".to_string(),
488 reason: "expected non-empty type reference, got empty string".to_string(),
489 },
490 });
491 }
492
493 let segments: Vec<&str> = s.split('.').collect();
494 match segments.as_slice() {
495 ["text"] => Ok(ParsedSchemaNodeContent::Text(TextSchema::default())),
497 ["integer"] => Ok(ParsedSchemaNodeContent::Integer(ParsedIntegerSchema {
498 range: None,
499 multiple_of: None,
500 })),
501 ["float"] => Ok(ParsedSchemaNodeContent::Float(ParsedFloatSchema {
502 range: None,
503 multiple_of: None,
504 precision: None,
505 })),
506 ["boolean"] => Ok(ParsedSchemaNodeContent::Boolean),
507 ["null"] => Ok(ParsedSchemaNodeContent::Null),
508 ["any"] => Ok(ParsedSchemaNodeContent::Any),
509
510 ["text", lang] => Ok(ParsedSchemaNodeContent::Text(TextSchema {
512 language: Some((*lang).to_string()),
513 ..Default::default()
514 })),
515
516 ["$types", type_name] => {
518 let name: Identifier = type_name.parse().map_err(|e| ParseError {
519 node_id,
520 kind: ParseErrorKind::InvalidIdentifier(e),
521 })?;
522 Ok(ParsedSchemaNodeContent::Reference(TypeReference {
523 namespace: None,
524 name,
525 }))
526 }
527
528 ["$types", namespace, type_name] => {
530 let name: Identifier = type_name.parse().map_err(|e| ParseError {
531 node_id,
532 kind: ParseErrorKind::InvalidIdentifier(e),
533 })?;
534 Ok(ParsedSchemaNodeContent::Reference(TypeReference {
535 namespace: Some((*namespace).to_string()),
536 name,
537 }))
538 }
539
540 _ => Err(ParseError {
542 node_id,
543 kind: ParseErrorKind::InvalidPattern {
544 kind: "type reference".to_string(),
545 reason: format!(
546 "expected 'text', 'integer', '$types.name', etc., got '{}'",
547 s
548 ),
549 },
550 }),
551 }
552}
553
554fn parse_primitive_as_schema(
556 ctx: &ParseContext<'_>,
557 prim: &PrimitiveValue,
558) -> Result<ParsedSchemaNodeContent, ParseError> {
559 let node_id = ctx.node_id();
560 match prim {
561 PrimitiveValue::Text(t) => {
562 match &t.language {
563 Language::Implicit => parse_type_reference_string(node_id, t.as_str()),
565 Language::Other(lang) if lang == "eure-path" => {
566 parse_type_reference_string(node_id, t.as_str())
567 }
568 _ => Ok(ParsedSchemaNodeContent::Literal(node_id)),
570 }
571 }
572 _ => Ok(ParsedSchemaNodeContent::Literal(node_id)),
574 }
575}
576
577fn parse_map_as_schema(
579 ctx: &ParseContext<'_>,
580 variant: Option<String>,
581) -> Result<ParsedSchemaNodeContent, ParseError> {
582 let node_id = ctx.node_id();
583 match variant.as_deref() {
584 Some("text") => Ok(ParsedSchemaNodeContent::Text(ctx.parse()?)),
585 Some("integer") => Ok(ParsedSchemaNodeContent::Integer(ctx.parse()?)),
586 Some("float") => Ok(ParsedSchemaNodeContent::Float(ctx.parse()?)),
587 Some("boolean") => Ok(ParsedSchemaNodeContent::Boolean),
588 Some("null") => Ok(ParsedSchemaNodeContent::Null),
589 Some("any") => Ok(ParsedSchemaNodeContent::Any),
590 Some("array") => Ok(ParsedSchemaNodeContent::Array(ctx.parse()?)),
591 Some("map") => Ok(ParsedSchemaNodeContent::Map(ctx.parse()?)),
592 Some("tuple") => Ok(ParsedSchemaNodeContent::Tuple(ctx.parse()?)),
593 Some("union") => Ok(ParsedSchemaNodeContent::Union(ctx.parse()?)),
594 Some("literal") => Ok(ParsedSchemaNodeContent::Literal(node_id)),
595 Some("record") | None => Ok(ParsedSchemaNodeContent::Record(ctx.parse()?)),
596 Some(other) => Err(ParseError {
597 node_id,
598 kind: ParseErrorKind::UnknownVariant(other.to_string()),
599 }),
600 }
601}
602
603impl FromEure<'_> for ParsedSchemaNodeContent {
604 type Error = ParseError;
605 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
606 let node_id = ctx.node_id();
607 let node = ctx.node();
608 let variant = get_variant_string(ctx)?;
609
610 match &node.content {
611 NodeValue::Hole(_) => Err(ParseError {
612 node_id,
613 kind: ParseErrorKind::UnexpectedHole,
614 }),
615
616 NodeValue::Primitive(prim) => {
617 if variant.as_deref() == Some("literal") {
619 return Ok(ParsedSchemaNodeContent::Literal(node_id));
620 }
621 parse_primitive_as_schema(ctx, prim)
622 }
623
624 NodeValue::Array(arr) => {
625 if arr.len() == 1 {
627 Ok(ParsedSchemaNodeContent::Array(ParsedArraySchema {
628 item: arr.get(0).unwrap(),
629 min_length: None,
630 max_length: None,
631 unique: false,
632 contains: None,
633 binding_style: None,
634 }))
635 } else {
636 Err(ParseError {
637 node_id,
638 kind: ParseErrorKind::InvalidPattern {
639 kind: "array schema shorthand".to_string(),
640 reason: format!(
641 "expected single-element array [type], got {}-element array",
642 arr.len()
643 ),
644 },
645 })
646 }
647 }
648
649 NodeValue::Tuple(tup) => {
650 Ok(ParsedSchemaNodeContent::Tuple(ParsedTupleSchema {
652 elements: tup.to_vec(),
653 binding_style: None,
654 }))
655 }
656
657 NodeValue::Map(_) => parse_map_as_schema(ctx, variant),
658 NodeValue::PartialMap(_) => Err(ParseError {
659 node_id,
660 kind: ParseErrorKind::TypeMismatch {
661 expected: ValueKind::Map,
662 actual: ValueKind::PartialMap,
663 },
664 }),
665 }
666 }
667}
668
669impl FromEure<'_> for ParsedSchemaNode {
670 type Error = ParseError;
671 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
672 let flatten_ctx = ctx.flatten();
675
676 let ext_types = parse_ext_types(&flatten_ctx)?;
678 let metadata = ParsedSchemaMetadata::parse_from_extensions(&flatten_ctx)?;
679 let codegen = flatten_ctx.ext_optional("codegen").map(|ctx| ctx.node_id());
680
681 let content = flatten_ctx.parse::<ParsedSchemaNodeContent>()?;
683
684 Ok(ParsedSchemaNode {
690 content,
691 metadata,
692 ext_types,
693 codegen,
694 })
695 }
696}
697
698fn parse_ext_types(
700 ctx: &ParseContext<'_>,
701) -> Result<IndexMap<Identifier, ParsedExtTypeSchema>, ParseError> {
702 let ext_type_ctx = ctx.ext_optional("ext-type");
703
704 let mut result = IndexMap::new();
705
706 if let Some(ext_type_ctx) = ext_type_ctx {
707 let rec = ext_type_ctx.parse_record()?;
708 let ext_fields: Vec<_> = rec
710 .unknown_fields()
711 .map(|r| {
712 r.map_err(|(key, ctx)| ParseError {
713 node_id: ctx.node_id(),
714 kind: ParseErrorKind::InvalidKeyType(key.clone()),
715 })
716 })
717 .collect::<Result<Vec<_>, _>>()?;
718
719 for (name, type_ctx) in ext_fields {
720 let ident: Identifier = name.parse().map_err(|e| ParseError {
721 node_id: ext_type_ctx.node_id(),
722 kind: ParseErrorKind::InvalidIdentifier(e),
723 })?;
724 let schema = type_ctx.parse::<ParsedExtTypeSchema>()?;
725 result.insert(ident, schema);
726 }
727
728 rec.allow_unknown_fields()?;
731 }
732
733 Ok(result)
734}
735
736#[cfg(test)]
737mod tests {
738 use super::*;
739 use crate::interop::VariantRepr;
740 use eure_document::document::EureDocument;
741 use eure_document::document::node::NodeValue;
742 use eure_document::text::Text;
743 use eure_document::value::PrimitiveValue;
744
745 fn create_text_node(doc: &mut EureDocument, text: &str) -> NodeId {
746 let root_id = doc.get_root_id();
747 doc.node_mut(root_id).content =
748 NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext(text.to_string())));
749 root_id
750 }
751
752 #[test]
753 fn test_binding_style_parse() {
754 let mut doc = EureDocument::new();
755 let node_id = create_text_node(&mut doc, "section");
756
757 let result: BindingStyle = doc.parse(node_id).unwrap();
758 assert_eq!(result, BindingStyle::Section);
759 }
760
761 #[test]
762 fn test_binding_style_parse_unknown() {
763 let mut doc = EureDocument::new();
764 let node_id = create_text_node(&mut doc, "unknown");
765
766 let result: Result<BindingStyle, _> = doc.parse(node_id);
767 let err = result.unwrap_err();
768 assert_eq!(
769 err.kind,
770 ParseErrorKind::UnknownVariant("unknown".to_string())
771 );
772 }
773
774 #[test]
775 fn test_description_parse_default() {
776 let mut doc = EureDocument::new();
777 let node_id = create_text_node(&mut doc, "Hello world");
778
779 let result: Description = doc.parse(node_id).unwrap();
780 assert!(matches!(result, Description::String(s) if s == "Hello world"));
781 }
782
783 #[test]
784 fn test_variant_repr_parse_string() {
785 let mut doc = EureDocument::new();
786 let node_id = create_text_node(&mut doc, "untagged");
787
788 let result: VariantRepr = doc.parse(node_id).unwrap();
789 assert_eq!(result, VariantRepr::Untagged);
790 }
791}