A better Rust ATProto crate
at main 11 kB view raw
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}