1use std::collections::BTreeMap;
4use std::str::FromStr;
5
6#[derive(Debug, thiserror::Error)]
8pub enum AuxiliaryError {
9 #[error("auxiliary schema id must not be empty")]
11 EmptySchemaId,
12
13 #[error("malformed auxiliary schema id: {value:?}")]
15 MalformedSchemaId {
16 value: String,
18 },
19
20 #[error("auxiliary artifact key must not be empty")]
22 EmptyArtifactKey,
23
24 #[error("auxiliary artifact path must not be empty for key {key:?}")]
26 EmptyArtifactPath {
27 key: String,
29 },
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub enum BlessedAuxSchema {
35 D8RasterV1,
37 SnapV1,
39}
40
41impl std::fmt::Display for BlessedAuxSchema {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 BlessedAuxSchema::D8RasterV1 => write!(f, "hfx.aux.d8_raster.v1"),
45 BlessedAuxSchema::SnapV1 => write!(f, "hfx.aux.snap.v1"),
46 }
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52pub enum AuxiliarySchemaId {
53 Blessed(BlessedAuxSchema),
55 Provisional(String),
57 ThirdParty(String),
59}
60
61impl AuxiliarySchemaId {
62 pub fn parse(raw: &str) -> Result<Self, AuxiliaryError> {
71 if raw.is_empty() {
72 return Err(AuxiliaryError::EmptySchemaId);
73 }
74 match raw {
75 "hfx.aux.d8_raster.v1" => Ok(Self::Blessed(BlessedAuxSchema::D8RasterV1)),
76 "hfx.aux.snap.v1" => Ok(Self::Blessed(BlessedAuxSchema::SnapV1)),
77 value if value.starts_with("hfx.aux.") => Err(AuxiliaryError::MalformedSchemaId {
78 value: value.to_owned(),
79 }),
80 value if value.starts_with("hfx.x.") => Ok(Self::Provisional(value.to_owned())),
81 value => Ok(Self::ThirdParty(value.to_owned())),
82 }
83 }
84}
85
86impl std::fmt::Display for AuxiliarySchemaId {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 match self {
89 AuxiliarySchemaId::Blessed(schema) => write!(f, "{schema}"),
90 AuxiliarySchemaId::Provisional(value) | AuxiliarySchemaId::ThirdParty(value) => {
91 write!(f, "{value}")
92 }
93 }
94 }
95}
96
97impl FromStr for AuxiliarySchemaId {
98 type Err = AuxiliaryError;
99
100 fn from_str(s: &str) -> Result<Self, Self::Err> {
101 Self::parse(s)
102 }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct AuxiliaryDecl {
108 schema: AuxiliarySchemaId,
109 artifacts: BTreeMap<String, String>,
110}
111
112impl AuxiliaryDecl {
113 pub fn new(
125 schema: AuxiliarySchemaId,
126 artifacts: BTreeMap<String, String>,
127 ) -> Result<Self, AuxiliaryError> {
128 for (key, path) in &artifacts {
129 if key.is_empty() {
130 return Err(AuxiliaryError::EmptyArtifactKey);
131 }
132 if path.is_empty() {
133 return Err(AuxiliaryError::EmptyArtifactPath { key: key.clone() });
134 }
135 }
136 Ok(Self { schema, artifacts })
137 }
138
139 pub fn schema(&self) -> &AuxiliarySchemaId {
141 &self.schema
142 }
143
144 pub fn artifacts(&self) -> &BTreeMap<String, String> {
146 &self.artifacts
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn parse_blessed_d8_raster() {
156 assert_eq!(
157 AuxiliarySchemaId::parse("hfx.aux.d8_raster.v1").unwrap(),
158 AuxiliarySchemaId::Blessed(BlessedAuxSchema::D8RasterV1)
159 );
160 }
161
162 #[test]
163 fn parse_blessed_snap_v1() {
164 assert_eq!(
165 AuxiliarySchemaId::parse("hfx.aux.snap.v1").unwrap(),
166 AuxiliarySchemaId::Blessed(BlessedAuxSchema::SnapV1)
167 );
168 }
169
170 #[test]
171 fn display_blessed_snap_v1() {
172 assert_eq!(BlessedAuxSchema::SnapV1.to_string(), "hfx.aux.snap.v1");
173 }
174
175 #[test]
176 fn parse_provisional_schema() {
177 assert_eq!(
178 AuxiliarySchemaId::parse("hfx.x.experimental.v1").unwrap(),
179 AuxiliarySchemaId::Provisional("hfx.x.experimental.v1".to_string())
180 );
181 }
182
183 #[test]
184 fn parse_third_party_schema() {
185 assert_eq!(
186 AuxiliarySchemaId::parse("org.example.custom.v1").unwrap(),
187 AuxiliarySchemaId::ThirdParty("org.example.custom.v1".to_string())
188 );
189 }
190
191 #[test]
192 fn parse_empty_schema_fails() {
193 assert!(matches!(
194 AuxiliarySchemaId::parse(""),
195 Err(AuxiliaryError::EmptySchemaId)
196 ));
197 }
198
199 #[test]
200 fn parse_unknown_blessed_schema_fails() {
201 assert!(matches!(
202 AuxiliarySchemaId::parse("hfx.aux.other.v1"),
203 Err(AuxiliaryError::MalformedSchemaId { .. })
204 ));
205 }
206
207 #[test]
208 fn auxiliary_decl_rejects_empty_path() {
209 let mut artifacts = BTreeMap::new();
210 artifacts.insert("flow_dir".to_string(), String::new());
211 assert!(matches!(
212 AuxiliaryDecl::new(
213 AuxiliarySchemaId::Blessed(BlessedAuxSchema::D8RasterV1),
214 artifacts
215 ),
216 Err(AuxiliaryError::EmptyArtifactPath { key }) if key == "flow_dir"
217 ));
218 }
219}