1use crate::span::{Position, Span, Spanned};
6use crate::error::{SchemaParseError, SchemaErrorKind};
7use super::{SchemaType, FieldDef, StructDef, EnumDef, HashSet, Schema, HashMap};
8
9#[derive(Debug)]
10struct Parser<'a> {
11 source: &'a str,
12 bytes: &'a [u8],
13 offset: usize,
14 line: usize,
15 column: usize,
16}
17
18impl<'a> Parser<'a> {
19 fn new(source: &'a str) -> Self {
20 Self { source, bytes: source.as_bytes(), offset: 0, line: 1, column: 1 }
21 }
22
23 fn position(&self) -> Position {
24 Position { offset: self.offset, line: self.line, column: self.column }
25 }
26
27 fn peek(&self) -> Option<u8> {
28 self.bytes.get(self.offset).copied()
29 }
30
31 fn advance(&mut self) {
32 if let Some(byte) = self.peek() {
33 if byte == b'\n'{
34 self.column = 1;
35 self.line += 1;
36 } else {
37 self.column += 1;
38 }
39 self.offset += 1;
40 }
41 }
42
43 fn skip_whitespace(&mut self) {
44 loop {
45 match self.peek() {
46 Some(b' ' | b'\t' | b'\n' | b'\r') => self.advance(),
47 Some(b'/') if self.bytes.get(self.offset + 1) == Some(&b'/') => {
48 while self.peek().is_some_and(|b| b != b'\n') {
49 self.advance();
50 }
51 }
52 _ => break,
53 }
54 }
55 }
56
57 fn expect_char(&mut self, expected: u8) -> Result<(), SchemaParseError> {
58 let start = self.position();
59 match self.peek() {
60 Some(b) if b == expected => {
61 self.advance();
62 Ok(())
63 },
64 Some(b) => {
65 self.advance();
66 let end = self.position();
67 Err(SchemaParseError {
68 span: Span {
69 start,
70 end
71 },
72 kind: SchemaErrorKind::UnexpectedToken {
73 expected: format!("'{}'", expected as char),
74 found: format!("'{}'", b as char)
75 }
76 })
77 },
78 None => {
79 Err(SchemaParseError {
80 span: Span {
81 start,
82 end: start
83 },
84 kind: SchemaErrorKind::UnexpectedToken {
85 expected: format!("'{}'", expected as char),
86 found: "end of input".to_string()
87 }
88 })
89 }
90 }
91 }
92
93 fn parse_identifier(&mut self) -> Result<Spanned<String>, SchemaParseError> {
94 let start = self.position();
95
96 match self.peek() {
98 Some(b) if b.is_ascii_alphabetic() || b == b'_' => {},
99 Some(b) => {
100 self.advance();
101 let end = self.position();
102 return Err(SchemaParseError {
103 span: Span { start, end },
104 kind: SchemaErrorKind::UnexpectedToken {
105 expected: "identifier".to_string(),
106 found: format!("'{}'", b as char),
107 },
108 });
109 },
110 None => {
111 return Err(SchemaParseError {
112 span: Span { start, end: start },
113 kind: SchemaErrorKind::UnexpectedToken {
114 expected: "identifier".to_string(),
115 found: "end of input".to_string(),
116 },
117 });
118 },
119 }
120
121 while self.peek().is_some_and(|b| b.is_ascii_alphanumeric() || b == b'_') {
123 self.advance();
124 }
125
126 let end = self.position();
128 Ok(Spanned {
129 value: self.source[start.offset..end.offset].to_string(),
130 span: Span { start, end },
131 })
132 }
133
134 #[allow(clippy::too_many_lines)]
135 fn parse_type(&mut self) -> Result<Spanned<SchemaType>, SchemaParseError> {
136 self.skip_whitespace();
137 let start = self.position();
138
139 match self.peek() {
140 Some(b'[') => {
141 self.advance();
143 self.skip_whitespace();
144 let inner = self.parse_type()?;
145 self.skip_whitespace();
146 self.expect_char(b']')?;
147 let end = self.position();
148 Ok(Spanned {
149 value: SchemaType::List(Box::new(inner.value)),
150 span: Span { start, end },
151 })
152 }
153 Some(b'{') => {
154 self.advance();
156 self.skip_whitespace();
157 let key_type = self.parse_type()?;
158 match &key_type.value {
160 SchemaType::String | SchemaType::Integer | SchemaType::EnumRef(_) => {}
161 _ => {
162 return Err(SchemaParseError {
163 span: key_type.span,
164 kind: SchemaErrorKind::InvalidMapKeyType {
165 found: format!("{:?}", key_type.value),
166 },
167 });
168 }
169 }
170 self.skip_whitespace();
171 self.expect_char(b':')?;
172 self.skip_whitespace();
173 let value_type = self.parse_type()?;
174 self.skip_whitespace();
175 self.expect_char(b'}')?;
176 let end = self.position();
177 Ok(Spanned {
178 value: SchemaType::Map(Box::new(key_type.value), Box::new(value_type.value)),
179 span: Span { start, end },
180 })
181 }
182 Some(b'(') => {
183 let saved = (self.offset, self.line, self.column);
188 self.advance(); self.skip_whitespace();
190
191 let is_struct = if self.peek() == Some(b')') {
192 true } else {
194 let probe_pos = (self.offset, self.line, self.column);
196 let is_field = if let Ok(_id) = self.parse_identifier() {
197 self.skip_whitespace();
198
199 self.peek() == Some(b':')
200 } else {
201 false
202 };
203 self.offset = probe_pos.0;
205 self.line = probe_pos.1;
206 self.column = probe_pos.2;
207 is_field
208 };
209
210 self.offset = saved.0;
212 self.line = saved.1;
213 self.column = saved.2;
214
215 if is_struct {
216 let struct_def = self.parse_struct()?;
217 let end = self.position();
218 Ok(Spanned {
219 value: SchemaType::Struct(struct_def),
220 span: Span { start, end },
221 })
222 } else {
223 let types = self.parse_tuple_type()?;
224 let end = self.position();
225 Ok(Spanned {
226 value: SchemaType::Tuple(types),
227 span: Span { start, end },
228 })
229 }
230 }
231 Some(b) if b.is_ascii_alphabetic() => {
232 let id = self.parse_identifier()?;
234 match id.value.as_str() {
235 "String" => Ok(Spanned { value: SchemaType::String, span: id.span }),
236 "Integer" => Ok(Spanned { value: SchemaType::Integer, span: id.span }),
237 "Float" => Ok(Spanned { value: SchemaType::Float, span: id.span }),
238 "Bool" => Ok(Spanned { value: SchemaType::Bool, span: id.span }),
239 "Option" => {
240 self.skip_whitespace();
242 self.expect_char(b'(')?;
243 self.skip_whitespace();
244 let inner = self.parse_type()?;
245 self.skip_whitespace();
246 self.expect_char(b')')?;
247 let end = self.position();
248 Ok(Spanned {
249 value: SchemaType::Option(Box::new(inner.value)),
250 span: Span { start, end },
251 })
252 }
253 _ => Ok(Spanned { value: SchemaType::EnumRef(id.value), span: id.span }),
254 }
255 }
256 Some(b) => {
257 self.advance();
259 let end = self.position();
260 Err(SchemaParseError {
261 span: Span { start, end },
262 kind: SchemaErrorKind::UnexpectedToken {
263 expected: "type".to_string(),
264 found: format!("'{}'", b as char),
265 },
266 })
267 }
268 None => {
269 Err(SchemaParseError {
270 span: Span { start, end: start },
271 kind: SchemaErrorKind::UnexpectedToken {
272 expected: "type".to_string(),
273 found: "end of input".to_string(),
274 },
275 })
276 }
277 }
278 }
279
280 fn parse_field(&mut self) -> Result<FieldDef, SchemaParseError> {
281 self.skip_whitespace();
282 let name = self.parse_identifier()?;
283 self.skip_whitespace();
284 self.expect_char(b':')?;
285 self.skip_whitespace();
286 let type_ = self.parse_type()?;
287 Ok(FieldDef{
288 name,
289 type_
290 })
291 }
292
293 fn parse_struct(&mut self) -> Result<StructDef, SchemaParseError> {
294 self.skip_whitespace();
295 self.expect_char(b'(')?;
296 let mut fields: Vec<FieldDef> = Vec::new();
297 loop {
298 self.skip_whitespace();
299 if let Some(byte) = self.peek() {
300 if byte == b')' {
301 break ;
302 }
303 let field = self.parse_field()?;
304 fields.push(field);
305 self.skip_whitespace();
306 if self.peek() == Some(b',') {
307 self.advance();
308 }
309 } else {
310 return Err(SchemaParseError {
311 span: Span { start: self.position(), end: self.position() },
312 kind: SchemaErrorKind::UnexpectedToken { expected: ")".to_string(), found: "end of file".to_string() }
313 });
314 }
315 }
316 self.expect_char(b')')?;
317 Ok(StructDef { fields })
318 }
319
320 fn parse_tuple_type(&mut self) -> Result<Vec<SchemaType>, SchemaParseError> {
322 self.skip_whitespace();
323 self.expect_char(b'(')?;
324 let mut types = Vec::new();
325 loop {
326 self.skip_whitespace();
327 if self.peek() == Some(b')') {
328 break;
329 }
330 let t = self.parse_type()?;
331 types.push(t.value);
332 self.skip_whitespace();
333 if self.peek() == Some(b',') {
334 self.advance();
335 }
336 }
337 self.expect_char(b')')?;
338 Ok(types)
339 }
340
341 fn parse_enum_def(&mut self) -> Result<EnumDef, SchemaParseError> {
342 self.skip_whitespace();
343 let keyword = self.parse_identifier()?;
344 if keyword.value != "enum" {
345 return Err(SchemaParseError {
346 span: keyword.span,
347 kind: SchemaErrorKind::UnexpectedToken {
348 expected: "\"enum\"".to_string(),
349 found: keyword.value,
350 },
351 });
352 }
353 self.skip_whitespace();
354 let name = self.parse_identifier()?;
355 self.skip_whitespace();
356 self.expect_char(b'{')?;
357 let mut variants = HashSet::new();
358 loop {
359 self.skip_whitespace();
360 if let Some(byte) = self.peek() {
361 if byte == b'}' {
362 break ;
363 }
364 let variant = self.parse_identifier()?;
365 variants.insert(variant.value);
366 self.skip_whitespace();
367 if self.peek() == Some(b',') {
368 self.advance();
369 }
370 } else {
371 return Err(SchemaParseError {
372 span: Span { start: self.position(), end: self.position() },
373 kind: SchemaErrorKind::UnexpectedToken { expected: "}".to_string(), found: "end of file".to_string() }
374 });
375 }
376 }
377
378 self.expect_char(b'}')?;
379 Ok(EnumDef { name: name.value, variants })
380 }
381
382 fn parse_alias_def(&mut self) -> Result<(String, Spanned<SchemaType>), SchemaParseError> {
384 self.skip_whitespace();
385 self.parse_identifier()?; self.skip_whitespace();
387 let name = self.parse_identifier()?;
388 self.skip_whitespace();
389 self.expect_char(b'=')?;
390 self.skip_whitespace();
391 let type_ = self.parse_type()?;
392 Ok((name.value, type_))
393 }
394}
395
396pub fn parse_schema(source: &str) -> Result<Schema, SchemaParseError> {
403 let mut parser = Parser::new(source);
404 parser.skip_whitespace();
405
406 let mut root = if parser.peek() == Some(b'(') {
407 parser.parse_struct()?
408 } else {
409 StructDef { fields: Vec::new() }
410 };
411
412 let mut enums: HashMap<String, EnumDef> = HashMap::new();
413 let mut aliases: HashMap<String, Spanned<SchemaType>> = HashMap::new();
414
415 loop {
416 parser.skip_whitespace();
417 if parser.peek().is_none() {
418 break;
419 }
420
421 let start = parser.position();
423 let keyword = parser.parse_identifier()?;
424
425 match keyword.value.as_str() {
426 "enum" => {
427 parser.offset = start.offset;
429 parser.line = start.line;
430 parser.column = start.column;
431
432 let enum_def = parser.parse_enum_def()?;
433 if let Some(old) = enums.insert(enum_def.name.clone(), enum_def) {
434 return Err(SchemaParseError {
435 span: Span { start: parser.position(), end: parser.position() },
436 kind: SchemaErrorKind::DuplicateEnum { name: old.name },
437 });
438 }
439 }
440 "type" => {
441 parser.offset = start.offset;
443 parser.line = start.line;
444 parser.column = start.column;
445
446 let (name, type_) = parser.parse_alias_def()?;
447 if aliases.contains_key(&name) {
448 return Err(SchemaParseError {
449 span: type_.span,
450 kind: SchemaErrorKind::DuplicateAlias { name },
451 });
452 }
453 aliases.insert(name, type_);
454 }
455 other => {
456 return Err(SchemaParseError {
457 span: keyword.span,
458 kind: SchemaErrorKind::UnexpectedToken {
459 expected: "\"enum\" or \"type\"".to_string(),
460 found: other.to_string(),
461 },
462 });
463 }
464 }
465 }
466
467 let alias_names: HashSet<String> = aliases.keys().cloned().collect();
470 reclassify_refs_in_struct_by_name(&mut root, &alias_names);
471 for spanned_type in aliases.values_mut() {
472 reclassify_refs_in_type_by_name(&mut spanned_type.value, &alias_names);
473 }
474
475 verify_refs(&root, &enums, &aliases)?;
477
478 verify_no_recursive_aliases(&aliases)?;
480
481 Ok(Schema { root, enums, aliases })
482}
483
484fn reclassify_refs_in_struct_by_name(
487 struct_def: &mut StructDef,
488 alias_names: &HashSet<String>,
489) {
490 for field in &mut struct_def.fields {
491 reclassify_refs_in_type_by_name(&mut field.type_.value, alias_names);
492 }
493}
494
495fn reclassify_refs_in_type_by_name(
496 schema_type: &mut SchemaType,
497 alias_names: &HashSet<String>,
498) {
499 match schema_type {
500 SchemaType::EnumRef(name) if alias_names.contains(name.as_str()) => {
501 *schema_type = SchemaType::AliasRef(name.clone());
502 }
503 SchemaType::Option(inner) | SchemaType::List(inner) => {
504 reclassify_refs_in_type_by_name(inner, alias_names);
505 }
506 SchemaType::Map(key, value) => {
507 reclassify_refs_in_type_by_name(key, alias_names);
508 reclassify_refs_in_type_by_name(value, alias_names);
509 }
510 SchemaType::Tuple(types) => {
511 for t in types {
512 reclassify_refs_in_type_by_name(t, alias_names);
513 }
514 }
515 SchemaType::Struct(struct_def) => {
516 reclassify_refs_in_struct_by_name(struct_def, alias_names);
517 }
518 _ => {}
519 }
520}
521
522fn verify_refs(
525 struct_def: &StructDef,
526 enums: &HashMap<String, EnumDef>,
527 aliases: &HashMap<String, Spanned<SchemaType>>,
528) -> Result<(), SchemaParseError> {
529 for field in &struct_def.fields {
530 check_type_refs(&field.type_.value, field.type_.span, enums, aliases)?;
531 }
532 Ok(())
533}
534
535fn check_type_refs(
536 schema_type: &SchemaType,
537 span: Span,
538 enums: &HashMap<String, EnumDef>,
539 aliases: &HashMap<String, Spanned<SchemaType>>,
540) -> Result<(), SchemaParseError> {
541 match schema_type {
542 SchemaType::EnumRef(name) => {
543 if !enums.contains_key(name) {
544 return Err(SchemaParseError {
545 span,
546 kind: SchemaErrorKind::UnresolvedType { name: name.clone() },
547 });
548 }
549 }
550 SchemaType::AliasRef(name) => {
551 if !aliases.contains_key(name) {
552 return Err(SchemaParseError {
553 span,
554 kind: SchemaErrorKind::UnresolvedType { name: name.clone() },
555 });
556 }
557 }
558 SchemaType::Option(inner) | SchemaType::List(inner) => {
559 check_type_refs(inner, span, enums, aliases)?;
560 }
561 SchemaType::Map(key, value) => {
562 check_type_refs(key, span, enums, aliases)?;
563 check_type_refs(value, span, enums, aliases)?;
564 }
565 SchemaType::Tuple(types) => {
566 for t in types {
567 check_type_refs(t, span, enums, aliases)?;
568 }
569 }
570 SchemaType::Struct(struct_def) => {
571 verify_refs(struct_def, enums, aliases)?;
572 }
573 _ => {}
574 }
575 Ok(())
576}
577
578fn verify_no_recursive_aliases(
580 aliases: &HashMap<String, Spanned<SchemaType>>,
581) -> Result<(), SchemaParseError> {
582 for (name, spanned_type) in aliases {
583 let mut visited = HashSet::new();
584 visited.insert(name.as_str());
585 if let Some(cycle_name) = find_alias_cycle(&spanned_type.value, aliases, &mut visited) {
586 return Err(SchemaParseError {
587 span: spanned_type.span,
588 kind: SchemaErrorKind::RecursiveAlias { name: cycle_name },
589 });
590 }
591 }
592 Ok(())
593}
594
595fn find_alias_cycle<'a>(
596 schema_type: &'a SchemaType,
597 aliases: &'a HashMap<String, Spanned<SchemaType>>,
598 visited: &mut HashSet<&'a str>,
599) -> Option<String> {
600 match schema_type {
601 SchemaType::AliasRef(name) => {
602 if visited.contains(name.as_str()) {
603 return Some(name.clone());
604 }
605 visited.insert(name.as_str());
606 if let Some(target) = aliases.get(name) {
607 return find_alias_cycle(&target.value, aliases, visited);
608 }
609 None
610 }
611 SchemaType::Option(inner) | SchemaType::List(inner) => {
612 find_alias_cycle(inner, aliases, visited)
613 }
614 SchemaType::Map(key, value) => {
615 if let Some(cycle) = find_alias_cycle(key, aliases, visited) {
616 return Some(cycle);
617 }
618 find_alias_cycle(value, aliases, visited)
619 }
620 SchemaType::Tuple(types) => {
621 for t in types {
622 if let Some(cycle) = find_alias_cycle(t, aliases, visited) {
623 return Some(cycle);
624 }
625 }
626 None
627 }
628 SchemaType::Struct(struct_def) => {
629 for field in &struct_def.fields {
630 if let Some(cycle) = find_alias_cycle(&field.type_.value, aliases, visited) {
631 return Some(cycle);
632 }
633 }
634 None
635 }
636 _ => None,
637 }
638}
639
640#[cfg(test)]
641mod tests {
642 use super::*;
643
644 fn parser(source: &str) -> Parser<'_> {
649 Parser::new(source)
650 }
651
652 #[test]
658 fn peek_returns_current_byte() {
659 let p = parser("abc");
660 assert_eq!(p.peek(), Some(b'a'));
661 }
662
663 #[test]
665 fn peek_returns_none_at_end() {
666 let p = parser("");
667 assert_eq!(p.peek(), None);
668 }
669
670 #[test]
676 fn advance_increments_offset_and_column() {
677 let mut p = parser("ab");
678 p.advance();
679 assert_eq!(p.offset, 1);
680 assert_eq!(p.column, 2);
681 assert_eq!(p.peek(), Some(b'b'));
682 }
683
684 #[test]
686 fn advance_past_newline_increments_line() {
687 let mut p = parser("a\nb");
688 p.advance(); p.advance(); assert_eq!(p.line, 2);
691 assert_eq!(p.column, 1);
692 }
693
694 #[test]
696 fn advance_at_end_is_noop() {
697 let mut p = parser("");
698 p.advance();
699 assert_eq!(p.offset, 0);
700 }
701
702 #[test]
708 fn position_initial_state() {
709 let p = parser("abc");
710 let pos = p.position();
711 assert_eq!(pos.offset, 0);
712 assert_eq!(pos.line, 1);
713 assert_eq!(pos.column, 1);
714 }
715
716 #[test]
718 fn position_after_advance() {
719 let mut p = parser("ab\nc");
720 p.advance(); p.advance(); p.advance(); let pos = p.position();
724 assert_eq!(pos.offset, 3);
725 assert_eq!(pos.line, 2);
726 assert_eq!(pos.column, 1);
727 }
728
729 #[test]
735 fn skip_whitespace_skips_spaces_tabs_newlines() {
736 let mut p = parser(" \t\nabc");
737 p.skip_whitespace();
738 assert_eq!(p.peek(), Some(b'a'));
739 }
740
741 #[test]
743 fn skip_whitespace_skips_line_comment() {
744 let mut p = parser("// comment\nabc");
745 p.skip_whitespace();
746 assert_eq!(p.peek(), Some(b'a'));
747 }
748
749 #[test]
751 fn skip_whitespace_skips_comment_then_whitespace() {
752 let mut p = parser("// comment\n abc");
753 p.skip_whitespace();
754 assert_eq!(p.peek(), Some(b'a'));
755 }
756
757 #[test]
759 fn skip_whitespace_noop_on_nonwhitespace() {
760 let mut p = parser("abc");
761 p.skip_whitespace();
762 assert_eq!(p.offset, 0);
763 }
764
765 #[test]
771 fn expect_char_consumes_matching_byte() {
772 let mut p = parser("(abc");
773 assert!(p.expect_char(b'(').is_ok());
774 assert_eq!(p.peek(), Some(b'a'));
775 }
776
777 #[test]
779 fn expect_char_error_on_mismatch() {
780 let mut p = parser("abc");
781 let err = p.expect_char(b'(').unwrap_err();
782 assert!(matches!(err.kind, SchemaErrorKind::UnexpectedToken { .. }));
783 }
784
785 #[test]
787 fn expect_char_error_at_end_of_input() {
788 let mut p = parser("");
789 let err = p.expect_char(b'(').unwrap_err();
790 match err.kind {
791 SchemaErrorKind::UnexpectedToken { found, .. } => {
792 assert_eq!(found, "end of input");
793 }
794 other => panic!("expected UnexpectedToken, got {:?}", other),
795 }
796 }
797
798 #[test]
804 fn parse_identifier_reads_alpha() {
805 let mut p = parser("name:");
806 let id = p.parse_identifier().unwrap();
807 assert_eq!(id.value, "name");
808 }
809
810 #[test]
812 fn parse_identifier_reads_snake_case() {
813 let mut p = parser("field_name:");
814 let id = p.parse_identifier().unwrap();
815 assert_eq!(id.value, "field_name");
816 }
817
818 #[test]
820 fn parse_identifier_reads_alphanumeric() {
821 let mut p = parser("cost2:");
822 let id = p.parse_identifier().unwrap();
823 assert_eq!(id.value, "cost2");
824 }
825
826 #[test]
828 fn parse_identifier_reads_pascal_case() {
829 let mut p = parser("CardType ");
830 let id = p.parse_identifier().unwrap();
831 assert_eq!(id.value, "CardType");
832 }
833
834 #[test]
836 fn parse_identifier_stops_at_delimiter() {
837 let mut p = parser("name: String");
838 let id = p.parse_identifier().unwrap();
839 assert_eq!(id.value, "name");
840 assert_eq!(p.peek(), Some(b':'));
841 }
842
843 #[test]
845 fn parse_identifier_span_is_correct() {
846 let mut p = parser("name:");
847 let id = p.parse_identifier().unwrap();
848 assert_eq!(id.span.start.offset, 0);
849 assert_eq!(id.span.end.offset, 4);
850 }
851
852 #[test]
854 fn parse_identifier_error_on_digit_start() {
855 let mut p = parser("42abc");
856 assert!(p.parse_identifier().is_err());
857 }
858
859 #[test]
861 fn parse_identifier_error_at_end_of_input() {
862 let mut p = parser("");
863 assert!(p.parse_identifier().is_err());
864 }
865
866 #[test]
872 fn parse_type_string() {
873 let mut p = parser("String");
874 let t = p.parse_type().unwrap();
875 assert_eq!(t.value, SchemaType::String);
876 }
877
878 #[test]
880 fn parse_type_integer() {
881 let mut p = parser("Integer");
882 let t = p.parse_type().unwrap();
883 assert_eq!(t.value, SchemaType::Integer);
884 }
885
886 #[test]
888 fn parse_type_float() {
889 let mut p = parser("Float");
890 let t = p.parse_type().unwrap();
891 assert_eq!(t.value, SchemaType::Float);
892 }
893
894 #[test]
896 fn parse_type_bool() {
897 let mut p = parser("Bool");
898 let t = p.parse_type().unwrap();
899 assert_eq!(t.value, SchemaType::Bool);
900 }
901
902 #[test]
904 fn parse_type_list() {
905 let mut p = parser("[String]");
906 let t = p.parse_type().unwrap();
907 assert_eq!(t.value, SchemaType::List(Box::new(SchemaType::String)));
908 }
909
910 #[test]
912 fn parse_type_option() {
913 let mut p = parser("Option(Integer)");
914 let t = p.parse_type().unwrap();
915 assert_eq!(t.value, SchemaType::Option(Box::new(SchemaType::Integer)));
916 }
917
918 #[test]
920 fn parse_type_enum_ref() {
921 let mut p = parser("Faction");
922 let t = p.parse_type().unwrap();
923 assert_eq!(t.value, SchemaType::EnumRef("Faction".to_string()));
924 }
925
926 #[test]
928 fn parse_type_nested_list_of_option() {
929 let mut p = parser("[Option(String)]");
930 let t = p.parse_type().unwrap();
931 assert_eq!(
932 t.value,
933 SchemaType::List(Box::new(SchemaType::Option(Box::new(SchemaType::String))))
934 );
935 }
936
937 #[test]
939 fn parse_type_inline_struct() {
940 let mut p = parser("(\n x: Integer,\n)");
941 let t = p.parse_type().unwrap();
942 if let SchemaType::Struct(s) = &t.value {
943 assert_eq!(s.fields.len(), 1);
944 assert_eq!(s.fields[0].name.value, "x");
945 } else {
946 panic!("expected SchemaType::Struct");
947 }
948 }
949
950 #[test]
952 fn parse_type_error_on_unexpected_token() {
953 let mut p = parser("42");
954 let err = p.parse_type().unwrap_err();
955 match err.kind {
956 SchemaErrorKind::UnexpectedToken { expected, .. } => {
957 assert_eq!(expected, "type");
958 }
959 other => panic!("expected UnexpectedToken, got {:?}", other),
960 }
961 }
962
963 #[test]
969 fn parse_field_name_and_type() {
970 let mut p = parser("name: String,");
971 let f = p.parse_field().unwrap();
972 assert_eq!(f.name.value, "name");
973 assert_eq!(f.type_.value, SchemaType::String);
974 }
975
976 #[test]
978 fn parse_field_error_missing_colon() {
979 let mut p = parser("name String");
980 let err = p.parse_field().unwrap_err();
981 assert!(matches!(err.kind, SchemaErrorKind::UnexpectedToken { .. }));
982 }
983
984 #[test]
990 fn parse_struct_empty() {
991 let mut p = parser("()");
992 let s = p.parse_struct().unwrap();
993 assert!(s.fields.is_empty());
994 }
995
996 #[test]
998 fn parse_struct_single_field() {
999 let mut p = parser("(\n name: String,\n)");
1000 let s = p.parse_struct().unwrap();
1001 assert_eq!(s.fields.len(), 1);
1002 assert_eq!(s.fields[0].name.value, "name");
1003 }
1004
1005 #[test]
1007 fn parse_struct_multiple_fields() {
1008 let mut p = parser("(\n a: String,\n b: Integer,\n)");
1009 let s = p.parse_struct().unwrap();
1010 assert_eq!(s.fields.len(), 2);
1011 }
1012
1013 #[test]
1015 fn parse_struct_no_trailing_comma() {
1016 let mut p = parser("(\n name: String\n)");
1017 let s = p.parse_struct().unwrap();
1018 assert_eq!(s.fields.len(), 1);
1019 }
1020
1021 #[test]
1023 fn parse_struct_error_on_unclosed() {
1024 let mut p = parser("(\n name: String,\n");
1025 assert!(p.parse_struct().is_err());
1026 }
1027
1028 #[test]
1034 fn parse_enum_def_simple() {
1035 let mut p = parser("enum Dir { North, South }");
1036 let e = p.parse_enum_def().unwrap();
1037 assert_eq!(e.name, "Dir");
1038 assert_eq!(e.variants.len(), 2);
1039 assert!(e.variants.contains("North"));
1040 assert!(e.variants.contains("South"));
1041 }
1042
1043 #[test]
1045 fn parse_enum_def_trailing_comma() {
1046 let mut p = parser("enum Dir { North, South, }");
1047 let e = p.parse_enum_def().unwrap();
1048 assert_eq!(e.variants.len(), 2);
1049 }
1050
1051 #[test]
1053 fn parse_enum_def_single_variant() {
1054 let mut p = parser("enum Single { Only }");
1055 let e = p.parse_enum_def().unwrap();
1056 assert_eq!(e.variants.len(), 1);
1057 }
1058
1059 #[test]
1061 fn parse_enum_def_error_wrong_keyword() {
1062 let mut p = parser("struct Dir { North }");
1063 let err = p.parse_enum_def().unwrap_err();
1064 assert!(matches!(err.kind, SchemaErrorKind::UnexpectedToken { .. }));
1065 }
1066
1067 #[test]
1069 fn parse_enum_def_error_on_unclosed() {
1070 let mut p = parser("enum Dir { North, South");
1071 assert!(p.parse_enum_def().is_err());
1072 }
1073
1074 #[test]
1080 fn schema_empty_input() {
1081 let schema = parse_schema("").unwrap();
1082 assert!(schema.root.fields.is_empty());
1083 }
1084
1085 #[test]
1087 fn schema_empty_input_no_enums() {
1088 let schema = parse_schema("").unwrap();
1089 assert!(schema.enums.is_empty());
1090 }
1091
1092 #[test]
1094 fn schema_enum_ref_resolves() {
1095 let source = "(\n faction: Faction,\n)\nenum Faction { Sentinels, Reavers }";
1096 let schema = parse_schema(source).unwrap();
1097 assert_eq!(schema.root.fields[0].type_.value, SchemaType::EnumRef("Faction".to_string()));
1098 }
1099
1100 #[test]
1102 fn schema_multiple_enums_stored() {
1103 let source = "enum A { X }\nenum B { Y }";
1104 let schema = parse_schema(source).unwrap();
1105 assert_eq!(schema.enums.len(), 2);
1106 }
1107
1108 #[test]
1110 fn schema_comments_before_root() {
1111 let source = "// comment\n(\n name: String,\n)";
1112 let schema = parse_schema(source).unwrap();
1113 assert_eq!(schema.root.fields.len(), 1);
1114 }
1115
1116 #[test]
1118 fn schema_inline_comment_after_field() {
1119 let source = "(\n name: String, // a name\n)";
1120 let schema = parse_schema(source).unwrap();
1121 assert_eq!(schema.root.fields[0].name.value, "name");
1122 }
1123
1124 #[test]
1126 fn schema_unresolved_type_ref() {
1127 let err = parse_schema("(\n f: Faction,\n)").unwrap_err();
1128 assert_eq!(err.kind, SchemaErrorKind::UnresolvedType { name: "Faction".to_string() });
1129 }
1130
1131 #[test]
1133 fn schema_unresolved_type_ref_in_option() {
1134 let err = parse_schema("(\n t: Option(Timing),\n)").unwrap_err();
1135 assert_eq!(err.kind, SchemaErrorKind::UnresolvedType { name: "Timing".to_string() });
1136 }
1137
1138 #[test]
1140 fn schema_unresolved_type_ref_in_list() {
1141 let err = parse_schema("(\n t: [CardType],\n)").unwrap_err();
1142 assert_eq!(err.kind, SchemaErrorKind::UnresolvedType { name: "CardType".to_string() });
1143 }
1144
1145 #[test]
1147 fn schema_duplicate_enum_name() {
1148 let err = parse_schema("enum A { X }\nenum A { Y }").unwrap_err();
1149 assert_eq!(err.kind, SchemaErrorKind::DuplicateEnum { name: "A".to_string() });
1150 }
1151
1152 #[test]
1158 fn alias_stored_in_schema() {
1159 let source = "(\n cost: Cost,\n)\ntype Cost = (generic: Integer,)";
1160 let schema = parse_schema(source).unwrap();
1161 assert!(schema.aliases.contains_key("Cost"));
1162 }
1163
1164 #[test]
1166 fn alias_ref_reclassified() {
1167 let source = "(\n cost: Cost,\n)\ntype Cost = (generic: Integer,)";
1168 let schema = parse_schema(source).unwrap();
1169 assert_eq!(schema.root.fields[0].type_.value, SchemaType::AliasRef("Cost".to_string()));
1170 }
1171
1172 #[test]
1174 fn alias_to_primitive() {
1175 let source = "(\n name: Name,\n)\ntype Name = String";
1176 let schema = parse_schema(source).unwrap();
1177 assert_eq!(schema.aliases["Name"].value, SchemaType::String);
1178 }
1179
1180 #[test]
1182 fn alias_to_list() {
1183 let source = "(\n tags: Tags,\n)\ntype Tags = [String]";
1184 let schema = parse_schema(source).unwrap();
1185 assert_eq!(schema.aliases["Tags"].value, SchemaType::List(Box::new(SchemaType::String)));
1186 }
1187
1188 #[test]
1190 fn alias_to_option() {
1191 let source = "(\n power: Power,\n)\ntype Power = Option(Integer)";
1192 let schema = parse_schema(source).unwrap();
1193 assert_eq!(schema.aliases["Power"].value, SchemaType::Option(Box::new(SchemaType::Integer)));
1194 }
1195
1196 #[test]
1198 fn alias_ref_inside_list_reclassified() {
1199 let source = "(\n costs: [Cost],\n)\ntype Cost = (generic: Integer,)";
1200 let schema = parse_schema(source).unwrap();
1201 assert_eq!(
1202 schema.root.fields[0].type_.value,
1203 SchemaType::List(Box::new(SchemaType::AliasRef("Cost".to_string())))
1204 );
1205 }
1206
1207 #[test]
1209 fn alias_ref_inside_option_reclassified() {
1210 let source = "(\n cost: Option(Cost),\n)\ntype Cost = (generic: Integer,)";
1211 let schema = parse_schema(source).unwrap();
1212 assert_eq!(
1213 schema.root.fields[0].type_.value,
1214 SchemaType::Option(Box::new(SchemaType::AliasRef("Cost".to_string())))
1215 );
1216 }
1217
1218 #[test]
1220 fn alias_and_enum_coexist() {
1221 let source = "(\n cost: Cost,\n kind: Kind,\n)\ntype Cost = (generic: Integer,)\nenum Kind { A, B }";
1222 let schema = parse_schema(source).unwrap();
1223 assert!(schema.aliases.contains_key("Cost"));
1224 assert!(schema.enums.contains_key("Kind"));
1225 }
1226
1227 #[test]
1233 fn alias_duplicate_name() {
1234 let source = "type A = String\ntype A = Integer";
1235 let err = parse_schema(source).unwrap_err();
1236 assert_eq!(err.kind, SchemaErrorKind::DuplicateAlias { name: "A".to_string() });
1237 }
1238
1239 #[test]
1241 fn alias_recursive_direct() {
1242 let source = "(\n x: Foo,\n)\ntype Foo = Option(Foo)";
1243 let err = parse_schema(source).unwrap_err();
1244 assert_eq!(err.kind, SchemaErrorKind::RecursiveAlias { name: "Foo".to_string() });
1245 }
1246
1247 #[test]
1249 fn alias_recursive_indirect() {
1250 let source = "(\n x: Foo,\n)\ntype Foo = Option(Bar)\ntype Bar = [Foo]";
1251 let err = parse_schema(source).unwrap_err();
1252 assert!(matches!(err.kind, SchemaErrorKind::RecursiveAlias { .. }));
1253 }
1254
1255 #[test]
1261 fn parse_type_map_string_to_integer() {
1262 let mut p = parser("{String: Integer}");
1263 let t = p.parse_type().unwrap();
1264 assert_eq!(
1265 t.value,
1266 SchemaType::Map(Box::new(SchemaType::String), Box::new(SchemaType::Integer))
1267 );
1268 }
1269
1270 #[test]
1272 fn parse_type_map_integer_keys() {
1273 let mut p = parser("{Integer: String}");
1274 let t = p.parse_type().unwrap();
1275 assert_eq!(
1276 t.value,
1277 SchemaType::Map(Box::new(SchemaType::Integer), Box::new(SchemaType::String))
1278 );
1279 }
1280
1281 #[test]
1283 fn schema_map_field() {
1284 let source = "(\n attrs: {String: Integer},\n)";
1285 let schema = parse_schema(source).unwrap();
1286 assert_eq!(
1287 schema.root.fields[0].type_.value,
1288 SchemaType::Map(Box::new(SchemaType::String), Box::new(SchemaType::Integer))
1289 );
1290 }
1291
1292 #[test]
1294 fn schema_map_enum_key() {
1295 let source = "(\n scores: {Stat: Integer},\n)\nenum Stat { Str, Dex, Con }";
1296 let schema = parse_schema(source).unwrap();
1297 assert_eq!(
1298 schema.root.fields[0].type_.value,
1299 SchemaType::Map(Box::new(SchemaType::EnumRef("Stat".to_string())), Box::new(SchemaType::Integer))
1300 );
1301 }
1302
1303 #[test]
1305 fn schema_map_float_key_rejected() {
1306 let source = "(\n bad: {Float: String},\n)";
1307 let err = parse_schema(source).unwrap_err();
1308 assert!(matches!(err.kind, SchemaErrorKind::InvalidMapKeyType { .. }));
1309 }
1310
1311 #[test]
1313 fn schema_map_bool_key_rejected() {
1314 let source = "(\n bad: {Bool: String},\n)";
1315 let err = parse_schema(source).unwrap_err();
1316 assert!(matches!(err.kind, SchemaErrorKind::InvalidMapKeyType { .. }));
1317 }
1318
1319 #[test]
1325 fn parse_type_tuple() {
1326 let mut p = parser("(Float, Float)");
1327 let t = p.parse_type().unwrap();
1328 assert_eq!(t.value, SchemaType::Tuple(vec![SchemaType::Float, SchemaType::Float]));
1329 }
1330
1331 #[test]
1333 fn parse_type_tuple_mixed() {
1334 let mut p = parser("(String, Integer, Bool)");
1335 let t = p.parse_type().unwrap();
1336 assert_eq!(
1337 t.value,
1338 SchemaType::Tuple(vec![SchemaType::String, SchemaType::Integer, SchemaType::Bool])
1339 );
1340 }
1341
1342 #[test]
1344 fn schema_tuple_field() {
1345 let source = "(\n pos: (Float, Float),\n)";
1346 let schema = parse_schema(source).unwrap();
1347 assert_eq!(
1348 schema.root.fields[0].type_.value,
1349 SchemaType::Tuple(vec![SchemaType::Float, SchemaType::Float])
1350 );
1351 }
1352
1353 #[test]
1355 fn schema_struct_still_works() {
1356 let source = "(\n cost: (generic: Integer,),\n)";
1357 let schema = parse_schema(source).unwrap();
1358 if let SchemaType::Struct(s) = &schema.root.fields[0].type_.value {
1359 assert_eq!(s.fields[0].name.value, "generic");
1360 } else {
1361 panic!("expected Struct");
1362 }
1363 }
1364
1365 #[test]
1367 fn schema_empty_parens_is_struct() {
1368 let source = "(\n empty: (),\n)";
1369 let schema = parse_schema(source).unwrap();
1370 assert!(matches!(schema.root.fields[0].type_.value, SchemaType::Struct(_)));
1371 }
1372}