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 fn parse_type(&mut self) -> Result<Spanned<SchemaType>, SchemaParseError> {
135 self.skip_whitespace();
136 let start = self.position();
137
138 match self.peek() {
139 Some(b'[') => {
140 self.advance();
142 self.skip_whitespace();
143 let inner = self.parse_type()?;
144 self.skip_whitespace();
145 self.expect_char(b']')?;
146 let end = self.position();
147 Ok(Spanned {
148 value: SchemaType::List(Box::new(inner.value)),
149 span: Span { start, end },
150 })
151 }
152 Some(b'{') => {
153 self.advance();
155 self.skip_whitespace();
156 let key_type = self.parse_type()?;
157 match &key_type.value {
159 SchemaType::String | SchemaType::Integer | SchemaType::EnumRef(_) => {}
160 _ => {
161 return Err(SchemaParseError {
162 span: key_type.span,
163 kind: SchemaErrorKind::InvalidMapKeyType {
164 found: format!("{:?}", key_type.value),
165 },
166 });
167 }
168 }
169 self.skip_whitespace();
170 self.expect_char(b':')?;
171 self.skip_whitespace();
172 let value_type = self.parse_type()?;
173 self.skip_whitespace();
174 self.expect_char(b'}')?;
175 let end = self.position();
176 Ok(Spanned {
177 value: SchemaType::Map(Box::new(key_type.value), Box::new(value_type.value)),
178 span: Span { start, end },
179 })
180 }
181 Some(b'(') => {
182 let struct_def = self.parse_struct()?;
183 let end = self.position();
184 Ok(Spanned {
185 value: SchemaType::Struct(struct_def),
186 span: Span { start, end },
187 })
188 }
189 Some(b) if b.is_ascii_alphabetic() => {
190 let id = self.parse_identifier()?;
192 match id.value.as_str() {
193 "String" => Ok(Spanned { value: SchemaType::String, span: id.span }),
194 "Integer" => Ok(Spanned { value: SchemaType::Integer, span: id.span }),
195 "Float" => Ok(Spanned { value: SchemaType::Float, span: id.span }),
196 "Bool" => Ok(Spanned { value: SchemaType::Bool, span: id.span }),
197 "Option" => {
198 self.skip_whitespace();
200 self.expect_char(b'(')?;
201 self.skip_whitespace();
202 let inner = self.parse_type()?;
203 self.skip_whitespace();
204 self.expect_char(b')')?;
205 let end = self.position();
206 Ok(Spanned {
207 value: SchemaType::Option(Box::new(inner.value)),
208 span: Span { start, end },
209 })
210 }
211 _ => Ok(Spanned { value: SchemaType::EnumRef(id.value), span: id.span }),
212 }
213 }
214 Some(b) => {
215 self.advance();
217 let end = self.position();
218 Err(SchemaParseError {
219 span: Span { start, end },
220 kind: SchemaErrorKind::UnexpectedToken {
221 expected: "type".to_string(),
222 found: format!("'{}'", b as char),
223 },
224 })
225 }
226 None => {
227 Err(SchemaParseError {
228 span: Span { start, end: start },
229 kind: SchemaErrorKind::UnexpectedToken {
230 expected: "type".to_string(),
231 found: "end of input".to_string(),
232 },
233 })
234 }
235 }
236 }
237
238 fn parse_field(&mut self) -> Result<FieldDef, SchemaParseError> {
239 self.skip_whitespace();
240 let name = self.parse_identifier()?;
241 self.skip_whitespace();
242 self.expect_char(b':')?;
243 self.skip_whitespace();
244 let type_ = self.parse_type()?;
245 Ok(FieldDef{
246 name,
247 type_
248 })
249 }
250
251 fn parse_struct(&mut self) -> Result<StructDef, SchemaParseError> {
252 self.skip_whitespace();
253 self.expect_char(b'(')?;
254 let mut fields: Vec<FieldDef> = Vec::new();
255 loop {
256 self.skip_whitespace();
257 if let Some(byte) = self.peek() {
258 if byte == b')' {
259 break ;
260 }
261 let field = self.parse_field()?;
262 fields.push(field);
263 self.skip_whitespace();
264 if self.peek() == Some(b',') {
265 self.advance();
266 }
267 } else {
268 return Err(SchemaParseError {
269 span: Span { start: self.position(), end: self.position() },
270 kind: SchemaErrorKind::UnexpectedToken { expected: ")".to_string(), found: "end of file".to_string() }
271 });
272 }
273 }
274 self.expect_char(b')')?;
275 Ok(StructDef { fields })
276 }
277
278 fn parse_enum_def(&mut self) -> Result<EnumDef, SchemaParseError> {
279 self.skip_whitespace();
280 let keyword = self.parse_identifier()?;
281 if keyword.value != "enum" {
282 return Err(SchemaParseError {
283 span: keyword.span,
284 kind: SchemaErrorKind::UnexpectedToken {
285 expected: "\"enum\"".to_string(),
286 found: keyword.value,
287 },
288 });
289 }
290 self.skip_whitespace();
291 let name = self.parse_identifier()?;
292 self.skip_whitespace();
293 self.expect_char(b'{')?;
294 let mut variants = HashSet::new();
295 loop {
296 self.skip_whitespace();
297 if let Some(byte) = self.peek() {
298 if byte == b'}' {
299 break ;
300 }
301 let variant = self.parse_identifier()?;
302 variants.insert(variant.value);
303 self.skip_whitespace();
304 if self.peek() == Some(b',') {
305 self.advance();
306 }
307 } else {
308 return Err(SchemaParseError {
309 span: Span { start: self.position(), end: self.position() },
310 kind: SchemaErrorKind::UnexpectedToken { expected: "}".to_string(), found: "end of file".to_string() }
311 });
312 }
313 }
314
315 self.expect_char(b'}')?;
316 Ok(EnumDef { name: name.value, variants })
317 }
318
319 fn parse_alias_def(&mut self) -> Result<(String, Spanned<SchemaType>), SchemaParseError> {
321 self.skip_whitespace();
322 self.parse_identifier()?; self.skip_whitespace();
324 let name = self.parse_identifier()?;
325 self.skip_whitespace();
326 self.expect_char(b'=')?;
327 self.skip_whitespace();
328 let type_ = self.parse_type()?;
329 Ok((name.value, type_))
330 }
331}
332
333pub fn parse_schema(source: &str) -> Result<Schema, SchemaParseError> {
340 let mut parser = Parser::new(source);
341 parser.skip_whitespace();
342
343 let mut root = if parser.peek() == Some(b'(') {
344 parser.parse_struct()?
345 } else {
346 StructDef { fields: Vec::new() }
347 };
348
349 let mut enums: HashMap<String, EnumDef> = HashMap::new();
350 let mut aliases: HashMap<String, Spanned<SchemaType>> = HashMap::new();
351
352 loop {
353 parser.skip_whitespace();
354 if parser.peek().is_none() {
355 break;
356 }
357
358 let start = parser.position();
360 let keyword = parser.parse_identifier()?;
361
362 match keyword.value.as_str() {
363 "enum" => {
364 parser.offset = start.offset;
366 parser.line = start.line;
367 parser.column = start.column;
368
369 let enum_def = parser.parse_enum_def()?;
370 if let Some(old) = enums.insert(enum_def.name.clone(), enum_def) {
371 return Err(SchemaParseError {
372 span: Span { start: parser.position(), end: parser.position() },
373 kind: SchemaErrorKind::DuplicateEnum { name: old.name },
374 });
375 }
376 }
377 "type" => {
378 parser.offset = start.offset;
380 parser.line = start.line;
381 parser.column = start.column;
382
383 let (name, type_) = parser.parse_alias_def()?;
384 if aliases.contains_key(&name) {
385 return Err(SchemaParseError {
386 span: type_.span,
387 kind: SchemaErrorKind::DuplicateAlias { name },
388 });
389 }
390 aliases.insert(name, type_);
391 }
392 other => {
393 return Err(SchemaParseError {
394 span: keyword.span,
395 kind: SchemaErrorKind::UnexpectedToken {
396 expected: "\"enum\" or \"type\"".to_string(),
397 found: other.to_string(),
398 },
399 });
400 }
401 }
402 }
403
404 let alias_names: HashSet<String> = aliases.keys().cloned().collect();
407 reclassify_refs_in_struct_by_name(&mut root, &alias_names);
408 for spanned_type in aliases.values_mut() {
409 reclassify_refs_in_type_by_name(&mut spanned_type.value, &alias_names);
410 }
411
412 verify_refs(&root, &enums, &aliases)?;
414
415 verify_no_recursive_aliases(&aliases)?;
417
418 Ok(Schema { root, enums, aliases })
419}
420
421fn reclassify_refs_in_struct_by_name(
424 struct_def: &mut StructDef,
425 alias_names: &HashSet<String>,
426) {
427 for field in &mut struct_def.fields {
428 reclassify_refs_in_type_by_name(&mut field.type_.value, alias_names);
429 }
430}
431
432fn reclassify_refs_in_type_by_name(
433 schema_type: &mut SchemaType,
434 alias_names: &HashSet<String>,
435) {
436 match schema_type {
437 SchemaType::EnumRef(name) if alias_names.contains(name.as_str()) => {
438 *schema_type = SchemaType::AliasRef(name.clone());
439 }
440 SchemaType::Option(inner) | SchemaType::List(inner) => {
441 reclassify_refs_in_type_by_name(inner, alias_names);
442 }
443 SchemaType::Map(key, value) => {
444 reclassify_refs_in_type_by_name(key, alias_names);
445 reclassify_refs_in_type_by_name(value, alias_names);
446 }
447 SchemaType::Struct(struct_def) => {
448 reclassify_refs_in_struct_by_name(struct_def, alias_names);
449 }
450 _ => {}
451 }
452}
453
454fn verify_refs(
457 struct_def: &StructDef,
458 enums: &HashMap<String, EnumDef>,
459 aliases: &HashMap<String, Spanned<SchemaType>>,
460) -> Result<(), SchemaParseError> {
461 for field in &struct_def.fields {
462 check_type_refs(&field.type_.value, field.type_.span, enums, aliases)?;
463 }
464 Ok(())
465}
466
467fn check_type_refs(
468 schema_type: &SchemaType,
469 span: Span,
470 enums: &HashMap<String, EnumDef>,
471 aliases: &HashMap<String, Spanned<SchemaType>>,
472) -> Result<(), SchemaParseError> {
473 match schema_type {
474 SchemaType::EnumRef(name) => {
475 if !enums.contains_key(name) {
476 return Err(SchemaParseError {
477 span,
478 kind: SchemaErrorKind::UnresolvedType { name: name.clone() },
479 });
480 }
481 }
482 SchemaType::AliasRef(name) => {
483 if !aliases.contains_key(name) {
484 return Err(SchemaParseError {
485 span,
486 kind: SchemaErrorKind::UnresolvedType { name: name.clone() },
487 });
488 }
489 }
490 SchemaType::Option(inner) | SchemaType::List(inner) => {
491 check_type_refs(inner, span, enums, aliases)?;
492 }
493 SchemaType::Map(key, value) => {
494 check_type_refs(key, span, enums, aliases)?;
495 check_type_refs(value, span, enums, aliases)?;
496 }
497 SchemaType::Struct(struct_def) => {
498 verify_refs(struct_def, enums, aliases)?;
499 }
500 _ => {}
501 }
502 Ok(())
503}
504
505fn verify_no_recursive_aliases(
507 aliases: &HashMap<String, Spanned<SchemaType>>,
508) -> Result<(), SchemaParseError> {
509 for (name, spanned_type) in aliases {
510 let mut visited = HashSet::new();
511 visited.insert(name.as_str());
512 if let Some(cycle_name) = find_alias_cycle(&spanned_type.value, aliases, &mut visited) {
513 return Err(SchemaParseError {
514 span: spanned_type.span,
515 kind: SchemaErrorKind::RecursiveAlias { name: cycle_name },
516 });
517 }
518 }
519 Ok(())
520}
521
522fn find_alias_cycle<'a>(
523 schema_type: &'a SchemaType,
524 aliases: &'a HashMap<String, Spanned<SchemaType>>,
525 visited: &mut HashSet<&'a str>,
526) -> Option<String> {
527 match schema_type {
528 SchemaType::AliasRef(name) => {
529 if visited.contains(name.as_str()) {
530 return Some(name.clone());
531 }
532 visited.insert(name.as_str());
533 if let Some(target) = aliases.get(name) {
534 return find_alias_cycle(&target.value, aliases, visited);
535 }
536 None
537 }
538 SchemaType::Option(inner) | SchemaType::List(inner) => {
539 find_alias_cycle(inner, aliases, visited)
540 }
541 SchemaType::Map(key, value) => {
542 if let Some(cycle) = find_alias_cycle(key, aliases, visited) {
543 return Some(cycle);
544 }
545 find_alias_cycle(value, aliases, visited)
546 }
547 SchemaType::Struct(struct_def) => {
548 for field in &struct_def.fields {
549 if let Some(cycle) = find_alias_cycle(&field.type_.value, aliases, visited) {
550 return Some(cycle);
551 }
552 }
553 None
554 }
555 _ => None,
556 }
557}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562
563 fn parser(source: &str) -> Parser<'_> {
568 Parser::new(source)
569 }
570
571 #[test]
577 fn peek_returns_current_byte() {
578 let p = parser("abc");
579 assert_eq!(p.peek(), Some(b'a'));
580 }
581
582 #[test]
584 fn peek_returns_none_at_end() {
585 let p = parser("");
586 assert_eq!(p.peek(), None);
587 }
588
589 #[test]
595 fn advance_increments_offset_and_column() {
596 let mut p = parser("ab");
597 p.advance();
598 assert_eq!(p.offset, 1);
599 assert_eq!(p.column, 2);
600 assert_eq!(p.peek(), Some(b'b'));
601 }
602
603 #[test]
605 fn advance_past_newline_increments_line() {
606 let mut p = parser("a\nb");
607 p.advance(); p.advance(); assert_eq!(p.line, 2);
610 assert_eq!(p.column, 1);
611 }
612
613 #[test]
615 fn advance_at_end_is_noop() {
616 let mut p = parser("");
617 p.advance();
618 assert_eq!(p.offset, 0);
619 }
620
621 #[test]
627 fn position_initial_state() {
628 let p = parser("abc");
629 let pos = p.position();
630 assert_eq!(pos.offset, 0);
631 assert_eq!(pos.line, 1);
632 assert_eq!(pos.column, 1);
633 }
634
635 #[test]
637 fn position_after_advance() {
638 let mut p = parser("ab\nc");
639 p.advance(); p.advance(); p.advance(); let pos = p.position();
643 assert_eq!(pos.offset, 3);
644 assert_eq!(pos.line, 2);
645 assert_eq!(pos.column, 1);
646 }
647
648 #[test]
654 fn skip_whitespace_skips_spaces_tabs_newlines() {
655 let mut p = parser(" \t\nabc");
656 p.skip_whitespace();
657 assert_eq!(p.peek(), Some(b'a'));
658 }
659
660 #[test]
662 fn skip_whitespace_skips_line_comment() {
663 let mut p = parser("// comment\nabc");
664 p.skip_whitespace();
665 assert_eq!(p.peek(), Some(b'a'));
666 }
667
668 #[test]
670 fn skip_whitespace_skips_comment_then_whitespace() {
671 let mut p = parser("// comment\n abc");
672 p.skip_whitespace();
673 assert_eq!(p.peek(), Some(b'a'));
674 }
675
676 #[test]
678 fn skip_whitespace_noop_on_nonwhitespace() {
679 let mut p = parser("abc");
680 p.skip_whitespace();
681 assert_eq!(p.offset, 0);
682 }
683
684 #[test]
690 fn expect_char_consumes_matching_byte() {
691 let mut p = parser("(abc");
692 assert!(p.expect_char(b'(').is_ok());
693 assert_eq!(p.peek(), Some(b'a'));
694 }
695
696 #[test]
698 fn expect_char_error_on_mismatch() {
699 let mut p = parser("abc");
700 let err = p.expect_char(b'(').unwrap_err();
701 assert!(matches!(err.kind, SchemaErrorKind::UnexpectedToken { .. }));
702 }
703
704 #[test]
706 fn expect_char_error_at_end_of_input() {
707 let mut p = parser("");
708 let err = p.expect_char(b'(').unwrap_err();
709 match err.kind {
710 SchemaErrorKind::UnexpectedToken { found, .. } => {
711 assert_eq!(found, "end of input");
712 }
713 other => panic!("expected UnexpectedToken, got {:?}", other),
714 }
715 }
716
717 #[test]
723 fn parse_identifier_reads_alpha() {
724 let mut p = parser("name:");
725 let id = p.parse_identifier().unwrap();
726 assert_eq!(id.value, "name");
727 }
728
729 #[test]
731 fn parse_identifier_reads_snake_case() {
732 let mut p = parser("field_name:");
733 let id = p.parse_identifier().unwrap();
734 assert_eq!(id.value, "field_name");
735 }
736
737 #[test]
739 fn parse_identifier_reads_alphanumeric() {
740 let mut p = parser("cost2:");
741 let id = p.parse_identifier().unwrap();
742 assert_eq!(id.value, "cost2");
743 }
744
745 #[test]
747 fn parse_identifier_reads_pascal_case() {
748 let mut p = parser("CardType ");
749 let id = p.parse_identifier().unwrap();
750 assert_eq!(id.value, "CardType");
751 }
752
753 #[test]
755 fn parse_identifier_stops_at_delimiter() {
756 let mut p = parser("name: String");
757 let id = p.parse_identifier().unwrap();
758 assert_eq!(id.value, "name");
759 assert_eq!(p.peek(), Some(b':'));
760 }
761
762 #[test]
764 fn parse_identifier_span_is_correct() {
765 let mut p = parser("name:");
766 let id = p.parse_identifier().unwrap();
767 assert_eq!(id.span.start.offset, 0);
768 assert_eq!(id.span.end.offset, 4);
769 }
770
771 #[test]
773 fn parse_identifier_error_on_digit_start() {
774 let mut p = parser("42abc");
775 assert!(p.parse_identifier().is_err());
776 }
777
778 #[test]
780 fn parse_identifier_error_at_end_of_input() {
781 let mut p = parser("");
782 assert!(p.parse_identifier().is_err());
783 }
784
785 #[test]
791 fn parse_type_string() {
792 let mut p = parser("String");
793 let t = p.parse_type().unwrap();
794 assert_eq!(t.value, SchemaType::String);
795 }
796
797 #[test]
799 fn parse_type_integer() {
800 let mut p = parser("Integer");
801 let t = p.parse_type().unwrap();
802 assert_eq!(t.value, SchemaType::Integer);
803 }
804
805 #[test]
807 fn parse_type_float() {
808 let mut p = parser("Float");
809 let t = p.parse_type().unwrap();
810 assert_eq!(t.value, SchemaType::Float);
811 }
812
813 #[test]
815 fn parse_type_bool() {
816 let mut p = parser("Bool");
817 let t = p.parse_type().unwrap();
818 assert_eq!(t.value, SchemaType::Bool);
819 }
820
821 #[test]
823 fn parse_type_list() {
824 let mut p = parser("[String]");
825 let t = p.parse_type().unwrap();
826 assert_eq!(t.value, SchemaType::List(Box::new(SchemaType::String)));
827 }
828
829 #[test]
831 fn parse_type_option() {
832 let mut p = parser("Option(Integer)");
833 let t = p.parse_type().unwrap();
834 assert_eq!(t.value, SchemaType::Option(Box::new(SchemaType::Integer)));
835 }
836
837 #[test]
839 fn parse_type_enum_ref() {
840 let mut p = parser("Faction");
841 let t = p.parse_type().unwrap();
842 assert_eq!(t.value, SchemaType::EnumRef("Faction".to_string()));
843 }
844
845 #[test]
847 fn parse_type_nested_list_of_option() {
848 let mut p = parser("[Option(String)]");
849 let t = p.parse_type().unwrap();
850 assert_eq!(
851 t.value,
852 SchemaType::List(Box::new(SchemaType::Option(Box::new(SchemaType::String))))
853 );
854 }
855
856 #[test]
858 fn parse_type_inline_struct() {
859 let mut p = parser("(\n x: Integer,\n)");
860 let t = p.parse_type().unwrap();
861 if let SchemaType::Struct(s) = &t.value {
862 assert_eq!(s.fields.len(), 1);
863 assert_eq!(s.fields[0].name.value, "x");
864 } else {
865 panic!("expected SchemaType::Struct");
866 }
867 }
868
869 #[test]
871 fn parse_type_error_on_unexpected_token() {
872 let mut p = parser("42");
873 let err = p.parse_type().unwrap_err();
874 match err.kind {
875 SchemaErrorKind::UnexpectedToken { expected, .. } => {
876 assert_eq!(expected, "type");
877 }
878 other => panic!("expected UnexpectedToken, got {:?}", other),
879 }
880 }
881
882 #[test]
888 fn parse_field_name_and_type() {
889 let mut p = parser("name: String,");
890 let f = p.parse_field().unwrap();
891 assert_eq!(f.name.value, "name");
892 assert_eq!(f.type_.value, SchemaType::String);
893 }
894
895 #[test]
897 fn parse_field_error_missing_colon() {
898 let mut p = parser("name String");
899 let err = p.parse_field().unwrap_err();
900 assert!(matches!(err.kind, SchemaErrorKind::UnexpectedToken { .. }));
901 }
902
903 #[test]
909 fn parse_struct_empty() {
910 let mut p = parser("()");
911 let s = p.parse_struct().unwrap();
912 assert!(s.fields.is_empty());
913 }
914
915 #[test]
917 fn parse_struct_single_field() {
918 let mut p = parser("(\n name: String,\n)");
919 let s = p.parse_struct().unwrap();
920 assert_eq!(s.fields.len(), 1);
921 assert_eq!(s.fields[0].name.value, "name");
922 }
923
924 #[test]
926 fn parse_struct_multiple_fields() {
927 let mut p = parser("(\n a: String,\n b: Integer,\n)");
928 let s = p.parse_struct().unwrap();
929 assert_eq!(s.fields.len(), 2);
930 }
931
932 #[test]
934 fn parse_struct_no_trailing_comma() {
935 let mut p = parser("(\n name: String\n)");
936 let s = p.parse_struct().unwrap();
937 assert_eq!(s.fields.len(), 1);
938 }
939
940 #[test]
942 fn parse_struct_error_on_unclosed() {
943 let mut p = parser("(\n name: String,\n");
944 assert!(p.parse_struct().is_err());
945 }
946
947 #[test]
953 fn parse_enum_def_simple() {
954 let mut p = parser("enum Dir { North, South }");
955 let e = p.parse_enum_def().unwrap();
956 assert_eq!(e.name, "Dir");
957 assert_eq!(e.variants.len(), 2);
958 assert!(e.variants.contains("North"));
959 assert!(e.variants.contains("South"));
960 }
961
962 #[test]
964 fn parse_enum_def_trailing_comma() {
965 let mut p = parser("enum Dir { North, South, }");
966 let e = p.parse_enum_def().unwrap();
967 assert_eq!(e.variants.len(), 2);
968 }
969
970 #[test]
972 fn parse_enum_def_single_variant() {
973 let mut p = parser("enum Single { Only }");
974 let e = p.parse_enum_def().unwrap();
975 assert_eq!(e.variants.len(), 1);
976 }
977
978 #[test]
980 fn parse_enum_def_error_wrong_keyword() {
981 let mut p = parser("struct Dir { North }");
982 let err = p.parse_enum_def().unwrap_err();
983 assert!(matches!(err.kind, SchemaErrorKind::UnexpectedToken { .. }));
984 }
985
986 #[test]
988 fn parse_enum_def_error_on_unclosed() {
989 let mut p = parser("enum Dir { North, South");
990 assert!(p.parse_enum_def().is_err());
991 }
992
993 #[test]
999 fn schema_empty_input() {
1000 let schema = parse_schema("").unwrap();
1001 assert!(schema.root.fields.is_empty());
1002 }
1003
1004 #[test]
1006 fn schema_empty_input_no_enums() {
1007 let schema = parse_schema("").unwrap();
1008 assert!(schema.enums.is_empty());
1009 }
1010
1011 #[test]
1013 fn schema_enum_ref_resolves() {
1014 let source = "(\n faction: Faction,\n)\nenum Faction { Sentinels, Reavers }";
1015 let schema = parse_schema(source).unwrap();
1016 assert_eq!(schema.root.fields[0].type_.value, SchemaType::EnumRef("Faction".to_string()));
1017 }
1018
1019 #[test]
1021 fn schema_multiple_enums_stored() {
1022 let source = "enum A { X }\nenum B { Y }";
1023 let schema = parse_schema(source).unwrap();
1024 assert_eq!(schema.enums.len(), 2);
1025 }
1026
1027 #[test]
1029 fn schema_comments_before_root() {
1030 let source = "// comment\n(\n name: String,\n)";
1031 let schema = parse_schema(source).unwrap();
1032 assert_eq!(schema.root.fields.len(), 1);
1033 }
1034
1035 #[test]
1037 fn schema_inline_comment_after_field() {
1038 let source = "(\n name: String, // a name\n)";
1039 let schema = parse_schema(source).unwrap();
1040 assert_eq!(schema.root.fields[0].name.value, "name");
1041 }
1042
1043 #[test]
1045 fn schema_unresolved_type_ref() {
1046 let err = parse_schema("(\n f: Faction,\n)").unwrap_err();
1047 assert_eq!(err.kind, SchemaErrorKind::UnresolvedType { name: "Faction".to_string() });
1048 }
1049
1050 #[test]
1052 fn schema_unresolved_type_ref_in_option() {
1053 let err = parse_schema("(\n t: Option(Timing),\n)").unwrap_err();
1054 assert_eq!(err.kind, SchemaErrorKind::UnresolvedType { name: "Timing".to_string() });
1055 }
1056
1057 #[test]
1059 fn schema_unresolved_type_ref_in_list() {
1060 let err = parse_schema("(\n t: [CardType],\n)").unwrap_err();
1061 assert_eq!(err.kind, SchemaErrorKind::UnresolvedType { name: "CardType".to_string() });
1062 }
1063
1064 #[test]
1066 fn schema_duplicate_enum_name() {
1067 let err = parse_schema("enum A { X }\nenum A { Y }").unwrap_err();
1068 assert_eq!(err.kind, SchemaErrorKind::DuplicateEnum { name: "A".to_string() });
1069 }
1070
1071 #[test]
1077 fn alias_stored_in_schema() {
1078 let source = "(\n cost: Cost,\n)\ntype Cost = (generic: Integer,)";
1079 let schema = parse_schema(source).unwrap();
1080 assert!(schema.aliases.contains_key("Cost"));
1081 }
1082
1083 #[test]
1085 fn alias_ref_reclassified() {
1086 let source = "(\n cost: Cost,\n)\ntype Cost = (generic: Integer,)";
1087 let schema = parse_schema(source).unwrap();
1088 assert_eq!(schema.root.fields[0].type_.value, SchemaType::AliasRef("Cost".to_string()));
1089 }
1090
1091 #[test]
1093 fn alias_to_primitive() {
1094 let source = "(\n name: Name,\n)\ntype Name = String";
1095 let schema = parse_schema(source).unwrap();
1096 assert_eq!(schema.aliases["Name"].value, SchemaType::String);
1097 }
1098
1099 #[test]
1101 fn alias_to_list() {
1102 let source = "(\n tags: Tags,\n)\ntype Tags = [String]";
1103 let schema = parse_schema(source).unwrap();
1104 assert_eq!(schema.aliases["Tags"].value, SchemaType::List(Box::new(SchemaType::String)));
1105 }
1106
1107 #[test]
1109 fn alias_to_option() {
1110 let source = "(\n power: Power,\n)\ntype Power = Option(Integer)";
1111 let schema = parse_schema(source).unwrap();
1112 assert_eq!(schema.aliases["Power"].value, SchemaType::Option(Box::new(SchemaType::Integer)));
1113 }
1114
1115 #[test]
1117 fn alias_ref_inside_list_reclassified() {
1118 let source = "(\n costs: [Cost],\n)\ntype Cost = (generic: Integer,)";
1119 let schema = parse_schema(source).unwrap();
1120 assert_eq!(
1121 schema.root.fields[0].type_.value,
1122 SchemaType::List(Box::new(SchemaType::AliasRef("Cost".to_string())))
1123 );
1124 }
1125
1126 #[test]
1128 fn alias_ref_inside_option_reclassified() {
1129 let source = "(\n cost: Option(Cost),\n)\ntype Cost = (generic: Integer,)";
1130 let schema = parse_schema(source).unwrap();
1131 assert_eq!(
1132 schema.root.fields[0].type_.value,
1133 SchemaType::Option(Box::new(SchemaType::AliasRef("Cost".to_string())))
1134 );
1135 }
1136
1137 #[test]
1139 fn alias_and_enum_coexist() {
1140 let source = "(\n cost: Cost,\n kind: Kind,\n)\ntype Cost = (generic: Integer,)\nenum Kind { A, B }";
1141 let schema = parse_schema(source).unwrap();
1142 assert!(schema.aliases.contains_key("Cost"));
1143 assert!(schema.enums.contains_key("Kind"));
1144 }
1145
1146 #[test]
1152 fn alias_duplicate_name() {
1153 let source = "type A = String\ntype A = Integer";
1154 let err = parse_schema(source).unwrap_err();
1155 assert_eq!(err.kind, SchemaErrorKind::DuplicateAlias { name: "A".to_string() });
1156 }
1157
1158 #[test]
1160 fn alias_recursive_direct() {
1161 let source = "(\n x: Foo,\n)\ntype Foo = Option(Foo)";
1162 let err = parse_schema(source).unwrap_err();
1163 assert_eq!(err.kind, SchemaErrorKind::RecursiveAlias { name: "Foo".to_string() });
1164 }
1165
1166 #[test]
1168 fn alias_recursive_indirect() {
1169 let source = "(\n x: Foo,\n)\ntype Foo = Option(Bar)\ntype Bar = [Foo]";
1170 let err = parse_schema(source).unwrap_err();
1171 assert!(matches!(err.kind, SchemaErrorKind::RecursiveAlias { .. }));
1172 }
1173
1174 #[test]
1180 fn parse_type_map_string_to_integer() {
1181 let mut p = parser("{String: Integer}");
1182 let t = p.parse_type().unwrap();
1183 assert_eq!(
1184 t.value,
1185 SchemaType::Map(Box::new(SchemaType::String), Box::new(SchemaType::Integer))
1186 );
1187 }
1188
1189 #[test]
1191 fn parse_type_map_integer_keys() {
1192 let mut p = parser("{Integer: String}");
1193 let t = p.parse_type().unwrap();
1194 assert_eq!(
1195 t.value,
1196 SchemaType::Map(Box::new(SchemaType::Integer), Box::new(SchemaType::String))
1197 );
1198 }
1199
1200 #[test]
1202 fn schema_map_field() {
1203 let source = "(\n attrs: {String: Integer},\n)";
1204 let schema = parse_schema(source).unwrap();
1205 assert_eq!(
1206 schema.root.fields[0].type_.value,
1207 SchemaType::Map(Box::new(SchemaType::String), Box::new(SchemaType::Integer))
1208 );
1209 }
1210
1211 #[test]
1213 fn schema_map_enum_key() {
1214 let source = "(\n scores: {Stat: Integer},\n)\nenum Stat { Str, Dex, Con }";
1215 let schema = parse_schema(source).unwrap();
1216 assert_eq!(
1217 schema.root.fields[0].type_.value,
1218 SchemaType::Map(Box::new(SchemaType::EnumRef("Stat".to_string())), Box::new(SchemaType::Integer))
1219 );
1220 }
1221
1222 #[test]
1224 fn schema_map_float_key_rejected() {
1225 let source = "(\n bad: {Float: String},\n)";
1226 let err = parse_schema(source).unwrap_err();
1227 assert!(matches!(err.kind, SchemaErrorKind::InvalidMapKeyType { .. }));
1228 }
1229
1230 #[test]
1232 fn schema_map_bool_key_rejected() {
1233 let source = "(\n bad: {Bool: String},\n)";
1234 let err = parse_schema(source).unwrap_err();
1235 assert!(matches!(err.kind, SchemaErrorKind::InvalidMapKeyType { .. }));
1236 }
1237}