1use eure_document::data_model::VariantRepr;
22use eure_document::document::NodeId;
23use eure_document::identifier::Identifier;
24use eure_document::parse::{ParseContext, ParseDocument, ParseError, ParseErrorKind};
25use indexmap::{IndexMap, IndexSet};
26use num_bigint::BigInt;
27
28use crate::{BindingStyle, Description, TextSchema, TypeReference};
29
30impl ParseDocument<'_> 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 ParseDocument<'_> 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::ParseDocument)]
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::ParseDocument)]
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::ParseDocument)]
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::ParseDocument)]
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::ParseDocument)]
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 ParseDocument<'_> 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 unknown_fields: ParsedUnknownFieldsPolicy,
230}
231
232impl ParseDocument<'_> for ParsedRecordSchema {
233 type Error = ParseError;
234 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
235 let unknown_fields = ctx
237 .parse_ext_optional::<ParsedUnknownFieldsPolicy>("unknown-fields")?
238 .unwrap_or_default();
239
240 let rec = ctx.parse_record()?;
242 let mut properties = IndexMap::new();
243
244 for (field_name, field_ctx) in rec.unknown_fields() {
245 let field_schema = ParsedRecordFieldSchema::parse(&field_ctx)?;
246 properties.insert(field_name.to_string(), field_schema);
247 }
248
249 Ok(ParsedRecordSchema {
250 properties,
251 unknown_fields,
252 })
253 }
254}
255
256#[derive(Debug, Clone, eure_macros::ParseDocument)]
258#[eure(crate = eure_document, rename_all = "kebab-case")]
259pub struct ParsedTupleSchema {
260 pub elements: Vec<NodeId>,
262 #[eure(ext, default)]
264 pub binding_style: Option<BindingStyle>,
265}
266
267#[derive(Debug, Clone)]
269pub struct ParsedUnionSchema {
270 pub variants: IndexMap<String, NodeId>,
272 pub unambiguous: IndexSet<String>,
275 pub repr: VariantRepr,
277 pub deny_untagged: IndexSet<String>,
279}
280
281impl ParseDocument<'_> for ParsedUnionSchema {
282 type Error = ParseError;
283 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
284 let rec = ctx.parse_record()?;
285 let mut variants = IndexMap::new();
286 let mut unambiguous = IndexSet::new();
287 let mut deny_untagged = IndexSet::new();
288
289 if let Some(variants_ctx) = rec.field_optional("variants") {
291 let variants_rec = variants_ctx.parse_record()?;
292 for (name, var_ctx) in variants_rec.unknown_fields() {
293 variants.insert(name.to_string(), var_ctx.node_id());
294
295 if var_ctx
297 .parse_ext_optional::<bool>("deny-untagged")?
298 .unwrap_or(false)
299 {
300 deny_untagged.insert(name.to_string());
301 }
302 if var_ctx
303 .parse_ext_optional::<bool>("unambiguous")?
304 .unwrap_or(false)
305 {
306 unambiguous.insert(name.to_string());
307 }
308 }
309 }
310
311 rec.allow_unknown_fields()?;
312
313 let repr = ctx
315 .parse_ext_optional::<VariantRepr>("variant-repr")?
316 .unwrap_or_default();
317
318 Ok(ParsedUnionSchema {
319 variants,
320 unambiguous,
321 repr,
322 deny_untagged,
323 })
324 }
325}
326
327#[derive(Debug, Clone, eure_macros::ParseDocument)]
329#[eure(crate = eure_document, parse_ext)]
330pub struct ParsedExtTypeSchema {
331 #[eure(flatten_ext)]
333 pub schema: NodeId,
334 #[eure(default)]
336 pub optional: bool,
337}
338
339#[derive(Debug, Clone, Default)]
341pub struct ParsedSchemaMetadata {
342 pub description: Option<Description>,
344 pub deprecated: bool,
346 pub default: Option<NodeId>,
348 pub examples: Option<Vec<NodeId>>,
350}
351
352impl ParsedSchemaMetadata {
353 pub fn parse_from_extensions(ctx: &ParseContext<'_>) -> Result<Self, ParseError> {
355 let description = ctx.parse_ext_optional::<Description>("description")?;
356 let deprecated = ctx
357 .parse_ext_optional::<bool>("deprecated")?
358 .unwrap_or(false);
359 let default = ctx.ext_optional("default").map(|ctx| ctx.node_id());
360 let examples = ctx.parse_ext_optional::<Vec<NodeId>>("examples")?;
361
362 Ok(ParsedSchemaMetadata {
363 description,
364 deprecated,
365 default,
366 examples,
367 })
368 }
369}
370
371#[derive(Debug, Clone)]
373pub enum ParsedSchemaNodeContent {
374 Any,
376 Text(TextSchema),
378 Integer(ParsedIntegerSchema),
380 Float(ParsedFloatSchema),
382 Boolean,
384 Null,
386 Literal(NodeId),
388 Array(ParsedArraySchema),
390 Map(ParsedMapSchema),
392 Record(ParsedRecordSchema),
394 Tuple(ParsedTupleSchema),
396 Union(ParsedUnionSchema),
398 Reference(TypeReference),
400}
401
402#[derive(Debug, Clone)]
404pub struct ParsedSchemaNode {
405 pub content: ParsedSchemaNodeContent,
407 pub metadata: ParsedSchemaMetadata,
409 pub ext_types: IndexMap<Identifier, ParsedExtTypeSchema>,
411}
412
413use eure_document::document::node::NodeValue;
418use eure_document::text::Language;
419use eure_document::value::{PrimitiveValue, ValueKind};
420
421fn get_variant_string(ctx: &ParseContext<'_>) -> Result<Option<String>, ParseError> {
423 let variant_ctx = ctx.ext_optional("variant");
424
425 match variant_ctx {
426 Some(var_ctx) => {
427 let node = var_ctx.node();
428 match &node.content {
429 NodeValue::Primitive(PrimitiveValue::Text(t)) => Ok(Some(t.as_str().to_string())),
430 _ => Err(ParseError {
431 node_id: var_ctx.node_id(),
432 kind: ParseErrorKind::TypeMismatch {
433 expected: ValueKind::Text,
434 actual: node.content.value_kind().unwrap_or(ValueKind::Null),
435 },
436 }),
437 }
438 }
439 None => Ok(None),
440 }
441}
442
443fn parse_type_reference_string(
446 node_id: NodeId,
447 s: &str,
448) -> Result<ParsedSchemaNodeContent, ParseError> {
449 if s.is_empty() {
450 return Err(ParseError {
451 node_id,
452 kind: ParseErrorKind::InvalidPattern {
453 kind: "type reference".to_string(),
454 reason: "expected non-empty type reference, got empty string".to_string(),
455 },
456 });
457 }
458
459 let segments: Vec<&str> = s.split('.').collect();
460 match segments.as_slice() {
461 ["text"] => Ok(ParsedSchemaNodeContent::Text(TextSchema::default())),
463 ["integer"] => Ok(ParsedSchemaNodeContent::Integer(ParsedIntegerSchema {
464 range: None,
465 multiple_of: None,
466 })),
467 ["float"] => Ok(ParsedSchemaNodeContent::Float(ParsedFloatSchema {
468 range: None,
469 multiple_of: None,
470 precision: None,
471 })),
472 ["boolean"] => Ok(ParsedSchemaNodeContent::Boolean),
473 ["null"] => Ok(ParsedSchemaNodeContent::Null),
474 ["any"] => Ok(ParsedSchemaNodeContent::Any),
475
476 ["text", lang] => Ok(ParsedSchemaNodeContent::Text(TextSchema {
478 language: Some((*lang).to_string()),
479 ..Default::default()
480 })),
481
482 ["$types", type_name] => {
484 let name: Identifier = type_name.parse().map_err(|e| ParseError {
485 node_id,
486 kind: ParseErrorKind::InvalidIdentifier(e),
487 })?;
488 Ok(ParsedSchemaNodeContent::Reference(TypeReference {
489 namespace: None,
490 name,
491 }))
492 }
493
494 ["$types", namespace, type_name] => {
496 let name: Identifier = type_name.parse().map_err(|e| ParseError {
497 node_id,
498 kind: ParseErrorKind::InvalidIdentifier(e),
499 })?;
500 Ok(ParsedSchemaNodeContent::Reference(TypeReference {
501 namespace: Some((*namespace).to_string()),
502 name,
503 }))
504 }
505
506 _ => Err(ParseError {
508 node_id,
509 kind: ParseErrorKind::InvalidPattern {
510 kind: "type reference".to_string(),
511 reason: format!(
512 "expected 'text', 'integer', '$types.name', etc., got '{}'",
513 s
514 ),
515 },
516 }),
517 }
518}
519
520fn parse_primitive_as_schema(
522 ctx: &ParseContext<'_>,
523 prim: &PrimitiveValue,
524) -> Result<ParsedSchemaNodeContent, ParseError> {
525 let node_id = ctx.node_id();
526 match prim {
527 PrimitiveValue::Text(t) => {
528 match &t.language {
529 Language::Implicit => parse_type_reference_string(node_id, t.as_str()),
531 Language::Other(lang) if lang == "eure-path" => {
532 parse_type_reference_string(node_id, t.as_str())
533 }
534 _ => Ok(ParsedSchemaNodeContent::Literal(node_id)),
536 }
537 }
538 _ => Ok(ParsedSchemaNodeContent::Literal(node_id)),
540 }
541}
542
543fn parse_map_as_schema(
545 ctx: &ParseContext<'_>,
546 variant: Option<String>,
547) -> Result<ParsedSchemaNodeContent, ParseError> {
548 let node_id = ctx.node_id();
549 match variant.as_deref() {
550 Some("text") => Ok(ParsedSchemaNodeContent::Text(ctx.parse()?)),
551 Some("integer") => Ok(ParsedSchemaNodeContent::Integer(ctx.parse()?)),
552 Some("float") => Ok(ParsedSchemaNodeContent::Float(ctx.parse()?)),
553 Some("boolean") => Ok(ParsedSchemaNodeContent::Boolean),
554 Some("null") => Ok(ParsedSchemaNodeContent::Null),
555 Some("any") => Ok(ParsedSchemaNodeContent::Any),
556 Some("array") => Ok(ParsedSchemaNodeContent::Array(ctx.parse()?)),
557 Some("map") => Ok(ParsedSchemaNodeContent::Map(ctx.parse()?)),
558 Some("tuple") => Ok(ParsedSchemaNodeContent::Tuple(ctx.parse()?)),
559 Some("union") => Ok(ParsedSchemaNodeContent::Union(ctx.parse()?)),
560 Some("literal") => Ok(ParsedSchemaNodeContent::Literal(node_id)),
561 Some("record") | None => Ok(ParsedSchemaNodeContent::Record(ctx.parse()?)),
562 Some(other) => Err(ParseError {
563 node_id,
564 kind: ParseErrorKind::UnknownVariant(other.to_string()),
565 }),
566 }
567}
568
569impl ParseDocument<'_> for ParsedSchemaNodeContent {
570 type Error = ParseError;
571 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
572 let node_id = ctx.node_id();
573 let node = ctx.node();
574 let variant = get_variant_string(ctx)?;
575
576 match &node.content {
577 NodeValue::Hole(_) => Err(ParseError {
578 node_id,
579 kind: ParseErrorKind::UnexpectedHole,
580 }),
581
582 NodeValue::Primitive(prim) => {
583 if variant.as_deref() == Some("literal") {
585 return Ok(ParsedSchemaNodeContent::Literal(node_id));
586 }
587 parse_primitive_as_schema(ctx, prim)
588 }
589
590 NodeValue::Array(arr) => {
591 if arr.len() == 1 {
593 Ok(ParsedSchemaNodeContent::Array(ParsedArraySchema {
594 item: arr.0[0],
595 min_length: None,
596 max_length: None,
597 unique: false,
598 contains: None,
599 binding_style: None,
600 }))
601 } else {
602 Err(ParseError {
603 node_id,
604 kind: ParseErrorKind::InvalidPattern {
605 kind: "array schema shorthand".to_string(),
606 reason: format!(
607 "expected single-element array [type], got {}-element array",
608 arr.len()
609 ),
610 },
611 })
612 }
613 }
614
615 NodeValue::Tuple(tup) => {
616 Ok(ParsedSchemaNodeContent::Tuple(ParsedTupleSchema {
618 elements: tup.0.clone(),
619 binding_style: None,
620 }))
621 }
622
623 NodeValue::Map(_) => parse_map_as_schema(ctx, variant),
624 }
625 }
626}
627
628impl ParseDocument<'_> for ParsedSchemaNode {
629 type Error = ParseError;
630 fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
631 let flatten_ctx = ctx.flatten();
634
635 let ext_types = parse_ext_types(&flatten_ctx)?;
637 let metadata = ParsedSchemaMetadata::parse_from_extensions(&flatten_ctx)?;
638
639 let content = flatten_ctx.parse::<ParsedSchemaNodeContent>()?;
641
642 Ok(ParsedSchemaNode {
648 content,
649 metadata,
650 ext_types,
651 })
652 }
653}
654
655fn parse_ext_types(
657 ctx: &ParseContext<'_>,
658) -> Result<IndexMap<Identifier, ParsedExtTypeSchema>, ParseError> {
659 let ext_type_ctx = ctx.ext_optional("ext-type");
660
661 let mut result = IndexMap::new();
662
663 if let Some(ext_type_ctx) = ext_type_ctx {
664 let rec = ext_type_ctx.parse_record()?;
665 let ext_fields: Vec<_> = rec.unknown_fields().collect();
667
668 for (name, type_ctx) in ext_fields {
669 let ident: Identifier = name.parse().map_err(|e| ParseError {
670 node_id: ext_type_ctx.node_id(),
671 kind: ParseErrorKind::InvalidIdentifier(e),
672 })?;
673 let schema = type_ctx.parse::<ParsedExtTypeSchema>()?;
674 result.insert(ident, schema);
675 }
676
677 rec.allow_unknown_fields()?;
680 }
681
682 Ok(result)
683}
684
685#[cfg(test)]
686mod tests {
687 use super::*;
688 use eure_document::document::EureDocument;
689 use eure_document::document::node::NodeValue;
690 use eure_document::text::Text;
691 use eure_document::value::PrimitiveValue;
692
693 fn create_text_node(doc: &mut EureDocument, text: &str) -> NodeId {
694 let root_id = doc.get_root_id();
695 doc.node_mut(root_id).content =
696 NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext(text.to_string())));
697 root_id
698 }
699
700 #[test]
701 fn test_binding_style_parse() {
702 let mut doc = EureDocument::new();
703 let node_id = create_text_node(&mut doc, "section");
704
705 let result: BindingStyle = doc.parse(node_id).unwrap();
706 assert_eq!(result, BindingStyle::Section);
707 }
708
709 #[test]
710 fn test_binding_style_parse_unknown() {
711 let mut doc = EureDocument::new();
712 let node_id = create_text_node(&mut doc, "unknown");
713
714 let result: Result<BindingStyle, _> = doc.parse(node_id);
715 let err = result.unwrap_err();
716 assert_eq!(
717 err.kind,
718 ParseErrorKind::UnknownVariant("unknown".to_string())
719 );
720 }
721
722 #[test]
723 fn test_description_parse_default() {
724 let mut doc = EureDocument::new();
725 let node_id = create_text_node(&mut doc, "Hello world");
726
727 let result: Description = doc.parse(node_id).unwrap();
728 assert!(matches!(result, Description::String(s) if s == "Hello world"));
729 }
730
731 #[test]
732 fn test_variant_repr_parse_string() {
733 let mut doc = EureDocument::new();
734 let node_id = create_text_node(&mut doc, "untagged");
735
736 let result: VariantRepr = doc.parse(node_id).unwrap();
737 assert_eq!(result, VariantRepr::Untagged);
738 }
739}