1use super::sources::{
2 AtProtoSource, GitSource, HttpSource, JsonFileSource, LocalSource, SlicesSource, Source,
3 SourceType,
4};
5use miette::{Result, miette};
6use std::path::PathBuf;
7
8#[derive(Debug, Clone)]
9pub struct Config {
10 pub output: OutputConfig,
11 pub sources: Vec<Source>,
12}
13
14#[derive(Debug, Clone)]
15pub struct OutputConfig {
16 pub lexicons_dir: PathBuf,
17 pub codegen_dir: PathBuf,
18 pub root_module: Option<String>,
19 pub cargo_toml_path: Option<PathBuf>,
20}
21
22impl Config {
23 pub fn from_kdl(text: &str) -> Result<Self> {
24 let doc = text
25 .parse::<kdl::KdlDocument>()
26 .map_err(|e| miette!("Failed to parse KDL: {}", e))?;
27
28 let mut output: Option<OutputConfig> = None;
29 let mut sources = Vec::new();
30
31 for node in doc.nodes() {
32 match node.name().value() {
33 "output" => {
34 if output.is_some() {
35 return Err(miette!("Multiple output blocks found"));
36 }
37 output = Some(parse_output(node)?);
38 }
39 "source" => {
40 sources.push(parse_source(node)?);
41 }
42 other => {
43 return Err(miette!("Unknown config node: {}", other));
44 }
45 }
46 }
47
48 let output = output.ok_or_else(|| miette!("Missing output block"))?;
49
50 Ok(Config { output, sources })
51 }
52}
53
54fn parse_output(node: &kdl::KdlNode) -> Result<OutputConfig> {
55 let children = node
56 .children()
57 .ok_or_else(|| miette!("output block has no children"))?;
58
59 let mut lexicons_dir: Option<PathBuf> = None;
60 let mut codegen_dir: Option<PathBuf> = None;
61 let mut root_module: Option<String> = None;
62 let mut cargo_toml_path: Option<PathBuf> = None;
63
64 for child in children.nodes() {
65 match child.name().value() {
66 "lexicons" => {
67 let val = child
68 .entries()
69 .get(0)
70 .and_then(|e| e.value().as_string())
71 .ok_or_else(|| miette!("lexicons expects a string value"))?;
72 lexicons_dir = Some(PathBuf::from(val));
73 }
74 "codegen" => {
75 let val = child
76 .entries()
77 .get(0)
78 .and_then(|e| e.value().as_string())
79 .ok_or_else(|| miette!("codegen expects a string value"))?;
80 codegen_dir = Some(PathBuf::from(val));
81 }
82 "root-module" => {
83 let val = child
84 .entries()
85 .get(0)
86 .and_then(|e| e.value().as_string())
87 .ok_or_else(|| miette!("root-module expects a string value"))?;
88 root_module = Some(val.to_string());
89 }
90 "cargo-toml" => {
91 let val = child
92 .entries()
93 .get(0)
94 .and_then(|e| e.value().as_string())
95 .ok_or_else(|| miette!("cargo-toml expects a string value"))?;
96 cargo_toml_path = Some(PathBuf::from(val));
97 }
98 other => {
99 return Err(miette!("Unknown output field: {}", other));
100 }
101 }
102 }
103
104 Ok(OutputConfig {
105 lexicons_dir: lexicons_dir.ok_or_else(|| miette!("Missing lexicons directory"))?,
106 codegen_dir: codegen_dir.ok_or_else(|| miette!("Missing codegen directory"))?,
107 root_module,
108 cargo_toml_path,
109 })
110}
111
112fn parse_source(node: &kdl::KdlNode) -> Result<Source> {
113 let name = node
114 .entries()
115 .get(0)
116 .and_then(|e| e.value().as_string())
117 .ok_or_else(|| miette!("source expects a name as first argument"))?
118 .to_string();
119
120 let type_str = node
121 .get("type")
122 .and_then(|v| v.as_string())
123 .ok_or_else(|| miette!("source {} missing type attribute", name))?;
124
125 let priority = node
126 .get("priority")
127 .and_then(|v| v.as_integer())
128 .map(|i| i as i32);
129
130 let children = node
131 .children()
132 .ok_or_else(|| miette!("source {} has no children", name))?;
133
134 let source_type = match type_str {
135 "atproto" => parse_atproto_source(children)?,
136 "git" => parse_git_source(children)?,
137 "http" => parse_http_source(children)?,
138 "jsonfile" => parse_jsonfile_source(children)?,
139 "local" => parse_local_source(children)?,
140 "slices" => parse_slices_source(children)?,
141 other => return Err(miette!("Unknown source type: {}", other)),
142 };
143
144 Ok(Source {
145 name,
146 source_type,
147 explicit_priority: priority,
148 })
149}
150
151fn parse_atproto_source(children: &kdl::KdlDocument) -> Result<SourceType> {
152 let mut endpoint: Option<String> = None;
153 let mut slice: Option<String> = None;
154
155 for child in children.nodes() {
156 match child.name().value() {
157 "endpoint" => {
158 let val = child
159 .entries()
160 .get(0)
161 .and_then(|e| e.value().as_string())
162 .ok_or_else(|| miette!("endpoint expects a string value"))?;
163 endpoint = Some(val.to_string());
164 }
165 "slice" => {
166 let val = child
167 .entries()
168 .get(0)
169 .and_then(|e| e.value().as_string())
170 .ok_or_else(|| miette!("slice expects a string value"))?;
171 slice = Some(val.to_string());
172 }
173 other => {
174 return Err(miette!("Unknown atproto source field: {}", other));
175 }
176 }
177 }
178
179 Ok(SourceType::AtProto(AtProtoSource {
180 endpoint: endpoint.ok_or_else(|| miette!("Missing endpoint"))?,
181 slice,
182 }))
183}
184
185fn parse_git_source(children: &kdl::KdlDocument) -> Result<SourceType> {
186 let mut repo: Option<String> = None;
187 let mut git_ref: Option<String> = None;
188 let mut pattern: Option<String> = None;
189
190 for child in children.nodes() {
191 match child.name().value() {
192 "repo" => {
193 let val = child
194 .entries()
195 .get(0)
196 .and_then(|e| e.value().as_string())
197 .ok_or_else(|| miette!("repo expects a string value"))?;
198 repo = Some(val.to_string());
199 }
200 "ref" => {
201 let val = child
202 .entries()
203 .get(0)
204 .and_then(|e| e.value().as_string())
205 .ok_or_else(|| miette!("ref expects a string value"))?;
206 git_ref = Some(val.to_string());
207 }
208 "pattern" => {
209 let val = child
210 .entries()
211 .get(0)
212 .and_then(|e| e.value().as_string())
213 .ok_or_else(|| miette!("pattern expects a string value"))?;
214 pattern = Some(val.to_string());
215 }
216 other => {
217 return Err(miette!("Unknown git source field: {}", other));
218 }
219 }
220 }
221
222 Ok(SourceType::Git(GitSource {
223 repo: repo.ok_or_else(|| miette!("Missing repo"))?,
224 git_ref,
225 pattern: pattern.unwrap_or_else(|| "**/*.json".to_string()),
226 }))
227}
228
229fn parse_http_source(children: &kdl::KdlDocument) -> Result<SourceType> {
230 let mut url: Option<String> = None;
231
232 for child in children.nodes() {
233 match child.name().value() {
234 "url" => {
235 let val = child
236 .entries()
237 .get(0)
238 .and_then(|e| e.value().as_string())
239 .ok_or_else(|| miette!("url expects a string value"))?;
240 url = Some(val.to_string());
241 }
242 other => {
243 return Err(miette!("Unknown http source field: {}", other));
244 }
245 }
246 }
247
248 Ok(SourceType::Http(HttpSource {
249 url: url.ok_or_else(|| miette!("Missing url"))?,
250 }))
251}
252
253fn parse_jsonfile_source(children: &kdl::KdlDocument) -> Result<SourceType> {
254 let mut path: Option<PathBuf> = None;
255
256 for child in children.nodes() {
257 match child.name().value() {
258 "path" => {
259 let val = child
260 .entries()
261 .get(0)
262 .and_then(|e| e.value().as_string())
263 .ok_or_else(|| miette!("path expects a string value"))?;
264 path = Some(PathBuf::from(val));
265 }
266
267 other => {
268 return Err(miette!("Unknown jsonfile source field: {}", other));
269 }
270 }
271 }
272
273 Ok(SourceType::JsonFile(JsonFileSource {
274 path: path.ok_or_else(|| miette!("Missing path"))?,
275 }))
276}
277
278fn parse_slices_source(children: &kdl::KdlDocument) -> Result<SourceType> {
279 let mut slice: Option<String> = None;
280
281 for child in children.nodes() {
282 match child.name().value() {
283 "slice" => {
284 let val = child
285 .entries()
286 .get(0)
287 .and_then(|e| e.value().as_string())
288 .ok_or_else(|| miette!("slice expects a string value"))?;
289 slice = Some(val.to_string());
290 }
291 other => {
292 return Err(miette!("Unknown slices source field: {}", other));
293 }
294 }
295 }
296
297 Ok(SourceType::Slices(SlicesSource {
298 slice: slice.ok_or_else(|| miette!("Missing slice"))?,
299 }))
300}
301
302fn parse_local_source(children: &kdl::KdlDocument) -> Result<SourceType> {
303 let mut path: Option<PathBuf> = None;
304 let mut pattern: Option<String> = None;
305
306 for child in children.nodes() {
307 match child.name().value() {
308 "path" => {
309 let val = child
310 .entries()
311 .get(0)
312 .and_then(|e| e.value().as_string())
313 .ok_or_else(|| miette!("path expects a string value"))?;
314 path = Some(PathBuf::from(val));
315 }
316 "pattern" => {
317 let val = child
318 .entries()
319 .get(0)
320 .and_then(|e| e.value().as_string())
321 .ok_or_else(|| miette!("pattern expects a string value"))?;
322 pattern = Some(val.to_string());
323 }
324 other => {
325 return Err(miette!("Unknown local source field: {}", other));
326 }
327 }
328 }
329
330 Ok(SourceType::Local(LocalSource {
331 path: path.ok_or_else(|| miette!("Missing path"))?,
332 pattern,
333 }))
334}