1use std::collections::hash_map::DefaultHasher;
47use std::hash::{Hash, Hasher};
48use std::path::Path;
49
50use crate::{Error, Result};
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum SchemaFormat {
55 Zap,
57 Capnp,
59}
60
61pub struct ZapSchema {
67 source: String,
69 path: String,
71 format: SchemaFormat,
73}
74
75#[derive(Debug, Clone)]
76enum Item {
77 Comment(String),
78 Using(String),
79 Const {
80 name: String,
81 typ: String,
82 value: String,
83 },
84 Struct {
85 name: String,
86 fields: Vec<Field>,
87 unions: Vec<Union>,
88 nested: Vec<Item>,
89 },
90 Enum {
91 name: String,
92 variants: Vec<String>,
93 },
94 Interface {
95 name: String,
96 extends: Option<String>,
97 methods: Vec<Method>,
98 nested: Vec<Item>,
99 },
100}
101
102#[derive(Debug, Clone)]
103struct Field {
104 name: String,
105 typ: String,
106 default: Option<String>,
107}
108
109#[derive(Debug, Clone)]
110struct Union {
111 name: Option<String>,
112 fields: Vec<Field>,
113}
114
115#[derive(Debug, Clone)]
116struct Method {
117 name: String,
118 params: Vec<Field>,
119 results: Vec<Field>,
120}
121
122impl ZapSchema {
123 pub fn new(source: &str, path: &str) -> Self {
125 let format = Self::detect_format(path, source);
126 Self {
127 source: source.to_string(),
128 path: path.to_string(),
129 format,
130 }
131 }
132
133 pub fn with_format(source: &str, path: &str, format: SchemaFormat) -> Self {
135 Self {
136 source: source.to_string(),
137 path: path.to_string(),
138 format,
139 }
140 }
141
142 pub fn from_file(path: &Path) -> Result<Self> {
144 let source = std::fs::read_to_string(path)
145 .map_err(|e| Error::Config(format!("failed to read schema: {}", e)))?;
146 let path_str = path.to_str().unwrap_or("schema.zap");
147 let format = Self::detect_format(path_str, &source);
148 Ok(Self {
149 source,
150 path: path_str.to_string(),
151 format,
152 })
153 }
154
155 fn detect_format(path: &str, source: &str) -> SchemaFormat {
157 if path.ends_with(".capnp") {
159 return SchemaFormat::Capnp;
160 }
161 if path.ends_with(".zap") {
162 return SchemaFormat::Zap;
163 }
164
165 if source.contains("@0x") || source.contains("@0;") || source.contains("@1;") {
167 return SchemaFormat::Capnp;
168 }
169
170 SchemaFormat::Zap
172 }
173
174 pub fn format(&self) -> &SchemaFormat {
176 &self.format
177 }
178
179 fn generate_id(seed: &str) -> u64 {
181 let mut hasher = DefaultHasher::new();
182 seed.hash(&mut hasher);
183 hasher.finish() | 0x8000_0000_0000_0000
185 }
186
187 fn parse(&self) -> Result<Vec<Item>> {
189 match self.format {
190 SchemaFormat::Zap => self.parse_zap(),
191 SchemaFormat::Capnp => self.parse_capnp(),
192 }
193 }
194
195 fn parse_zap(&self) -> Result<Vec<Item>> {
197 let mut items = Vec::new();
198 let lines: Vec<&str> = self.source.lines().collect();
199 let mut i = 0;
200
201 while i < lines.len() {
202 let line = lines[i].trim();
203
204 if line.is_empty() {
206 i += 1;
207 continue;
208 }
209
210 if line.starts_with('#') {
212 items.push(Item::Comment(line[1..].trim().to_string()));
213 i += 1;
214 continue;
215 }
216
217 if line.starts_with("using ") {
219 items.push(Item::Using(line[6..].trim().to_string()));
220 i += 1;
221 continue;
222 }
223
224 if line.starts_with("const ") {
226 let rest = line[6..].trim();
227 if let Some((name_type, value)) = rest.split_once('=') {
228 let name_type = name_type.trim();
229 let value = value.trim().to_string();
230 if let Some((name, typ)) = name_type.split_once(':') {
231 items.push(Item::Const {
232 name: name.trim().to_string(),
233 typ: typ.trim().to_string(),
234 value,
235 });
236 }
237 }
238 i += 1;
239 continue;
240 }
241
242 if line.starts_with("struct ") {
244 let name = line[7..].trim().to_string();
245 let (fields, unions, nested, consumed) = self.parse_struct_body(&lines[i + 1..])?;
246 items.push(Item::Struct { name, fields, unions, nested });
247 i += 1 + consumed;
248 continue;
249 }
250
251 if line.starts_with("enum ") {
253 let name = line[5..].trim().to_string();
254 let (variants, consumed) = self.parse_enum_body(&lines[i + 1..])?;
255 items.push(Item::Enum { name, variants });
256 i += 1 + consumed;
257 continue;
258 }
259
260 if line.starts_with("interface ") {
262 let rest = line[10..].trim();
263 let (name, extends) = if let Some((n, e)) = rest.split_once("extends") {
264 (n.trim().to_string(), Some(e.trim().to_string()))
265 } else {
266 (rest.to_string(), None)
267 };
268 let (methods, nested, consumed) = self.parse_interface_body(&lines[i + 1..])?;
269 items.push(Item::Interface { name, extends, methods, nested });
270 i += 1 + consumed;
271 continue;
272 }
273
274 i += 1;
276 }
277
278 Ok(items)
279 }
280
281 fn parse_capnp(&self) -> Result<Vec<Item>> {
283 let mut items = Vec::new();
284
285 let mut source = String::new();
287 for line in self.source.lines() {
288 let line = if let Some(idx) = line.find('#') {
289 &line[..idx]
290 } else {
291 line
292 };
293 source.push_str(line);
294 source.push(' ');
295 }
296
297 let tokens = self.tokenize_capnp(&source);
299 let mut pos = 0;
300
301 while pos < tokens.len() {
302 let token = &tokens[pos];
303
304 if token.starts_with('@') {
306 pos += 1;
307 if pos < tokens.len() && tokens[pos] == ";" {
308 pos += 1;
309 }
310 continue;
311 }
312
313 if token == "using" {
315 let mut import = String::new();
316 pos += 1;
317 while pos < tokens.len() && tokens[pos] != ";" {
318 import.push_str(&tokens[pos]);
319 import.push(' ');
320 pos += 1;
321 }
322 items.push(Item::Using(import.trim().to_string()));
323 pos += 1; continue;
325 }
326
327 if token == "const" {
329 pos += 1;
330 let name = tokens.get(pos).cloned().unwrap_or_default();
331 pos += 1;
332 if tokens.get(pos).map(|s| s.as_str()) == Some(":") {
333 pos += 1;
334 }
335 let typ = tokens.get(pos).cloned().unwrap_or_default();
336 pos += 1;
337 if tokens.get(pos).map(|s| s.as_str()) == Some("=") {
338 pos += 1;
339 }
340 let value = tokens.get(pos).cloned().unwrap_or_default();
341 pos += 1;
342 if tokens.get(pos).map(|s| s.as_str()) == Some(";") {
343 pos += 1;
344 }
345 items.push(Item::Const { name, typ, value });
346 continue;
347 }
348
349 if token == "struct" {
351 pos += 1;
352 let name = tokens.get(pos).cloned().unwrap_or_default();
353 pos += 1;
354 if pos < tokens.len() && tokens[pos].starts_with('@') {
356 pos += 1;
357 }
358 while pos < tokens.len() && tokens[pos] != "{" {
360 pos += 1;
361 }
362 pos += 1; let (fields, unions, nested, new_pos) = self.parse_capnp_struct_body(&tokens, pos)?;
365 pos = new_pos;
366
367 items.push(Item::Struct { name, fields, unions, nested });
368 continue;
369 }
370
371 if token == "enum" {
373 pos += 1;
374 let name = tokens.get(pos).cloned().unwrap_or_default();
375 pos += 1;
376 if pos < tokens.len() && tokens[pos].starts_with('@') {
378 pos += 1;
379 }
380 while pos < tokens.len() && tokens[pos] != "{" {
382 pos += 1;
383 }
384 pos += 1; let (variants, new_pos) = self.parse_capnp_enum_body(&tokens, pos)?;
387 pos = new_pos;
388
389 items.push(Item::Enum { name, variants });
390 continue;
391 }
392
393 if token == "interface" {
395 pos += 1;
396 let name = tokens.get(pos).cloned().unwrap_or_default();
397 pos += 1;
398
399 let mut extends = None;
401 if pos < tokens.len() && tokens[pos] == "extends" {
402 pos += 1;
403 if pos < tokens.len() && tokens[pos] == "(" {
404 pos += 1;
405 extends = tokens.get(pos).cloned();
406 pos += 1;
407 if pos < tokens.len() && tokens[pos] == ")" {
408 pos += 1;
409 }
410 }
411 }
412
413 if pos < tokens.len() && tokens[pos].starts_with('@') {
415 pos += 1;
416 }
417 while pos < tokens.len() && tokens[pos] != "{" {
419 pos += 1;
420 }
421 pos += 1; let (methods, nested, new_pos) = self.parse_capnp_interface_body(&tokens, pos)?;
424 pos = new_pos;
425
426 items.push(Item::Interface { name, extends, methods, nested });
427 continue;
428 }
429
430 pos += 1;
431 }
432
433 Ok(items)
434 }
435
436 fn tokenize_capnp(&self, source: &str) -> Vec<String> {
438 let mut tokens = Vec::new();
439 let mut current = String::new();
440 let mut in_string = false;
441 let mut chars = source.chars().peekable();
442
443 while let Some(c) = chars.next() {
444 if in_string {
445 current.push(c);
446 if c == '"' {
447 tokens.push(std::mem::take(&mut current));
448 in_string = false;
449 }
450 continue;
451 }
452
453 match c {
454 '"' => {
455 if !current.is_empty() {
456 tokens.push(std::mem::take(&mut current));
457 }
458 current.push(c);
459 in_string = true;
460 }
461 '{' | '}' | '(' | ')' | ';' | ':' | '=' | ',' => {
462 if !current.is_empty() {
463 tokens.push(std::mem::take(&mut current));
464 }
465 tokens.push(c.to_string());
466 }
467 '-' if chars.peek() == Some(&'>') => {
468 if !current.is_empty() {
469 tokens.push(std::mem::take(&mut current));
470 }
471 chars.next();
472 tokens.push("->".to_string());
473 }
474 c if c.is_whitespace() => {
475 if !current.is_empty() {
476 tokens.push(std::mem::take(&mut current));
477 }
478 }
479 _ => {
480 current.push(c);
481 }
482 }
483 }
484
485 if !current.is_empty() {
486 tokens.push(current);
487 }
488
489 tokens
490 }
491
492 fn parse_capnp_struct_body(&self, tokens: &[String], mut pos: usize) -> Result<(Vec<Field>, Vec<Union>, Vec<Item>, usize)> {
494 let mut fields = Vec::new();
495 let mut unions = Vec::new();
496 let mut nested = Vec::new();
497
498 while pos < tokens.len() && tokens[pos] != "}" {
499 let token = &tokens[pos];
500
501 if token == "struct" {
503 pos += 1;
504 let name = tokens.get(pos).cloned().unwrap_or_default();
505 pos += 1;
506 if pos < tokens.len() && tokens[pos].starts_with('@') {
507 pos += 1;
508 }
509 while pos < tokens.len() && tokens[pos] != "{" {
510 pos += 1;
511 }
512 pos += 1;
513 let (nfields, nunions, nnested, new_pos) = self.parse_capnp_struct_body(tokens, pos)?;
514 pos = new_pos;
515 nested.push(Item::Struct { name, fields: nfields, unions: nunions, nested: nnested });
516 continue;
517 }
518
519 if token == "enum" {
521 pos += 1;
522 let name = tokens.get(pos).cloned().unwrap_or_default();
523 pos += 1;
524 if pos < tokens.len() && tokens[pos].starts_with('@') {
525 pos += 1;
526 }
527 while pos < tokens.len() && tokens[pos] != "{" {
528 pos += 1;
529 }
530 pos += 1;
531 let (variants, new_pos) = self.parse_capnp_enum_body(tokens, pos)?;
532 pos = new_pos;
533 nested.push(Item::Enum { name, variants });
534 continue;
535 }
536
537 if token == "union" {
539 pos += 1;
540 if pos < tokens.len() && tokens[pos] == "{" {
541 pos += 1;
542 }
543 let mut union_fields = Vec::new();
544 while pos < tokens.len() && tokens[pos] != "}" {
545 if let Some((field, new_pos)) = self.parse_capnp_field(tokens, pos)? {
546 union_fields.push(field);
547 pos = new_pos;
548 } else {
549 pos += 1;
550 }
551 }
552 pos += 1; unions.push(Union { name: None, fields: union_fields });
554 continue;
555 }
556
557 if let Some((field, new_pos)) = self.parse_capnp_field(tokens, pos)? {
559 fields.push(field);
560 pos = new_pos;
561 } else {
562 pos += 1;
563 }
564 }
565
566 if pos < tokens.len() && tokens[pos] == "}" {
567 pos += 1;
568 }
569
570 Ok((fields, unions, nested, pos))
571 }
572
573 fn parse_capnp_field(&self, tokens: &[String], mut pos: usize) -> Result<Option<(Field, usize)>> {
575 if pos >= tokens.len() {
577 return Ok(None);
578 }
579
580 let name = &tokens[pos];
581 if name == "}" || name == "union" || name == "struct" || name == "enum" {
582 return Ok(None);
583 }
584
585 pos += 1;
586
587 if pos < tokens.len() && tokens[pos].starts_with('@') {
589 pos += 1;
590 }
591
592 if pos < tokens.len() && tokens[pos] == ":" {
594 pos += 1;
595 } else {
596 return Ok(None);
597 }
598
599 let mut typ = String::new();
601 let mut paren_depth = 0;
602 while pos < tokens.len() {
603 let t = &tokens[pos];
604 if t == "=" || (t == ";" && paren_depth == 0) {
605 break;
606 }
607 if t == "(" {
608 paren_depth += 1;
609 }
610 if t == ")" {
611 paren_depth -= 1;
612 }
613 typ.push_str(t);
614 pos += 1;
615 }
616
617 let default = if pos < tokens.len() && tokens[pos] == "=" {
619 pos += 1;
620 let mut val = String::new();
621 while pos < tokens.len() && tokens[pos] != ";" {
622 val.push_str(&tokens[pos]);
623 pos += 1;
624 }
625 Some(val)
626 } else {
627 None
628 };
629
630 if pos < tokens.len() && tokens[pos] == ";" {
632 pos += 1;
633 }
634
635 Ok(Some((Field {
636 name: name.clone(),
637 typ,
638 default,
639 }, pos)))
640 }
641
642 fn parse_capnp_enum_body(&self, tokens: &[String], mut pos: usize) -> Result<(Vec<String>, usize)> {
644 let mut variants = Vec::new();
645
646 while pos < tokens.len() && tokens[pos] != "}" {
647 let name = tokens[pos].clone();
648 if name != ";" && !name.starts_with('@') {
649 variants.push(name);
650 }
651 pos += 1;
652 }
653
654 if pos < tokens.len() && tokens[pos] == "}" {
655 pos += 1;
656 }
657
658 Ok((variants, pos))
659 }
660
661 fn parse_capnp_interface_body(&self, tokens: &[String], mut pos: usize) -> Result<(Vec<Method>, Vec<Item>, usize)> {
663 let mut methods = Vec::new();
664 let mut nested = Vec::new();
665
666 while pos < tokens.len() && tokens[pos] != "}" {
667 let token = &tokens[pos];
668
669 if token == "enum" {
671 pos += 1;
672 let name = tokens.get(pos).cloned().unwrap_or_default();
673 pos += 1;
674 if pos < tokens.len() && tokens[pos].starts_with('@') {
675 pos += 1;
676 }
677 while pos < tokens.len() && tokens[pos] != "{" {
678 pos += 1;
679 }
680 pos += 1;
681 let (variants, new_pos) = self.parse_capnp_enum_body(tokens, pos)?;
682 pos = new_pos;
683 nested.push(Item::Enum { name, variants });
684 continue;
685 }
686
687 if token == "struct" {
689 pos += 1;
690 let name = tokens.get(pos).cloned().unwrap_or_default();
691 pos += 1;
692 if pos < tokens.len() && tokens[pos].starts_with('@') {
693 pos += 1;
694 }
695 while pos < tokens.len() && tokens[pos] != "{" {
696 pos += 1;
697 }
698 pos += 1;
699 let (nfields, nunions, nnested, new_pos) = self.parse_capnp_struct_body(tokens, pos)?;
700 pos = new_pos;
701 nested.push(Item::Struct { name, fields: nfields, unions: nunions, nested: nnested });
702 continue;
703 }
704
705 if let Some((method, new_pos)) = self.parse_capnp_method(tokens, pos)? {
707 methods.push(method);
708 pos = new_pos;
709 } else {
710 pos += 1;
711 }
712 }
713
714 if pos < tokens.len() && tokens[pos] == "}" {
715 pos += 1;
716 }
717
718 Ok((methods, nested, pos))
719 }
720
721 fn parse_capnp_method(&self, tokens: &[String], mut pos: usize) -> Result<Option<(Method, usize)>> {
723 if pos >= tokens.len() {
724 return Ok(None);
725 }
726
727 let name = &tokens[pos];
728 if name == "}" || name == "enum" || name == "struct" {
729 return Ok(None);
730 }
731
732 pos += 1;
733
734 if pos < tokens.len() && tokens[pos].starts_with('@') {
736 pos += 1;
737 }
738
739 let params = if pos < tokens.len() && tokens[pos] == "(" {
741 let (p, new_pos) = self.parse_capnp_param_list(tokens, pos)?;
742 pos = new_pos;
743 p
744 } else {
745 Vec::new()
746 };
747
748 if pos < tokens.len() && tokens[pos] == "->" {
750 pos += 1;
751 }
752
753 let results = if pos < tokens.len() && tokens[pos] == "(" {
755 let (r, new_pos) = self.parse_capnp_param_list(tokens, pos)?;
756 pos = new_pos;
757 r
758 } else {
759 Vec::new()
760 };
761
762 if pos < tokens.len() && tokens[pos] == ";" {
764 pos += 1;
765 }
766
767 Ok(Some((Method {
768 name: name.clone(),
769 params,
770 results,
771 }, pos)))
772 }
773
774 fn parse_capnp_param_list(&self, tokens: &[String], mut pos: usize) -> Result<(Vec<Field>, usize)> {
776 let mut params = Vec::new();
777
778 if pos < tokens.len() && tokens[pos] == "(" {
779 pos += 1;
780 }
781
782 while pos < tokens.len() && tokens[pos] != ")" {
783 if tokens[pos] == "," {
784 pos += 1;
785 continue;
786 }
787
788 let name = tokens[pos].clone();
789 pos += 1;
790
791 if pos < tokens.len() && tokens[pos] == ":" {
792 pos += 1;
793
794 let mut typ = String::new();
796 let mut paren_depth = 0;
797 while pos < tokens.len() {
798 let t = &tokens[pos];
799 if (t == "," || t == ")") && paren_depth == 0 {
800 break;
801 }
802 if t == "(" {
803 paren_depth += 1;
804 }
805 if t == ")" && paren_depth > 0 {
806 paren_depth -= 1;
807 }
808 typ.push_str(t);
809 pos += 1;
810 }
811
812 params.push(Field { name, typ, default: None });
813 }
814 }
815
816 if pos < tokens.len() && tokens[pos] == ")" {
817 pos += 1;
818 }
819
820 Ok((params, pos))
821 }
822
823 fn parse_struct_body(&self, lines: &[&str]) -> Result<(Vec<Field>, Vec<Union>, Vec<Item>, usize)> {
825 let mut fields = Vec::new();
826 let mut unions = Vec::new();
827 let mut nested = Vec::new();
828 let mut i = 0;
829
830 while i < lines.len() {
831 let line = lines[i];
832
833 if !line.starts_with(" ") && !line.starts_with("\t") && !line.trim().is_empty() {
835 break;
836 }
837
838 let trimmed = line.trim();
839 i += 1;
840
841 if trimmed.is_empty() {
842 continue;
843 }
844
845 if trimmed.starts_with('#') {
847 continue;
848 }
849
850 if trimmed.starts_with("struct ") {
852 let name = trimmed[7..].trim().to_string();
853 let (nfields, nunions, nnested, nconsumed) = self.parse_struct_body(&lines[i..])?;
854 nested.push(Item::Struct { name, fields: nfields, unions: nunions, nested: nnested });
855 i += nconsumed;
856 continue;
857 }
858
859 if trimmed.starts_with("enum ") {
861 let name = trimmed[5..].trim().to_string();
862 let (variants, nconsumed) = self.parse_enum_body(&lines[i..])?;
863 nested.push(Item::Enum { name, variants });
864 i += nconsumed;
865 continue;
866 }
867
868 if trimmed.starts_with("union") {
870 let union_name = if trimmed.len() > 5 {
871 Some(trimmed[5..].trim().to_string())
872 } else {
873 None
874 };
875
876 let mut union_fields = Vec::new();
878 while i < lines.len() {
879 let uline = lines[i];
880 if !uline.starts_with(" ") && !uline.starts_with("\t\t") {
881 if !uline.trim().is_empty() && (uline.starts_with(" ") || uline.starts_with("\t")) {
882 break;
883 }
884 }
885 let utrimmed = uline.trim();
886 if utrimmed.is_empty() {
887 i += 1;
888 continue;
889 }
890 if !uline.starts_with(" ") && !uline.starts_with("\t\t") {
891 break;
892 }
893 if let Some(field) = self.parse_field(utrimmed) {
894 union_fields.push(field);
895 }
896 i += 1;
897 }
898
899 unions.push(Union {
900 name: union_name.filter(|n| !n.is_empty()),
901 fields: union_fields,
902 });
903 continue;
904 }
905
906 if let Some(field) = self.parse_field(trimmed) {
908 fields.push(field);
909 }
910 }
911
912 Ok((fields, unions, nested, i))
913 }
914
915 fn parse_field(&self, line: &str) -> Option<Field> {
917 let (name_type, default) = if let Some((nt, d)) = line.split_once('=') {
920 (nt.trim(), Some(d.trim().to_string()))
921 } else {
922 (line, None)
923 };
924
925 if let Some((name, typ)) = name_type.split_once(':') {
927 return Some(Field {
928 name: name.trim().to_string(),
929 typ: typ.trim().to_string(),
930 default,
931 });
932 }
933
934 let parts: Vec<&str> = name_type.splitn(2, char::is_whitespace).collect();
937 if parts.len() == 2 {
938 let name = parts[0].trim();
939 let typ = parts[1].trim();
940 if !typ.is_empty() && !["struct", "enum", "interface", "union", "using", "const"].contains(&name) {
942 return Some(Field {
943 name: name.to_string(),
944 typ: typ.to_string(),
945 default,
946 });
947 }
948 }
949
950 None
951 }
952
953 fn parse_enum_body(&self, lines: &[&str]) -> Result<(Vec<String>, usize)> {
955 let mut variants = Vec::new();
956 let mut consumed = 0;
957
958 for line in lines {
959 if !line.starts_with(" ") && !line.starts_with("\t") && !line.trim().is_empty() {
960 break;
961 }
962
963 let trimmed = line.trim();
964 consumed += 1;
965
966 if trimmed.is_empty() || trimmed.starts_with('#') {
967 continue;
968 }
969
970 variants.push(trimmed.to_string());
971 }
972
973 Ok((variants, consumed))
974 }
975
976 fn parse_interface_body(&self, lines: &[&str]) -> Result<(Vec<Method>, Vec<Item>, usize)> {
978 let mut methods = Vec::new();
979 let mut nested = Vec::new();
980 let mut i = 0;
981
982 while i < lines.len() {
983 let line = lines[i];
984
985 if !line.starts_with(" ") && !line.starts_with("\t") && !line.trim().is_empty() {
986 break;
987 }
988
989 let trimmed = line.trim();
990 i += 1;
991
992 if trimmed.is_empty() || trimmed.starts_with('#') {
993 continue;
994 }
995
996 if trimmed.starts_with("enum ") {
998 let name = trimmed[5..].trim().to_string();
999 let (variants, nconsumed) = self.parse_enum_body(&lines[i..])?;
1000 nested.push(Item::Enum { name, variants });
1001 i += nconsumed;
1002 continue;
1003 }
1004
1005 if trimmed.starts_with("struct ") {
1007 let name = trimmed[7..].trim().to_string();
1008 let (nfields, nunions, nnested, nconsumed) = self.parse_struct_body(&lines[i..])?;
1009 nested.push(Item::Struct { name, fields: nfields, unions: nunions, nested: nnested });
1010 i += nconsumed;
1011 continue;
1012 }
1013
1014 if let Some(method) = self.parse_method(trimmed) {
1016 methods.push(method);
1017 }
1018 }
1019
1020 Ok((methods, nested, i))
1021 }
1022
1023 fn parse_method(&self, line: &str) -> Option<Method> {
1025 let (name_params, results_str) = line.split_once("->")?;
1030 let name_params = name_params.trim();
1031
1032 let (name, params_str) = if let Some(idx) = name_params.find('(') {
1034 let name = name_params[..idx].trim();
1035 let params = name_params[idx..].trim();
1036 (name, params)
1037 } else {
1038 (name_params, "()")
1039 };
1040
1041 let params = self.parse_param_list(params_str);
1042 let results = self.parse_param_list(results_str.trim());
1043
1044 Some(Method {
1045 name: name.to_string(),
1046 params,
1047 results,
1048 })
1049 }
1050
1051 fn parse_param_list(&self, s: &str) -> Vec<Field> {
1053 let s = s.trim();
1054 if s == "()" || s.is_empty() {
1055 return Vec::new();
1056 }
1057
1058 let inner = s.trim_start_matches('(').trim_end_matches(')').trim();
1060 if inner.is_empty() {
1061 return Vec::new();
1062 }
1063
1064 let mut params = Vec::new();
1066 let mut current = String::new();
1067 let mut depth = 0;
1068
1069 for c in inner.chars() {
1070 match c {
1071 '(' => {
1072 depth += 1;
1073 current.push(c);
1074 }
1075 ')' => {
1076 depth -= 1;
1077 current.push(c);
1078 }
1079 ',' if depth == 0 => {
1080 if let Some(field) = self.parse_field(current.trim()) {
1081 params.push(field);
1082 }
1083 current.clear();
1084 }
1085 _ => {
1086 current.push(c);
1087 }
1088 }
1089 }
1090
1091 if !current.is_empty() {
1093 if let Some(field) = self.parse_field(current.trim()) {
1094 params.push(field);
1095 }
1096 }
1097
1098 params
1099 }
1100
1101 pub fn compile(&self) -> Result<String> {
1103 let items = self.parse()?;
1104 let mut output = String::new();
1105
1106 let file_id = Self::generate_id(&self.path);
1108 output.push_str(&format!("@{:#018x};\n\n", file_id));
1109
1110 for item in items {
1111 self.emit_item(&mut output, &item, 0)?;
1112 }
1113
1114 Ok(output)
1115 }
1116
1117 fn emit_item(&self, output: &mut String, item: &Item, indent: usize) -> Result<()> {
1119 let pad = " ".repeat(indent);
1120
1121 match item {
1122 Item::Comment(text) => {
1123 output.push_str(&format!("{}# {}\n", pad, text));
1124 }
1125 Item::Using(import) => {
1126 output.push_str(&format!("{}using {};\n", pad, import));
1127 }
1128 Item::Const { name, typ, value } => {
1129 output.push_str(&format!("{}const {} :{} = {};\n", pad, name, typ, value));
1130 }
1131 Item::Struct { name, fields, unions, nested } => {
1132 let struct_id = Self::generate_id(&format!("{}:{}", self.path, name));
1133 output.push_str(&format!("{}struct {} @{:#018x} {{\n", pad, name, struct_id));
1134
1135 let mut ordinal = 0u16;
1136
1137 for field in fields {
1138 let default_str = field
1139 .default
1140 .as_ref()
1141 .map(|d| format!(" = {}", d))
1142 .unwrap_or_default();
1143 output.push_str(&format!(
1144 "{} {} @{} :{}{};\n",
1145 pad, field.name, ordinal, field.typ, default_str
1146 ));
1147 ordinal += 1;
1148 }
1149
1150 for union in unions {
1151 if let Some(ref uname) = union.name {
1152 output.push_str(&format!("{} {} :union {{\n", pad, uname));
1153 } else {
1154 output.push_str(&format!("{} union {{\n", pad));
1155 }
1156 for field in &union.fields {
1157 let default_str = field
1158 .default
1159 .as_ref()
1160 .map(|d| format!(" = {}", d))
1161 .unwrap_or_default();
1162 output.push_str(&format!(
1163 "{} {} @{} :{}{};\n",
1164 pad, field.name, ordinal, field.typ, default_str
1165 ));
1166 ordinal += 1;
1167 }
1168 output.push_str(&format!("{} }}\n", pad));
1169 }
1170
1171 for nested_item in nested {
1172 self.emit_item(output, nested_item, indent + 1)?;
1173 }
1174
1175 output.push_str(&format!("{}}}\n\n", pad));
1176 }
1177 Item::Enum { name, variants } => {
1178 let enum_id = Self::generate_id(&format!("{}:{}", self.path, name));
1179 output.push_str(&format!("{}enum {} @{:#018x} {{\n", pad, name, enum_id));
1180
1181 for (i, variant) in variants.iter().enumerate() {
1182 output.push_str(&format!("{} {} @{};\n", pad, variant, i));
1183 }
1184
1185 output.push_str(&format!("{}}}\n\n", pad));
1186 }
1187 Item::Interface { name, extends, methods, nested } => {
1188 let iface_id = Self::generate_id(&format!("{}:{}", self.path, name));
1189 let extends_str = extends
1190 .as_ref()
1191 .map(|e| format!(" extends({})", e))
1192 .unwrap_or_default();
1193 output.push_str(&format!(
1194 "{}interface {}{} @{:#018x} {{\n",
1195 pad, name, extends_str, iface_id
1196 ));
1197
1198 for (i, method) in methods.iter().enumerate() {
1199 let params_str = if method.params.is_empty() {
1200 "()".to_string()
1201 } else {
1202 let ps: Vec<String> = method
1203 .params
1204 .iter()
1205 .map(|p| format!("{} :{}", p.name, p.typ))
1206 .collect();
1207 format!("({})", ps.join(", "))
1208 };
1209
1210 let results_str = if method.results.is_empty() {
1211 "()".to_string()
1212 } else {
1213 let rs: Vec<String> = method
1214 .results
1215 .iter()
1216 .map(|r| format!("{} :{}", r.name, r.typ))
1217 .collect();
1218 format!("({})", rs.join(", "))
1219 };
1220
1221 output.push_str(&format!(
1222 "{} {} @{} {} -> {};\n",
1223 pad, method.name, i, params_str, results_str
1224 ));
1225 }
1226
1227 for nested_item in nested {
1228 self.emit_item(output, nested_item, indent + 1)?;
1229 }
1230
1231 output.push_str(&format!("{}}}\n\n", pad));
1232 }
1233 }
1234
1235 Ok(())
1236 }
1237
1238 #[deprecated(note = "use compile() instead")]
1240 pub fn to_capnp(&self) -> Result<String> {
1241 self.compile()
1242 }
1243
1244 pub fn to_zap(&self) -> Result<String> {
1249 let items = self.parse()?;
1250 let mut output = String::new();
1251
1252 output.push_str("# ZAP Schema - converted to whitespace format\n\n");
1253
1254 for item in items {
1255 self.emit_zap_item(&mut output, &item, 0)?;
1256 }
1257
1258 Ok(output)
1259 }
1260
1261 fn emit_zap_item(&self, output: &mut String, item: &Item, indent: usize) -> Result<()> {
1263 let pad = " ".repeat(indent);
1264
1265 match item {
1266 Item::Comment(text) => {
1267 output.push_str(&format!("{}# {}\n", pad, text));
1268 }
1269 Item::Using(import) => {
1270 output.push_str(&format!("{}using {}\n", pad, import));
1271 }
1272 Item::Const { name, typ, value } => {
1273 output.push_str(&format!("{}const {} :{} = {}\n", pad, name, typ, value));
1274 }
1275 Item::Struct { name, fields, unions, nested } => {
1276 output.push_str(&format!("{}struct {}\n", pad, name));
1277
1278 for field in fields {
1279 let default_str = field
1280 .default
1281 .as_ref()
1282 .map(|d| format!(" = {}", d))
1283 .unwrap_or_default();
1284 output.push_str(&format!(
1285 "{} {} :{}{}\n",
1286 pad, field.name, field.typ, default_str
1287 ));
1288 }
1289
1290 for union in unions {
1291 if let Some(ref uname) = union.name {
1292 output.push_str(&format!("{} union {}\n", pad, uname));
1293 } else {
1294 output.push_str(&format!("{} union\n", pad));
1295 }
1296 for field in &union.fields {
1297 let default_str = field
1298 .default
1299 .as_ref()
1300 .map(|d| format!(" = {}", d))
1301 .unwrap_or_default();
1302 output.push_str(&format!(
1303 "{} {} :{}{}\n",
1304 pad, field.name, field.typ, default_str
1305 ));
1306 }
1307 }
1308
1309 for nested_item in nested {
1310 output.push('\n');
1311 self.emit_zap_item(output, nested_item, indent + 1)?;
1312 }
1313
1314 output.push('\n');
1315 }
1316 Item::Enum { name, variants } => {
1317 output.push_str(&format!("{}enum {}\n", pad, name));
1318 for variant in variants {
1319 output.push_str(&format!("{} {}\n", pad, variant));
1320 }
1321 output.push('\n');
1322 }
1323 Item::Interface { name, extends, methods, nested } => {
1324 let extends_str = extends
1325 .as_ref()
1326 .map(|e| format!(" extends {}", e))
1327 .unwrap_or_default();
1328 output.push_str(&format!("{}interface {}{}\n", pad, name, extends_str));
1329
1330 for method in methods {
1331 let params_str = if method.params.is_empty() {
1332 "()".to_string()
1333 } else {
1334 let ps: Vec<String> = method
1335 .params
1336 .iter()
1337 .map(|p| format!("{} :{}", p.name, p.typ))
1338 .collect();
1339 format!("({})", ps.join(", "))
1340 };
1341
1342 let results_str = if method.results.is_empty() {
1343 "()".to_string()
1344 } else {
1345 let rs: Vec<String> = method
1346 .results
1347 .iter()
1348 .map(|r| format!("{} :{}", r.name, r.typ))
1349 .collect();
1350 format!("({})", rs.join(", "))
1351 };
1352
1353 output.push_str(&format!(
1354 "{} {} {} -> {}\n",
1355 pad, method.name, params_str, results_str
1356 ));
1357 }
1358
1359 for nested_item in nested {
1360 output.push('\n');
1361 self.emit_zap_item(output, nested_item, indent + 1)?;
1362 }
1363
1364 output.push('\n');
1365 }
1366 }
1367
1368 Ok(())
1369 }
1370
1371 pub fn write(&self, output_path: &Path) -> Result<()> {
1373 let compiled = self.compile()?;
1374 std::fs::write(output_path, compiled)
1375 .map_err(|e| Error::Config(format!("failed to write schema: {}", e)))?;
1376 Ok(())
1377 }
1378
1379 pub fn to_rust(&self) -> Result<String> {
1381 let items = self.parse()?;
1382 let mut output = String::new();
1383
1384 output.push_str("//! Generated by ZAP Schema Compiler\n");
1385 output.push_str("//! Do not edit manually\n\n");
1386 output.push_str("use serde::{Serialize, Deserialize};\n\n");
1387
1388 for item in items {
1389 self.emit_rust_item(&mut output, &item)?;
1390 }
1391
1392 Ok(output)
1393 }
1394
1395 fn emit_rust_item(&self, output: &mut String, item: &Item) -> Result<()> {
1397 match item {
1398 Item::Comment(text) => {
1399 output.push_str(&format!("/// {}\n", text));
1400 }
1401 Item::Struct { name, fields, unions, nested } => {
1402 output.push_str("#[derive(Debug, Clone, Serialize, Deserialize)]\n");
1403 output.push_str(&format!("pub struct {} {{\n", name));
1404
1405 for field in fields {
1406 let rust_type = self.zap_type_to_rust(&field.typ);
1407 output.push_str(&format!(" pub {}: {},\n",
1408 to_snake_case(&field.name), rust_type));
1409 }
1410
1411 for (i, union) in unions.iter().enumerate() {
1413 let union_name = union.name.as_ref()
1414 .cloned()
1415 .unwrap_or_else(|| format!("{}Union{}", name, i));
1416 output.push_str(&format!(" pub {}: {},\n",
1417 to_snake_case(&union_name), union_name));
1418 }
1419
1420 output.push_str("}\n\n");
1421
1422 for (i, union) in unions.iter().enumerate() {
1424 let union_name = union.name.as_ref()
1425 .cloned()
1426 .unwrap_or_else(|| format!("{}Union{}", name, i));
1427 output.push_str("#[derive(Debug, Clone, Serialize, Deserialize)]\n");
1428 output.push_str(&format!("pub enum {} {{\n", union_name));
1429 for field in &union.fields {
1430 let rust_type = self.zap_type_to_rust(&field.typ);
1431 output.push_str(&format!(" {}({}),\n",
1432 to_pascal_case(&field.name), rust_type));
1433 }
1434 output.push_str("}\n\n");
1435 }
1436
1437 for nested_item in nested {
1439 self.emit_rust_item(output, nested_item)?;
1440 }
1441 }
1442 Item::Enum { name, variants } => {
1443 output.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]\n");
1444 output.push_str(&format!("pub enum {} {{\n", name));
1445 for variant in variants {
1446 output.push_str(&format!(" {},\n", to_pascal_case(variant)));
1447 }
1448 output.push_str("}\n\n");
1449 }
1450 Item::Interface { name, methods, nested, .. } => {
1451 output.push_str("#[async_trait::async_trait]\n");
1453 output.push_str(&format!("pub trait {} {{\n", name));
1454 for method in methods {
1455 let params_str = method.params.iter()
1456 .map(|p| format!("{}: {}", to_snake_case(&p.name), self.zap_type_to_rust(&p.typ)))
1457 .collect::<Vec<_>>()
1458 .join(", ");
1459
1460 let result_type = if method.results.is_empty() {
1461 "()".to_string()
1462 } else if method.results.len() == 1 {
1463 self.zap_type_to_rust(&method.results[0].typ)
1464 } else {
1465 let types: Vec<_> = method.results.iter()
1466 .map(|r| self.zap_type_to_rust(&r.typ))
1467 .collect();
1468 format!("({})", types.join(", "))
1469 };
1470
1471 output.push_str(&format!(" async fn {}(&self{}{}) -> Result<{}, ZapError>;\n",
1472 to_snake_case(&method.name),
1473 if params_str.is_empty() { "" } else { ", " },
1474 params_str,
1475 result_type
1476 ));
1477 }
1478 output.push_str("}\n\n");
1479
1480 for nested_item in nested {
1482 self.emit_rust_item(output, nested_item)?;
1483 }
1484 }
1485 _ => {}
1486 }
1487
1488 Ok(())
1489 }
1490
1491 fn zap_type_to_rust(&self, typ: &str) -> String {
1493 match typ {
1494 "Text" => "String".to_string(),
1495 "Data" => "Vec<u8>".to_string(),
1496 "Void" => "()".to_string(),
1497 "Bool" => "bool".to_string(),
1498 "Int8" => "i8".to_string(),
1499 "Int16" => "i16".to_string(),
1500 "Int32" => "i32".to_string(),
1501 "Int64" => "i64".to_string(),
1502 "UInt8" => "u8".to_string(),
1503 "UInt16" => "u16".to_string(),
1504 "UInt32" => "u32".to_string(),
1505 "UInt64" => "u64".to_string(),
1506 "Float32" => "f32".to_string(),
1507 "Float64" => "f64".to_string(),
1508 t if t.starts_with("List(") && t.ends_with(")") => {
1509 let inner = &t[5..t.len()-1];
1510 format!("Vec<{}>", self.zap_type_to_rust(inner))
1511 }
1512 t => t.to_string(), }
1514 }
1515}
1516
1517fn to_snake_case(s: &str) -> String {
1519 let mut result = String::new();
1520 for (i, c) in s.chars().enumerate() {
1521 if c.is_uppercase() {
1522 if i > 0 {
1523 result.push('_');
1524 }
1525 result.push(c.to_lowercase().next().unwrap());
1526 } else {
1527 result.push(c);
1528 }
1529 }
1530 result
1531}
1532
1533fn to_pascal_case(s: &str) -> String {
1535 let mut result = String::new();
1536 let mut capitalize_next = true;
1537 for c in s.chars() {
1538 if c == '_' {
1539 capitalize_next = true;
1540 } else if capitalize_next {
1541 result.push(c.to_uppercase().next().unwrap());
1542 capitalize_next = false;
1543 } else {
1544 result.push(c);
1545 }
1546 }
1547 result
1548}
1549
1550pub fn transpile(input: &Path, output: &Path) -> Result<()> {
1552 let schema = ZapSchema::from_file(input)?;
1553 schema.write(output)
1554}
1555
1556pub fn transpile_str(source: &str, name: &str) -> Result<String> {
1558 let schema = ZapSchema::new(source, name);
1559 schema.compile()
1560}
1561
1562pub fn compile_to_rust(source: &str, name: &str) -> Result<String> {
1564 let schema = ZapSchema::new(source, name);
1565 schema.to_rust()
1566}
1567
1568pub fn capnp_to_zap(source: &str) -> Result<String> {
1572 let schema = ZapSchema::with_format(source, "input.capnp", SchemaFormat::Capnp);
1573 schema.to_zap()
1574}
1575
1576pub fn migrate_capnp_to_zap(input: &Path, output: &Path) -> Result<()> {
1578 let schema = ZapSchema::from_file(input)?;
1579 let zap_source = schema.to_zap()?;
1580 std::fs::write(output, zap_source)
1581 .map_err(|e| Error::Config(format!("failed to write schema: {}", e)))?;
1582 Ok(())
1583}
1584
1585#[cfg(test)]
1586mod tests {
1587 use super::*;
1588
1589 #[test]
1590 fn test_simple_struct() {
1591 let source = r#"
1593struct Person
1594 name Text
1595 age UInt32
1596 email Text
1597"#;
1598 let result = transpile_str(source, "test.zap").unwrap();
1599 assert!(result.contains("struct Person"));
1600 assert!(result.contains("name @0 :Text"));
1601 assert!(result.contains("age @1 :UInt32"));
1602 assert!(result.contains("email @2 :Text"));
1603 }
1604
1605 #[test]
1606 fn test_simple_struct_legacy_syntax() {
1607 let source = r#"
1609struct Person
1610 name :Text
1611 age :UInt32
1612 email :Text
1613"#;
1614 let result = transpile_str(source, "test.zap").unwrap();
1615 assert!(result.contains("struct Person"));
1616 assert!(result.contains("name @0 :Text"));
1617 assert!(result.contains("age @1 :UInt32"));
1618 assert!(result.contains("email @2 :Text"));
1619 }
1620
1621 #[test]
1622 fn test_enum() {
1623 let source = r#"
1624enum Status
1625 pending
1626 active
1627 completed
1628 failed
1629"#;
1630 let result = transpile_str(source, "test.zap").unwrap();
1631 assert!(result.contains("enum Status"));
1632 assert!(result.contains("pending @0"));
1633 assert!(result.contains("active @1"));
1634 assert!(result.contains("completed @2"));
1635 assert!(result.contains("failed @3"));
1636 }
1637
1638 #[test]
1639 fn test_interface() {
1640 let source = r#"
1642interface Greeter
1643 sayHello (name Text) -> (greeting Text)
1644 sayGoodbye (name Text) -> ()
1645"#;
1646 let result = transpile_str(source, "test.zap").unwrap();
1647 assert!(result.contains("interface Greeter"));
1648 assert!(result.contains("sayHello @0"));
1649 assert!(result.contains("sayGoodbye @1"));
1650 }
1651
1652 #[test]
1653 fn test_interface_legacy_syntax() {
1654 let source = r#"
1656interface Greeter
1657 sayHello (name :Text) -> (greeting :Text)
1658 sayGoodbye (name :Text) -> ()
1659"#;
1660 let result = transpile_str(source, "test.zap").unwrap();
1661 assert!(result.contains("interface Greeter"));
1662 assert!(result.contains("sayHello @0"));
1663 assert!(result.contains("sayGoodbye @1"));
1664 }
1665
1666 #[test]
1667 fn test_struct_with_defaults() {
1668 let source = r#"
1670struct Config
1671 host Text = "localhost"
1672 port UInt16 = 9999
1673 enabled Bool = true
1674"#;
1675 let result = transpile_str(source, "test.zap").unwrap();
1676 assert!(result.contains("host @0 :Text = \"localhost\""));
1677 assert!(result.contains("port @1 :UInt16 = 9999"));
1678 assert!(result.contains("enabled @2 :Bool = true"));
1679 }
1680
1681 #[test]
1682 fn test_comments_preserved() {
1683 let source = r#"
1684# This is a comment
1685struct Foo
1686 bar Text
1687"#;
1688 let result = transpile_str(source, "test.zap").unwrap();
1689 assert!(result.contains("# This is a comment"));
1690 }
1691
1692 #[test]
1693 fn test_using_import() {
1694 let source = r#"
1695using import "other.zap"
1696using Foo = import "foo.zap"
1697
1698struct Bar
1699 x Int32
1700"#;
1701 let result = transpile_str(source, "test.zap").unwrap();
1702 assert!(result.contains("using import \"other.zap\""));
1703 assert!(result.contains("using Foo = import \"foo.zap\""));
1704 }
1705
1706 #[test]
1707 fn test_interface_extends() {
1708 let source = r#"
1709interface Child extends Parent
1710 childMethod () -> ()
1711"#;
1712 let result = transpile_str(source, "test.zap").unwrap();
1713 assert!(result.contains("interface Child extends(Parent)"));
1714 }
1715
1716 #[test]
1717 fn test_const() {
1718 let source = r#"
1719const version :Text = "1.0.0"
1720const maxSize :UInt32 = 1024
1721"#;
1722 let result = transpile_str(source, "test.zap").unwrap();
1723 assert!(result.contains("const version :Text = \"1.0.0\""));
1724 assert!(result.contains("const maxSize :UInt32 = 1024"));
1725 }
1726
1727 #[test]
1728 fn test_rust_codegen() {
1729 let source = r#"
1730struct Person
1731 name Text
1732 age UInt32
1733
1734enum Status
1735 pending
1736 active
1737"#;
1738 let result = compile_to_rust(source, "test.zap").unwrap();
1739 assert!(result.contains("pub struct Person"));
1740 assert!(result.contains("pub name: String"));
1741 assert!(result.contains("pub age: u32"));
1742 assert!(result.contains("pub enum Status"));
1743 assert!(result.contains("Pending"));
1744 assert!(result.contains("Active"));
1745 }
1746
1747 #[test]
1748 fn test_complex_types() {
1749 let source = r#"
1751struct Order
1752 items List(Item)
1753 quantities List(Int32)
1754 tags List(Text)
1755
1756struct Item
1757 name Text
1758 price Float64
1759"#;
1760 let result = transpile_str(source, "test.zap").unwrap();
1761 assert!(result.contains("items @0 :List(Item)"));
1762 assert!(result.contains("quantities @1 :List(Int32)"));
1763 assert!(result.contains("tags @2 :List(Text)"));
1764 }
1765
1766 #[test]
1767 fn test_interface_with_complex_params() {
1768 let source = r#"
1770interface Calculator
1771 sum (numbers List(Float64)) -> (total Float64)
1772 average (values List(Float64)) -> (avg Float64)
1773"#;
1774 let result = transpile_str(source, "test.zap").unwrap();
1775 assert!(result.contains("sum @0"));
1777 assert!(result.contains("numbers :List(Float64)"));
1778 assert!(result.contains("total :Float64"));
1779 assert!(result.contains("average @1"));
1780 assert!(result.contains("values :List(Float64)"));
1781 assert!(result.contains("avg :Float64"));
1782 }
1783
1784 #[test]
1787 fn test_capnp_struct() {
1788 let source = r#"
1789@0x9eb32e19f86ee174;
1790
1791struct Person @0xabcd1234 {
1792 name @0 :Text;
1793 age @1 :UInt32;
1794 email @2 :Text;
1795}
1796"#;
1797 let schema = ZapSchema::new(source, "test.capnp");
1798 assert_eq!(schema.format, SchemaFormat::Capnp);
1799 let result = schema.compile().unwrap();
1800 assert!(result.contains("struct Person"));
1801 assert!(result.contains("name @0 :Text"));
1802 assert!(result.contains("age @1 :UInt32"));
1803 assert!(result.contains("email @2 :Text"));
1804 }
1805
1806 #[test]
1807 fn test_capnp_enum() {
1808 let source = r#"
1809@0x9eb32e19f86ee174;
1810
1811enum Status @0x1234 {
1812 pending @0;
1813 active @1;
1814 completed @2;
1815}
1816"#;
1817 let schema = ZapSchema::new(source, "test.capnp");
1818 assert_eq!(schema.format, SchemaFormat::Capnp);
1819 let result = schema.compile().unwrap();
1820 assert!(result.contains("enum Status"));
1821 assert!(result.contains("pending @0"));
1822 assert!(result.contains("active @1"));
1823 assert!(result.contains("completed @2"));
1824 }
1825
1826 #[test]
1827 fn test_capnp_interface() {
1828 let source = r#"
1829@0x9eb32e19f86ee174;
1830
1831interface Greeter @0xabcd {
1832 sayHello @0 (name :Text) -> (greeting :Text);
1833 sayGoodbye @1 (name :Text) -> ();
1834}
1835"#;
1836 let schema = ZapSchema::new(source, "test.capnp");
1837 assert_eq!(schema.format, SchemaFormat::Capnp);
1838 let result = schema.compile().unwrap();
1839 assert!(result.contains("interface Greeter"));
1840 assert!(result.contains("sayHello @0"));
1841 assert!(result.contains("sayGoodbye @1"));
1842 }
1843
1844 #[test]
1845 fn test_capnp_interface_extends() {
1846 let source = r#"
1847@0x9eb32e19f86ee174;
1848
1849interface Child extends(Parent) @0xabcd {
1850 childMethod @0 () -> ();
1851}
1852"#;
1853 let schema = ZapSchema::new(source, "test.capnp");
1854 let result = schema.compile().unwrap();
1855 assert!(result.contains("interface Child extends(Parent)"));
1856 }
1857
1858 #[test]
1859 fn test_capnp_nested_types() {
1860 let source = r#"
1861@0x9eb32e19f86ee174;
1862
1863struct Outer @0x1234 {
1864 value @0 :Text;
1865
1866 struct Inner @0x5678 {
1867 x @0 :Int32;
1868 }
1869
1870 enum InnerEnum @0x9abc {
1871 a @0;
1872 b @1;
1873 }
1874}
1875"#;
1876 let schema = ZapSchema::new(source, "test.capnp");
1877 let result = schema.compile().unwrap();
1878 assert!(result.contains("struct Outer"));
1879 assert!(result.contains("struct Inner"));
1880 assert!(result.contains("enum InnerEnum"));
1881 }
1882
1883 #[test]
1884 fn test_format_detection_by_extension() {
1885 let zap_schema = ZapSchema::new("struct Foo { }", "test.zap");
1886 assert_eq!(zap_schema.format, SchemaFormat::Zap);
1887
1888 let capnp_schema = ZapSchema::new("struct Foo { }", "test.capnp");
1889 assert_eq!(capnp_schema.format, SchemaFormat::Capnp);
1890 }
1891
1892 #[test]
1893 fn test_format_detection_by_content() {
1894 let source = "@0x1234; struct Foo { bar @0; }";
1896 let schema = ZapSchema::new(source, "test.schema");
1897 assert_eq!(schema.format, SchemaFormat::Capnp);
1898
1899 let source = "struct Foo\n bar Text";
1901 let schema = ZapSchema::new(source, "test.schema");
1902 assert_eq!(schema.format, SchemaFormat::Zap);
1903
1904 let source = "struct Foo\n bar :Text";
1906 let schema = ZapSchema::new(source, "test.schema");
1907 assert_eq!(schema.format, SchemaFormat::Zap);
1908 }
1909
1910 #[test]
1911 fn test_explicit_format() {
1912 let source = "struct Foo { bar @0 :Text; }";
1913 let schema = ZapSchema::with_format(source, "test.txt", SchemaFormat::Zap);
1914 assert_eq!(schema.format, SchemaFormat::Zap);
1915 }
1916
1917 #[test]
1918 fn test_capnp_to_zap_conversion() {
1919 let capnp_source = r#"
1920@0x9eb32e19f86ee174;
1921
1922struct Person @0xabcd1234 {
1923 name @0 :Text;
1924 age @1 :UInt32;
1925 email @2 :Text;
1926}
1927
1928enum Status @0x1234 {
1929 pending @0;
1930 active @1;
1931 completed @2;
1932}
1933
1934interface Greeter @0xabcd {
1935 sayHello @0 (name :Text) -> (greeting :Text);
1936 sayGoodbye @1 () -> ();
1937}
1938"#;
1939 let result = capnp_to_zap(capnp_source).unwrap();
1940
1941 assert!(result.contains("struct Person"));
1943 assert!(result.contains(" name :Text"));
1944 assert!(result.contains(" age :UInt32"));
1945 assert!(result.contains(" email :Text"));
1946
1947 assert!(result.contains("enum Status"));
1949 assert!(result.contains(" pending"));
1950 assert!(result.contains(" active"));
1951 assert!(result.contains(" completed"));
1952
1953 assert!(result.contains("interface Greeter"));
1955 assert!(result.contains("sayHello"));
1956 assert!(result.contains("sayGoodbye"));
1957
1958 assert!(!result.contains("@0"));
1960 assert!(!result.contains("@1"));
1961 assert!(!result.contains("@2"));
1962 }
1963
1964 #[test]
1969 fn test_all_primitive_types() {
1970 let source = r#"
1971struct AllTypes
1972 int8Val Int8
1973 int16Val Int16
1974 int32Val Int32
1975 int64Val Int64
1976 uint8Val UInt8
1977 uint16Val UInt16
1978 uint32Val UInt32
1979 uint64Val UInt64
1980 float32Val Float32
1981 float64Val Float64
1982 boolVal Bool
1983 textVal Text
1984 dataVal Data
1985 voidVal Void
1986"#;
1987 let result = transpile_str(source, "test.zap").unwrap();
1988 assert!(result.contains("int8Val @0 :Int8"));
1989 assert!(result.contains("int16Val @1 :Int16"));
1990 assert!(result.contains("int32Val @2 :Int32"));
1991 assert!(result.contains("int64Val @3 :Int64"));
1992 assert!(result.contains("uint8Val @4 :UInt8"));
1993 assert!(result.contains("uint16Val @5 :UInt16"));
1994 assert!(result.contains("uint32Val @6 :UInt32"));
1995 assert!(result.contains("uint64Val @7 :UInt64"));
1996 assert!(result.contains("float32Val @8 :Float32"));
1997 assert!(result.contains("float64Val @9 :Float64"));
1998 assert!(result.contains("boolVal @10 :Bool"));
1999 assert!(result.contains("textVal @11 :Text"));
2000 assert!(result.contains("dataVal @12 :Data"));
2001 assert!(result.contains("voidVal @13 :Void"));
2002 }
2003
2004 #[test]
2005 fn test_nested_structs_deep() {
2006 let source = r#"
2007struct Level1
2008 name Text
2009 level2 Level2
2010
2011 struct Level2
2012 value Int32
2013 level3 Level3
2014
2015 struct Level3
2016 data Data
2017 count UInt64
2018"#;
2019 let result = transpile_str(source, "test.zap").unwrap();
2020 assert!(result.contains("struct Level1"));
2021 assert!(result.contains("struct Level2"));
2022 assert!(result.contains("struct Level3"));
2023 assert!(result.contains("name @0 :Text"));
2024 assert!(result.contains("level2 @1 :Level2"));
2025 }
2026
2027 #[test]
2028 fn test_union_in_struct() {
2029 let source = r#"
2030struct Result
2031 id Text
2032 union
2033 success Data
2034 error Text
2035 pending Void
2036"#;
2037 let result = transpile_str(source, "test.zap").unwrap();
2038 assert!(result.contains("struct Result"));
2039 assert!(result.contains("id @0 :Text"));
2040 assert!(result.contains("union {"));
2041 assert!(result.contains("success"));
2042 assert!(result.contains("error"));
2043 assert!(result.contains("pending"));
2044 }
2045
2046 #[test]
2047 fn test_named_union() {
2048 let source = r#"
2049struct Shape
2050 name Text
2051 union geometry
2052 circle Circle
2053 rectangle Rectangle
2054"#;
2055 let result = transpile_str(source, "test.zap").unwrap();
2056 assert!(result.contains("struct Shape"));
2057 assert!(result.contains("geometry :union"));
2058 }
2059
2060 #[test]
2061 fn test_multiple_interfaces() {
2062 let source = r#"
2063interface Reader
2064 read (offset UInt64, size UInt32) -> (data Data)
2065 size () -> (bytes UInt64)
2066
2067interface Writer
2068 write (data Data) -> (written UInt64)
2069 flush () -> ()
2070 close () -> ()
2071
2072interface ReadWriter extends Reader
2073 write (data Data) -> (written UInt64)
2074"#;
2075 let result = transpile_str(source, "test.zap").unwrap();
2076 assert!(result.contains("interface Reader"));
2077 assert!(result.contains("interface Writer"));
2078 assert!(result.contains("interface ReadWriter extends(Reader)"));
2079 assert!(result.contains("read @0"));
2080 assert!(result.contains("write @0"));
2081 }
2082
2083 #[test]
2084 fn test_interface_with_nested_types() {
2085 let source = r#"
2086interface Database
2087 query (sql Text) -> (results QueryResult)
2088 execute (sql Text) -> (affected UInt64)
2089
2090 struct QueryResult
2091 columns List(Text)
2092 rows List(Row)
2093
2094 struct Row
2095 values List(Text)
2096
2097 enum ErrorCode
2098 notFound
2099 permissionDenied
2100 timeout
2101 internal
2102"#;
2103 let result = transpile_str(source, "test.zap").unwrap();
2104 assert!(result.contains("interface Database"));
2105 assert!(result.contains("struct QueryResult"));
2106 assert!(result.contains("struct Row"));
2107 assert!(result.contains("enum ErrorCode"));
2108 }
2109
2110 #[test]
2111 fn test_list_of_lists() {
2112 let source = r#"
2113struct Matrix
2114 rows List(List(Float64))
2115 labels List(Text)
2116"#;
2117 let result = transpile_str(source, "test.zap").unwrap();
2118 assert!(result.contains("rows @0 :List(List(Float64))"));
2119 assert!(result.contains("labels @1 :List(Text)"));
2120 }
2121
2122 #[test]
2123 fn test_default_values_various_types() {
2124 let source = r#"
2125struct Config
2126 host Text = "localhost"
2127 port UInt16 = 8080
2128 maxConnections UInt32 = 100
2129 timeout Float64 = 30.0
2130 enabled Bool = true
2131 retryCount Int32 = 3
2132"#;
2133 let result = transpile_str(source, "test.zap").unwrap();
2134 assert!(result.contains("host @0 :Text = \"localhost\""));
2135 assert!(result.contains("port @1 :UInt16 = 8080"));
2136 assert!(result.contains("maxConnections @2 :UInt32 = 100"));
2137 assert!(result.contains("timeout @3 :Float64 = 30.0"));
2138 assert!(result.contains("enabled @4 :Bool = true"));
2139 assert!(result.contains("retryCount @5 :Int32 = 3"));
2140 }
2141
2142 #[test]
2143 fn test_enum_many_variants() {
2144 let source = r#"
2145enum HttpStatus
2146 continue100
2147 ok200
2148 created201
2149 accepted202
2150 noContent204
2151 movedPermanently301
2152 found302
2153 notModified304
2154 badRequest400
2155 unauthorized401
2156 forbidden403
2157 notFound404
2158 methodNotAllowed405
2159 internalServerError500
2160 notImplemented501
2161 badGateway502
2162 serviceUnavailable503
2163"#;
2164 let result = transpile_str(source, "test.zap").unwrap();
2165 assert!(result.contains("enum HttpStatus"));
2166 assert!(result.contains("continue100 @0"));
2167 assert!(result.contains("ok200 @1"));
2168 assert!(result.contains("serviceUnavailable503 @16"));
2169 }
2170
2171 #[test]
2172 fn test_method_multiple_params() {
2173 let source = r#"
2174interface DataService
2175 search (query Text, limit UInt32, offset UInt64, filters List(Text)) -> (results List(Data), total UInt64)
2176 aggregate (keys List(Text), operation Text, groupBy Text) -> (result Data)
2177"#;
2178 let result = transpile_str(source, "test.zap").unwrap();
2179 assert!(result.contains("search @0"));
2180 assert!(result.contains("query :Text"));
2181 assert!(result.contains("limit :UInt32"));
2182 assert!(result.contains("offset :UInt64"));
2183 assert!(result.contains("filters :List(Text)"));
2184 assert!(result.contains("results :List(Data)"));
2185 assert!(result.contains("total :UInt64"));
2186 }
2187
2188 #[test]
2189 fn test_empty_interface() {
2190 let source = r#"
2191interface Empty
2192"#;
2193 let result = transpile_str(source, "test.zap").unwrap();
2194 assert!(result.contains("interface Empty"));
2195 }
2196
2197 #[test]
2198 fn test_empty_struct() {
2199 let source = r#"
2200struct Empty
2201"#;
2202 let result = transpile_str(source, "test.zap").unwrap();
2203 assert!(result.contains("struct Empty"));
2204 }
2205
2206 #[test]
2207 fn test_comments_multiline() {
2208 let source = r#"
2209# This is the first comment
2210# This is the second comment
2211# This is the third comment
2212struct Documented
2213 # Field comment
2214 value Text
2215"#;
2216 let result = transpile_str(source, "test.zap").unwrap();
2217 assert!(result.contains("# This is the first comment"));
2218 assert!(result.contains("# This is the second comment"));
2219 assert!(result.contains("# This is the third comment"));
2220 }
2221
2222 #[test]
2223 fn test_mixed_syntax_colon_and_space() {
2224 let source = r#"
2226struct MixedSyntax
2227 fieldWithColon :Text
2228 fieldWithSpace Int32
2229 anotherColon :UInt64
2230 anotherSpace Bool
2231"#;
2232 let result = transpile_str(source, "test.zap").unwrap();
2233 assert!(result.contains("fieldWithColon @0 :Text"));
2234 assert!(result.contains("fieldWithSpace @1 :Int32"));
2235 assert!(result.contains("anotherColon @2 :UInt64"));
2236 assert!(result.contains("anotherSpace @3 :Bool"));
2237 }
2238
2239 #[test]
2240 fn test_complex_nested_lists() {
2241 let source = r#"
2242struct ComplexData
2243 matrix List(List(List(Float64)))
2244 records List(Record)
2245 tags List(List(Text))
2246
2247struct Record
2248 id UInt64
2249 data Data
2250"#;
2251 let result = transpile_str(source, "test.zap").unwrap();
2252 assert!(result.contains("matrix @0 :List(List(List(Float64)))"));
2253 assert!(result.contains("records @1 :List(Record)"));
2254 assert!(result.contains("tags @2 :List(List(Text))"));
2255 }
2256
2257 #[test]
2258 fn test_interface_method_no_params_no_results() {
2259 let source = r#"
2260interface Simple
2261 ping () -> ()
2262 noop () -> ()
2263"#;
2264 let result = transpile_str(source, "test.zap").unwrap();
2265 assert!(result.contains("ping @0 () -> ()"));
2266 assert!(result.contains("noop @1 () -> ()"));
2267 }
2268
2269 #[test]
2270 fn test_interface_method_only_results() {
2271 let source = r#"
2272interface Generator
2273 generate () -> (value UInt64)
2274 timestamp () -> (seconds Int64, nanos UInt32)
2275"#;
2276 let result = transpile_str(source, "test.zap").unwrap();
2277 assert!(result.contains("generate @0 () -> (value :UInt64)"));
2278 assert!(result.contains("timestamp @1 () -> (seconds :Int64, nanos :UInt32)"));
2279 }
2280
2281 #[test]
2282 fn test_stable_ids_deterministic() {
2283 let source = r#"
2284struct TestStruct
2285 field Text
2286"#;
2287 let result1 = transpile_str(source, "test.zap").unwrap();
2288 let result2 = transpile_str(source, "test.zap").unwrap();
2289 assert_eq!(result1, result2);
2291 }
2292
2293 #[test]
2294 fn test_different_paths_different_ids() {
2295 let source = r#"
2296struct TestStruct
2297 field Text
2298"#;
2299 let result1 = transpile_str(source, "path1.zap").unwrap();
2300 let result2 = transpile_str(source, "path2.zap").unwrap();
2301 assert_ne!(result1, result2);
2303 }
2304
2305 #[test]
2306 fn test_rust_codegen_interface() {
2307 let source = r#"
2308interface Calculator
2309 add (a Float64, b Float64) -> (result Float64)
2310 multiply (a Float64, b Float64) -> (result Float64)
2311"#;
2312 let result = compile_to_rust(source, "test.zap").unwrap();
2313 assert!(result.contains("pub trait Calculator"));
2314 assert!(result.contains("async fn add"));
2315 assert!(result.contains("async fn multiply"));
2316 assert!(result.contains("a: f64"));
2317 assert!(result.contains("b: f64"));
2318 }
2319
2320 #[test]
2321 fn test_rust_codegen_union() {
2322 let source = r#"
2323struct Response
2324 union
2325 success Data
2326 error Text
2327"#;
2328 let result = compile_to_rust(source, "test.zap").unwrap();
2329 assert!(result.contains("pub struct Response"));
2330 assert!(result.contains("pub enum"));
2331 assert!(result.contains("Success(Vec<u8>)"));
2332 assert!(result.contains("Error(String)"));
2333 }
2334
2335 #[test]
2336 fn test_full_mcp_like_schema() {
2337 let source = r#"
2339struct Tool
2340 name Text
2341 description Text
2342 schema Data
2343 annotations Metadata
2344
2345struct Metadata
2346 entries List(Entry)
2347
2348 struct Entry
2349 key Text
2350 value Text
2351
2352struct ToolCall
2353 id Text
2354 name Text
2355 args Data
2356
2357struct ToolResult
2358 id Text
2359 union
2360 content Data
2361 error Text
2362
2363interface ToolService
2364 listTools () -> (tools List(Tool))
2365 callTool (call ToolCall) -> (result ToolResult)
2366
2367enum LogLevel
2368 debug
2369 info
2370 warn
2371 error
2372"#;
2373 let result = transpile_str(source, "test.zap").unwrap();
2374 assert!(result.contains("struct Tool"));
2375 assert!(result.contains("struct Metadata"));
2376 assert!(result.contains("struct Entry"));
2377 assert!(result.contains("struct ToolCall"));
2378 assert!(result.contains("struct ToolResult"));
2379 assert!(result.contains("interface ToolService"));
2380 assert!(result.contains("enum LogLevel"));
2381 assert!(result.contains("listTools @0"));
2382 assert!(result.contains("callTool @1"));
2383 }
2384
2385 #[test]
2386 fn test_whitespace_variations() {
2387 let source1 = "struct Foo\n bar Text";
2389 let source2 = "struct Foo\n bar Text\n";
2390 let source3 = "struct Foo\n bar Text\n\n";
2391 let source4 = "\nstruct Foo\n bar Text";
2392
2393 for (i, source) in [source1, source2, source3, source4].iter().enumerate() {
2394 let result = transpile_str(source, "test.zap");
2395 assert!(result.is_ok(), "Failed on source variant {}", i);
2396 let output = result.unwrap();
2397 assert!(output.contains("bar @0 :Text"), "Missing field in variant {}", i);
2398 }
2399 }
2400
2401 #[test]
2402 fn test_tab_indentation() {
2403 let source = "struct Foo\n\tbar Text\n\tbaz Int32";
2404 let result = transpile_str(source, "test.zap").unwrap();
2405 assert!(result.contains("bar @0 :Text"));
2406 assert!(result.contains("baz @1 :Int32"));
2407 }
2408
2409 #[test]
2410 fn test_special_characters_in_strings() {
2411 let source = r#"
2412struct Config
2413 path Text = "/usr/local/bin"
2414 pattern Text = ".*\\.txt"
2415 message Text = "Hello, World!"
2416"#;
2417 let result = transpile_str(source, "test.zap").unwrap();
2418 assert!(result.contains("\"/usr/local/bin\""));
2419 assert!(result.contains("\".*\\\\.txt\""));
2420 assert!(result.contains("\"Hello, World!\""));
2421 }
2422
2423 #[test]
2424 fn test_camel_case_names() {
2425 let source = r#"
2426struct MyComplexStructName
2427 myFieldName Text
2428 anotherFieldWithLongName UInt64
2429 yetAnotherOne Bool
2430
2431interface MyServiceInterface
2432 myMethodName (myParam Text) -> (myResult Data)
2433"#;
2434 let result = transpile_str(source, "test.zap").unwrap();
2435 assert!(result.contains("struct MyComplexStructName"));
2436 assert!(result.contains("myFieldName @0 :Text"));
2437 assert!(result.contains("interface MyServiceInterface"));
2438 assert!(result.contains("myMethodName @0"));
2439 }
2440
2441 #[test]
2442 fn test_snake_case_to_rust() {
2443 let source = r#"
2444struct TestStruct
2445 myFieldName Text
2446 anotherField Int32
2447"#;
2448 let result = compile_to_rust(source, "test.zap").unwrap();
2449 assert!(result.contains("my_field_name"));
2451 assert!(result.contains("another_field"));
2452 }
2453
2454 #[test]
2459 fn test_single_field_struct() {
2460 let source = r#"
2461struct Single
2462 value Text
2463"#;
2464 let result = transpile_str(source, "test.zap").unwrap();
2465 assert!(result.contains("struct Single"));
2466 assert!(result.contains("value @0 :Text"));
2467 }
2468
2469 #[test]
2470 fn test_single_variant_enum() {
2471 let source = r#"
2472enum Single
2473 only
2474"#;
2475 let result = transpile_str(source, "test.zap").unwrap();
2476 assert!(result.contains("enum Single"));
2477 assert!(result.contains("only @0"));
2478 }
2479
2480 #[test]
2481 fn test_single_method_interface() {
2482 let source = r#"
2483interface Single
2484 method () -> ()
2485"#;
2486 let result = transpile_str(source, "test.zap").unwrap();
2487 assert!(result.contains("interface Single"));
2488 assert!(result.contains("method @0"));
2489 }
2490
2491 #[test]
2492 fn test_numeric_looking_names() {
2493 let source = r#"
2494struct Data123
2495 field456 Text
2496 x789 Int32
2497"#;
2498 let result = transpile_str(source, "test.zap").unwrap();
2499 assert!(result.contains("struct Data123"));
2500 assert!(result.contains("field456 @0 :Text"));
2501 assert!(result.contains("x789 @1 :Int32"));
2502 }
2503
2504 #[test]
2505 fn test_underscore_names() {
2506 let source = r#"
2507struct Under_Score
2508 field_name Text
2509 another_field Int32
2510"#;
2511 let result = transpile_str(source, "test.zap").unwrap();
2512 assert!(result.contains("struct Under_Score"));
2513 assert!(result.contains("field_name @0 :Text"));
2514 }
2515
2516 #[test]
2517 fn test_large_ordinals() {
2518 let source = r#"
2520struct ManyFields
2521 f0 Text
2522 f1 Text
2523 f2 Text
2524 f3 Text
2525 f4 Text
2526 f5 Text
2527 f6 Text
2528 f7 Text
2529 f8 Text
2530 f9 Text
2531 f10 Text
2532 f11 Text
2533 f12 Text
2534 f13 Text
2535 f14 Text
2536 f15 Text
2537 f16 Text
2538 f17 Text
2539 f18 Text
2540 f19 Text
2541"#;
2542 let result = transpile_str(source, "test.zap").unwrap();
2543 assert!(result.contains("f0 @0 :Text"));
2544 assert!(result.contains("f10 @10 :Text"));
2545 assert!(result.contains("f19 @19 :Text"));
2546 }
2547}