nmbrs_workload/
drivers.rs1use std::collections::BTreeMap;
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct DriverManifest {
39 pub driver: String,
41 pub adapter: String,
43 pub library: String,
49 pub description: Option<String>,
51 pub default_params: BTreeMap<String, String>,
55}
56
57pub fn parse_driver_manifest(source: &str, origin: &str) -> Result<DriverManifest, String> {
61 let doc: serde_json::Value =
62 serde_yaml::from_str(source).map_err(|e| format!("driver manifest {origin}: {e}"))?;
63 let obj = doc
64 .as_object()
65 .ok_or_else(|| format!("driver manifest {origin}: top level must be a mapping"))?;
66
67 let mut driver = None;
68 let mut adapter = None;
69 let mut library = None;
70 let mut description = None;
71 let mut default_params = BTreeMap::new();
72
73 for (k, v) in obj {
74 match k.as_str() {
75 "driver" => driver = Some(string_field(v, k, origin)?),
76 "adapter" => adapter = Some(string_field(v, k, origin)?),
77 "library" => library = Some(string_field(v, k, origin)?),
78 "description" => description = Some(string_field(v, k, origin)?),
79 "defaults" => {
80 let d = v.as_object().ok_or_else(|| {
81 format!("driver manifest {origin}: `defaults:` must be a mapping")
82 })?;
83 for (dk, dv) in d {
84 match dk.as_str() {
85 "params" => {
86 let p = dv.as_object().ok_or_else(|| {
87 format!(
88 "driver manifest {origin}: `defaults.params:` \
89 must be a mapping"
90 )
91 })?;
92 for (pk, pv) in p {
93 let s = match pv {
94 serde_json::Value::String(s) => s.clone(),
95 other => other.to_string(),
96 };
97 default_params.insert(pk.clone(), s);
98 }
99 }
100 other => {
101 return Err(format!(
102 "driver manifest {origin}: unknown key \
103 `defaults.{other}` (allowed: params)"
104 ));
105 }
106 }
107 }
108 }
109 other => {
110 return Err(format!(
111 "driver manifest {origin}: unknown top-level key `{other}` \
112 (allowed: driver, adapter, library, description, defaults)"
113 ));
114 }
115 }
116 }
117
118 Ok(DriverManifest {
119 driver: driver.ok_or_else(|| format!("driver manifest {origin}: missing `driver:`"))?,
120 adapter: adapter.ok_or_else(|| format!("driver manifest {origin}: missing `adapter:`"))?,
121 library: library.ok_or_else(|| format!("driver manifest {origin}: missing `library:`"))?,
122 description: description.map(|s| s.trim().to_string()),
123 default_params,
124 })
125}
126
127fn string_field(v: &serde_json::Value, key: &str, origin: &str) -> Result<String, String> {
128 v.as_str()
129 .map(str::to_string)
130 .ok_or_else(|| format!("driver manifest {origin}: `{key}:` must be a string, got {v}"))
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn parses_full_manifest() {
139 let m = parse_driver_manifest(
140 r#"
141driver: vendorx
142adapter: http
143library: vector_impl
144description: VendorX REST vector client
145defaults:
146 params:
147 base_url: "http://localhost:8099"
148 api_key: ""
149"#,
150 "<test>",
151 )
152 .unwrap();
153 assert_eq!(m.driver, "vendorx");
154 assert_eq!(m.adapter, "http");
155 assert_eq!(m.library, "vector_impl");
156 assert_eq!(
157 m.default_params.get("base_url").map(String::as_str),
158 Some("http://localhost:8099")
159 );
160 }
161
162 #[test]
163 fn unknown_keys_are_rejected() {
164 let err =
165 parse_driver_manifest("driver: x\nadapter: http\nlibrary: l\nops: {}\n", "<test>")
166 .unwrap_err();
167 assert!(err.contains("unknown top-level key `ops`"), "err: {err}");
168 }
169
170 #[test]
171 fn missing_required_fields_are_named() {
172 let err = parse_driver_manifest("driver: x\nadapter: http\n", "<test>").unwrap_err();
173 assert!(err.contains("missing `library:`"), "err: {err}");
174 }
175}