1use eure_document::data_model::VariantRepr;
22use eure_document::document::NodeId;
23use eure_document::identifier::Identifier;
24use eure_document::parse::{FromEure, ParseContext, ParseError, ParseErrorKind};
25use indexmap::{IndexMap, IndexSet};
26use num_bigint::BigInt;
27
28use crate::{BindingStyle, Description, 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}
184
185#[derive(Debug, Clone, Default)]
187pub enum ParsedUnknownFieldsPolicy {
188 #[default]
190 Deny,
191 Allow,
193 Schema(NodeId),
195}
196
197impl FromEure<'_> for ParsedUnknownFieldsPolicy {
198 type Error = ParseError;
199 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
200 let node = ctx.node();
201 let node_id = ctx.node_id();
202
203 if let NodeValue::Primitive(PrimitiveValue::Text(text)) = &node.content {
205 if text.language == Language::Plaintext {
207 return match text.as_str() {
208 "deny" => Ok(ParsedUnknownFieldsPolicy::Deny),
209 "allow" => Ok(ParsedUnknownFieldsPolicy::Allow),
210 _ => Err(ParseError {
211 node_id,
212 kind: ParseErrorKind::UnknownVariant(text.as_str().to_string()),
213 }),
214 };
215 }
216 }
217
218 Ok(ParsedUnknownFieldsPolicy::Schema(node_id))
220 }
221}
222
223#[derive(Debug, Clone, Default)]
225pub struct ParsedRecordSchema {
226 pub properties: IndexMap<String, ParsedRecordFieldSchema>,
228 pub flatten: Vec<NodeId>,
230 pub unknown_fields: ParsedUnknownFieldsPolicy,
232}
233
234impl FromEure<'_> for ParsedRecordSchema {
235 type Error = ParseError;
236 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
237 let unknown_fields = ctx
239 .parse_ext_optional::<ParsedUnknownFieldsPolicy>("unknown-fields")?
240 .unwrap_or_default();
241
242 let flatten = ctx
244 .parse_ext_optional::<Vec<NodeId>>("flatten")?
245 .unwrap_or_default();
246
247 let rec = ctx.parse_record()?;
249 let mut properties = IndexMap::new();
250
251 for result in rec.unknown_fields() {
252 let (field_name, field_ctx) = result.map_err(|(key, ctx)| ParseError {
253 node_id: ctx.node_id(),
254 kind: ParseErrorKind::InvalidKeyType(key.clone()),
255 })?;
256 let field_schema = ParsedRecordFieldSchema::parse(&field_ctx)?;
257 properties.insert(field_name.to_string(), field_schema);
258 }
259
260 Ok(ParsedRecordSchema {
261 properties,
262 flatten,
263 unknown_fields,
264 })
265 }
266}
267
268#[derive(Debug, Clone, eure_macros::FromEure)]
270#[eure(crate = eure_document, rename_all = "kebab-case")]
271pub struct ParsedTupleSchema {
272 pub elements: Vec<NodeId>,
274 #[eure(ext, default)]
276 pub binding_style: Option<BindingStyle>,
277}
278
279#[derive(Debug, Clone)]
281pub struct ParsedUnionSchema {
282 pub variants: IndexMap<String, NodeId>,
284 pub unambiguous: IndexSet<String>,
287 pub repr: VariantRepr,
289 pub deny_untagged: IndexSet<String>,
291}
292
293impl FromEure<'_> for ParsedUnionSchema {
294 type Error = ParseError;
295 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
296 let rec = ctx.parse_record()?;
297 let mut variants = IndexMap::new();
298 let mut unambiguous = IndexSet::new();
299 let mut deny_untagged = IndexSet::new();
300
301 if let Some(variants_ctx) = rec.field_optional("variants") {
303 let variants_rec = variants_ctx.parse_record()?;
304 for result in variants_rec.unknown_fields() {
305 let (name, var_ctx) = result.map_err(|(key, ctx)| ParseError {
306 node_id: ctx.node_id(),
307 kind: ParseErrorKind::InvalidKeyType(key.clone()),
308 })?;
309 variants.insert(name.to_string(), var_ctx.node_id());
310
311 if var_ctx
313 .parse_ext_optional::<bool>("deny-untagged")?
314 .unwrap_or(false)
315 {
316 deny_untagged.insert(name.to_string());
317 }
318 if var_ctx
319 .parse_ext_optional::<bool>("unambiguous")?
320 .unwrap_or(false)
321 {
322 unambiguous.insert(name.to_string());
323 }
324 }
325 }
326
327 rec.allow_unknown_fields()?;
328
329 let repr = ctx
331 .parse_ext_optional::<VariantRepr>("variant-repr")?
332 .unwrap_or_default();
333
334 Ok(ParsedUnionSchema {
335 variants,
336 unambiguous,
337 repr,
338 deny_untagged,
339 })
340 }
341}
342
343#[derive(Debug, Clone, eure_macros::FromEure)]
345#[eure(crate = eure_document, parse_ext)]
346pub struct ParsedExtTypeSchema {
347 #[eure(flatten_ext)]
349 pub schema: NodeId,
350 #[eure(default)]
352 pub optional: bool,
353}
354
355#[derive(Debug, Clone, Default)]
357pub struct ParsedSchemaMetadata {
358 pub description: Option<Description>,
360 pub deprecated: bool,
362 pub default: Option<NodeId>,
364 pub examples: Option<Vec<NodeId>>,
366}
367
368impl ParsedSchemaMetadata {
369 pub fn parse_from_extensions(ctx: &ParseContext<'_>) -> Result<Self, ParseError> {
371 let description = ctx.parse_ext_optional::<Description>("description")?;
372 let deprecated = ctx
373 .parse_ext_optional::<bool>("deprecated")?
374 .unwrap_or(false);
375 let default = ctx.ext_optional("default").map(|ctx| ctx.node_id());
376 let examples = ctx.parse_ext_optional::<Vec<NodeId>>("examples")?;
377
378 Ok(ParsedSchemaMetadata {
379 description,
380 deprecated,
381 default,
382 examples,
383 })
384 }
385}
386
387#[derive(Debug, Clone)]
389pub enum ParsedSchemaNodeContent {
390 Any,
392 Text(TextSchema),
394 Integer(ParsedIntegerSchema),
396 Float(ParsedFloatSchema),
398 Boolean,
400 Null,
402 Literal(NodeId),
404 Array(ParsedArraySchema),
406 Map(ParsedMapSchema),
408 Record(ParsedRecordSchema),
410 Tuple(ParsedTupleSchema),
412 Union(ParsedUnionSchema),
414 Reference(TypeReference),
416}
417
418#[derive(Debug, Clone)]
420pub struct ParsedSchemaNode {
421 pub content: ParsedSchemaNodeContent,
423 pub metadata: ParsedSchemaMetadata,
425 pub ext_types: IndexMap<Identifier, ParsedExtTypeSchema>,
427}
428
429use eure_document::document::node::NodeValue;
434use eure_document::text::Language;
435use eure_document::value::{PrimitiveValue, ValueKind};
436
437fn get_variant_string(ctx: &ParseContext<'_>) -> Result<Option<String>, ParseError> {
439 let variant_ctx = ctx.ext_optional("variant");
440
441 match variant_ctx {
442 Some(var_ctx) => {
443 let node = var_ctx.node();
444 match &node.content {
445 NodeValue::Primitive(PrimitiveValue::Text(t)) => Ok(Some(t.as_str().to_string())),
446 _ => Err(ParseError {
447 node_id: var_ctx.node_id(),
448 kind: ParseErrorKind::TypeMismatch {
449 expected: ValueKind::Text,
450 actual: node.content.value_kind(),
451 },
452 }),
453 }
454 }
455 None => Ok(None),
456 }
457}
458
459fn parse_type_reference_string(
462 node_id: NodeId,
463 s: &str,
464) -> Result<ParsedSchemaNodeContent, ParseError> {
465 if s.is_empty() {
466 return Err(ParseError {
467 node_id,
468 kind: ParseErrorKind::InvalidPattern {
469 kind: "type reference".to_string(),
470 reason: "expected non-empty type reference, got empty string".to_string(),
471 },
472 });
473 }
474
475 let segments: Vec<&str> = s.split('.').collect();
476 match segments.as_slice() {
477 ["text"] => Ok(ParsedSchemaNodeContent::Text(TextSchema::default())),
479 ["integer"] => Ok(ParsedSchemaNodeContent::Integer(ParsedIntegerSchema {
480 range: None,
481 multiple_of: None,
482 })),
483 ["float"] => Ok(ParsedSchemaNodeContent::Float(ParsedFloatSchema {
484 range: None,
485 multiple_of: None,
486 precision: None,
487 })),
488 ["boolean"] => Ok(ParsedSchemaNodeContent::Boolean),
489 ["null"] => Ok(ParsedSchemaNodeContent::Null),
490 ["any"] => Ok(ParsedSchemaNodeContent::Any),
491
492 ["text", lang] => Ok(ParsedSchemaNodeContent::Text(TextSchema {
494 language: Some((*lang).to_string()),
495 ..Default::default()
496 })),
497
498 ["$types", type_name] => {
500 let name: Identifier = type_name.parse().map_err(|e| ParseError {
501 node_id,
502 kind: ParseErrorKind::InvalidIdentifier(e),
503 })?;
504 Ok(ParsedSchemaNodeContent::Reference(TypeReference {
505 namespace: None,
506 name,
507 }))
508 }
509
510 ["$types", namespace, type_name] => {
512 let name: Identifier = type_name.parse().map_err(|e| ParseError {
513 node_id,
514 kind: ParseErrorKind::InvalidIdentifier(e),
515 })?;
516 Ok(ParsedSchemaNodeContent::Reference(TypeReference {
517 namespace: Some((*namespace).to_string()),
518 name,
519 }))
520 }
521
522 _ => Err(ParseError {
524 node_id,
525 kind: ParseErrorKind::InvalidPattern {
526 kind: "type reference".to_string(),
527 reason: format!(
528 "expected 'text', 'integer', '$types.name', etc., got '{}'",
529 s
530 ),
531 },
532 }),
533 }
534}
535
536fn parse_primitive_as_schema(
538 ctx: &ParseContext<'_>,
539 prim: &PrimitiveValue,
540) -> Result<ParsedSchemaNodeContent, ParseError> {
541 let node_id = ctx.node_id();
542 match prim {
543 PrimitiveValue::Text(t) => {
544 match &t.language {
545 Language::Implicit => parse_type_reference_string(node_id, t.as_str()),
547 Language::Other(lang) if lang == "eure-path" => {
548 parse_type_reference_string(node_id, t.as_str())
549 }
550 _ => Ok(ParsedSchemaNodeContent::Literal(node_id)),
552 }
553 }
554 _ => Ok(ParsedSchemaNodeContent::Literal(node_id)),
556 }
557}
558
559fn parse_map_as_schema(
561 ctx: &ParseContext<'_>,
562 variant: Option<String>,
563) -> Result<ParsedSchemaNodeContent, ParseError> {
564 let node_id = ctx.node_id();
565 match variant.as_deref() {
566 Some("text") => Ok(ParsedSchemaNodeContent::Text(ctx.parse()?)),
567 Some("integer") => Ok(ParsedSchemaNodeContent::Integer(ctx.parse()?)),
568 Some("float") => Ok(ParsedSchemaNodeContent::Float(ctx.parse()?)),
569 Some("boolean") => Ok(ParsedSchemaNodeContent::Boolean),
570 Some("null") => Ok(ParsedSchemaNodeContent::Null),
571 Some("any") => Ok(ParsedSchemaNodeContent::Any),
572 Some("array") => Ok(ParsedSchemaNodeContent::Array(ctx.parse()?)),
573 Some("map") => Ok(ParsedSchemaNodeContent::Map(ctx.parse()?)),
574 Some("tuple") => Ok(ParsedSchemaNodeContent::Tuple(ctx.parse()?)),
575 Some("union") => Ok(ParsedSchemaNodeContent::Union(ctx.parse()?)),
576 Some("literal") => Ok(ParsedSchemaNodeContent::Literal(node_id)),
577 Some("record") | None => Ok(ParsedSchemaNodeContent::Record(ctx.parse()?)),
578 Some(other) => Err(ParseError {
579 node_id,
580 kind: ParseErrorKind::UnknownVariant(other.to_string()),
581 }),
582 }
583}
584
585impl FromEure<'_> for ParsedSchemaNodeContent {
586 type Error = ParseError;
587 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
588 let node_id = ctx.node_id();
589 let node = ctx.node();
590 let variant = get_variant_string(ctx)?;
591
592 match &node.content {
593 NodeValue::Hole(_) => Err(ParseError {
594 node_id,
595 kind: ParseErrorKind::UnexpectedHole,
596 }),
597
598 NodeValue::Primitive(prim) => {
599 if variant.as_deref() == Some("literal") {
601 return Ok(ParsedSchemaNodeContent::Literal(node_id));
602 }
603 parse_primitive_as_schema(ctx, prim)
604 }
605
606 NodeValue::Array(arr) => {
607 if arr.len() == 1 {
609 Ok(ParsedSchemaNodeContent::Array(ParsedArraySchema {
610 item: arr.get(0).unwrap(),
611 min_length: None,
612 max_length: None,
613 unique: false,
614 contains: None,
615 binding_style: None,
616 }))
617 } else {
618 Err(ParseError {
619 node_id,
620 kind: ParseErrorKind::InvalidPattern {
621 kind: "array schema shorthand".to_string(),
622 reason: format!(
623 "expected single-element array [type], got {}-element array",
624 arr.len()
625 ),
626 },
627 })
628 }
629 }
630
631 NodeValue::Tuple(tup) => {
632 Ok(ParsedSchemaNodeContent::Tuple(ParsedTupleSchema {
634 elements: tup.to_vec(),
635 binding_style: None,
636 }))
637 }
638
639 NodeValue::Map(_) => parse_map_as_schema(ctx, variant),
640 }
641 }
642}
643
644impl FromEure<'_> for ParsedSchemaNode {
645 type Error = ParseError;
646 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
647 let flatten_ctx = ctx.flatten();
650
651 let ext_types = parse_ext_types(&flatten_ctx)?;
653 let metadata = ParsedSchemaMetadata::parse_from_extensions(&flatten_ctx)?;
654
655 let content = flatten_ctx.parse::<ParsedSchemaNodeContent>()?;
657
658 Ok(ParsedSchemaNode {
664 content,
665 metadata,
666 ext_types,
667 })
668 }
669}
670
671fn parse_ext_types(
673 ctx: &ParseContext<'_>,
674) -> Result<IndexMap<Identifier, ParsedExtTypeSchema>, ParseError> {
675 let ext_type_ctx = ctx.ext_optional("ext-type");
676
677 let mut result = IndexMap::new();
678
679 if let Some(ext_type_ctx) = ext_type_ctx {
680 let rec = ext_type_ctx.parse_record()?;
681 let ext_fields: Vec<_> = rec
683 .unknown_fields()
684 .map(|r| {
685 r.map_err(|(key, ctx)| ParseError {
686 node_id: ctx.node_id(),
687 kind: ParseErrorKind::InvalidKeyType(key.clone()),
688 })
689 })
690 .collect::<Result<Vec<_>, _>>()?;
691
692 for (name, type_ctx) in ext_fields {
693 let ident: Identifier = name.parse().map_err(|e| ParseError {
694 node_id: ext_type_ctx.node_id(),
695 kind: ParseErrorKind::InvalidIdentifier(e),
696 })?;
697 let schema = type_ctx.parse::<ParsedExtTypeSchema>()?;
698 result.insert(ident, schema);
699 }
700
701 rec.allow_unknown_fields()?;
704 }
705
706 Ok(result)
707}
708
709#[cfg(test)]
710mod tests {
711 use super::*;
712 use eure_document::document::EureDocument;
713 use eure_document::document::node::NodeValue;
714 use eure_document::text::Text;
715 use eure_document::value::PrimitiveValue;
716
717 fn create_text_node(doc: &mut EureDocument, text: &str) -> NodeId {
718 let root_id = doc.get_root_id();
719 doc.node_mut(root_id).content =
720 NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext(text.to_string())));
721 root_id
722 }
723
724 #[test]
725 fn test_binding_style_parse() {
726 let mut doc = EureDocument::new();
727 let node_id = create_text_node(&mut doc, "section");
728
729 let result: BindingStyle = doc.parse(node_id).unwrap();
730 assert_eq!(result, BindingStyle::Section);
731 }
732
733 #[test]
734 fn test_binding_style_parse_unknown() {
735 let mut doc = EureDocument::new();
736 let node_id = create_text_node(&mut doc, "unknown");
737
738 let result: Result<BindingStyle, _> = doc.parse(node_id);
739 let err = result.unwrap_err();
740 assert_eq!(
741 err.kind,
742 ParseErrorKind::UnknownVariant("unknown".to_string())
743 );
744 }
745
746 #[test]
747 fn test_description_parse_default() {
748 let mut doc = EureDocument::new();
749 let node_id = create_text_node(&mut doc, "Hello world");
750
751 let result: Description = doc.parse(node_id).unwrap();
752 assert!(matches!(result, Description::String(s) if s == "Hello world"));
753 }
754
755 #[test]
756 fn test_variant_repr_parse_string() {
757 let mut doc = EureDocument::new();
758 let node_id = create_text_node(&mut doc, "untagged");
759
760 let result: VariantRepr = doc.parse(node_id).unwrap();
761 assert_eq!(result, VariantRepr::Untagged);
762 }
763}