1use std::collections::BTreeMap;
10use std::path::PathBuf;
11
12use crate::document::{parse_document, DocumentFormat, ParseError};
13use crate::schema::{EnumType, MeasurandDefinition, ModDefDocument};
14
15pub trait PackageResolver {
18 fn fetch(&self, uri: &str) -> Result<(Vec<u8>, DocumentFormat), ParseError>;
19}
20
21pub struct DirResolver {
23 roots: Vec<PathBuf>,
24}
25
26impl DirResolver {
27 pub fn new<I, P>(roots: I) -> Self
28 where
29 I: IntoIterator<Item = P>,
30 P: Into<PathBuf>,
31 {
32 DirResolver {
33 roots: roots.into_iter().map(Into::into).collect(),
34 }
35 }
36
37 pub fn from_env() -> Self {
39 let roots = std::env::var("MODDEF_PACKAGE_ROOTS").unwrap_or_default();
40 DirResolver::new(roots.split(':').filter(|s| !s.is_empty()))
41 }
42}
43
44impl PackageResolver for DirResolver {
45 fn fetch(&self, uri: &str) -> Result<(Vec<u8>, DocumentFormat), ParseError> {
46 let parts: Vec<&str> = uri.split(':').collect();
47 let [scheme, _ns, name, version] = parts[..] else {
48 return Err(ParseError::new(format!("unsupported import uri: {uri}")));
49 };
50 if scheme != "moddef" {
51 return Err(ParseError::new(format!("unsupported import uri: {uri}")));
52 }
53 let candidates = [
54 (format!("{name}.moddef.yaml"), DocumentFormat::Yaml),
55 (format!("{name}.moddef.json"), DocumentFormat::Json),
56 (format!("{name}.moddef"), DocumentFormat::Binary),
57 ];
58 for root in &self.roots {
59 for (file, format) in &candidates {
60 let path = root.join(name).join(version).join(file);
61 if let Ok(data) = std::fs::read(&path) {
62 return Ok((data, *format));
63 }
64 }
65 }
66 Err(ParseError::new(format!(
67 "import not found under package roots: {uri}"
68 )))
69 }
70}
71
72pub struct ResolvedDocument {
76 pub enums: BTreeMap<String, EnumType>,
77 pub measurands: BTreeMap<String, MeasurandDefinition>,
78 pub imports: BTreeMap<String, ModDefDocument>,
80}
81
82pub fn resolve_imports(
84 doc: &ModDefDocument,
85 resolver: Option<&dyn PackageResolver>,
86) -> Result<ResolvedDocument, ParseError> {
87 let mut out = ResolvedDocument {
88 enums: BTreeMap::new(),
89 measurands: BTreeMap::new(),
90 imports: BTreeMap::new(),
91 };
92
93 for e in &doc.enums {
94 out.enums.insert(e.type_id.clone(), e.clone());
95 }
96 for m in &doc.measurands {
97 out.measurands.insert(m.measurand_id.clone(), m.clone());
98 }
99
100 for imp in &doc.imports {
101 let Some(resolver) = resolver else {
102 return Err(ParseError::new(format!(
103 "document imports {} but no resolver was supplied",
104 imp.uri
105 )));
106 };
107 let (data, format) = resolver.fetch(&imp.uri)?;
108 let idoc = parse_document(&data, format)?;
109 let prefix = if imp.alias.is_empty() {
110 String::new()
111 } else {
112 format!("{}:", imp.alias)
113 };
114 for e in &idoc.enums {
115 out.enums
116 .entry(format!("{prefix}{}", e.type_id))
117 .or_insert_with(|| e.clone());
118 }
119 for m in &idoc.measurands {
120 out.measurands
121 .entry(format!("{prefix}{}", m.measurand_id))
122 .or_insert_with(|| m.clone());
123 }
124 out.imports.insert(imp.uri.clone(), idoc);
125 }
126 Ok(out)
127}