ryo_app/spec_dsl/
parser.rs1use std::path::Path;
4use thiserror::Error;
5
6use super::types::DomainSpec;
7
8#[derive(Debug, Error)]
10pub enum SpecParseError {
11 #[error("Failed to read file: {0}")]
12 IoError(#[from] std::io::Error),
13
14 #[error("Failed to parse YAML: {0}")]
15 YamlError(#[from] serde_yaml::Error),
16
17 #[error("Failed to parse TOML: {0}")]
18 TomlError(#[from] toml::de::Error),
19
20 #[error("Unsupported file format: {0}")]
21 UnsupportedFormat(String),
22}
23
24pub fn parse_spec(content: &str) -> Result<DomainSpec, SpecParseError> {
26 serde_yaml::from_str(content).map_err(SpecParseError::from)
27}
28
29pub fn parse_spec_toml(content: &str) -> Result<DomainSpec, SpecParseError> {
31 toml::from_str(content).map_err(SpecParseError::from)
32}
33
34pub fn parse_spec_from_file(path: impl AsRef<Path>) -> Result<DomainSpec, SpecParseError> {
36 let path = path.as_ref();
37 let content = std::fs::read_to_string(path)?;
38
39 let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
40
41 match ext {
42 "yaml" | "yml" => parse_spec(&content),
43 "toml" => parse_spec_toml(&content),
44 _ => Err(SpecParseError::UnsupportedFormat(ext.to_string())),
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 #[test]
53 fn test_parse_minimal_spec() {
54 let yaml = r#"
55project:
56 name: "test"
57
58modules: []
59entities: []
60"#;
61
62 let spec = parse_spec(yaml).unwrap();
63 assert_eq!(spec.project.name, "test");
64 assert!(spec.modules.is_empty());
65 assert!(spec.entities.is_empty());
66 }
67
68 #[test]
69 fn test_parse_with_entities() {
70 let yaml = r#"
71project:
72 name: "ecommerce"
73
74entities:
75 - name: User
76 module: user
77 fields:
78 - name: id
79 type: UserId
80 - name: name
81 type: String
82 derives: [Debug, Clone]
83"#;
84
85 let spec = parse_spec(yaml).unwrap();
86 assert_eq!(spec.entities.len(), 1);
87
88 let user = &spec.entities[0];
89 assert_eq!(user.name, "User");
90 assert_eq!(user.fields.len(), 2);
91 assert_eq!(user.derives, vec!["Debug", "Clone"]);
92 }
93
94 #[test]
95 fn test_parse_with_modules() {
96 let yaml = r#"
97project:
98 name: "test"
99
100modules:
101 - name: lib
102 is_pub: true
103 children:
104 - name: user
105 is_pub: true
106 - name: common
107 is_pub: true
108 children:
109 - name: types
110 is_pub: true
111"#;
112
113 let spec = parse_spec(yaml).unwrap();
114 assert_eq!(spec.modules.len(), 1);
115
116 let lib = &spec.modules[0];
117 assert_eq!(lib.name, "lib");
118 assert_eq!(lib.children.len(), 2);
119
120 let flattened = lib.flatten("");
122 assert_eq!(flattened.len(), 4);
123 }
124
125 #[test]
126 fn test_parse_with_implementations() {
127 let yaml = r#"
128project:
129 name: "test"
130
131implementations:
132 - target: UserId
133 methods:
134 - name: new
135 self_param: null
136 return_type: Self
137 body: "Self(uuid::Uuid::new_v4())"
138 is_pub: true
139 - name: as_str
140 self_param: ref
141 return_type: "&str"
142 body: "self.0.as_str()"
143 is_pub: true
144"#;
145
146 let spec = parse_spec(yaml).unwrap();
147 assert_eq!(spec.implementations.len(), 1);
148
149 let impl_spec = &spec.implementations[0];
150 assert_eq!(impl_spec.target, "UserId");
151 assert_eq!(impl_spec.methods.len(), 2);
152
153 let new_method = &impl_spec.methods[0];
154 assert_eq!(new_method.name, "new");
155 assert!(new_method.self_param.is_none());
156 assert!(new_method.is_pub);
157 }
158
159 #[test]
160 fn test_parse_with_refactors() {
161 let yaml = r#"
162project:
163 name: "test"
164
165refactors:
166 - kind: AddBuilderPattern
167 targets:
168 - User
169 - Product
170
171 - kind: AddFromInto
172 pairs:
173 - [UserId, "uuid::Uuid"]
174 - [ProductId, "uuid::Uuid"]
175
176 - kind: OrganizeImports
177 target_modules: all
178"#;
179
180 let spec = parse_spec(yaml).unwrap();
181 assert_eq!(spec.refactors.len(), 3);
182 }
183
184 #[test]
185 fn test_parse_with_verification() {
186 let yaml = r#"
187project:
188 name: "test"
189
190verification:
191 phase1:
192 - kind: ModuleExists
193 paths: [user, product]
194
195 phase2:
196 - kind: TypeExists
197 types: [UserId, ProductId]
198
199 final:
200 - kind: Compiles
201"#;
202
203 let spec = parse_spec(yaml).unwrap();
204 let verification = spec.verification.as_ref().unwrap();
205
206 assert_eq!(verification.phase1.len(), 1);
207 assert_eq!(verification.phase2.len(), 1);
208 assert_eq!(verification.final_.len(), 1);
209 }
210}