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