use std::path::Path;
use serde::Deserialize;
use crate::error::MifRhError;
#[derive(Debug, Clone, Deserialize)]
pub struct TopicConfig {
pub id: String,
#[serde(default)]
pub ontologies: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct HarnessConfig {
#[serde(default)]
pub topics: Vec<TopicConfig>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TopicBinding {
pub id: String,
pub pinned_version: Option<String>,
}
impl HarnessConfig {
pub fn load(path: &Path) -> Result<Self, MifRhError> {
if !path.exists() {
return Err(MifRhError::ConfigMissing {
path: path.display().to_string(),
});
}
let contents = std::fs::read_to_string(path).map_err(|source| MifRhError::Io {
path: path.display().to_string(),
source,
})?;
serde_json::from_str(&contents).map_err(|source| MifRhError::Json {
path: path.display().to_string(),
source,
})
}
#[must_use]
pub fn topic_bindings(&self, topic: &str) -> Vec<TopicBinding> {
self.topics
.iter()
.find(|t| t.id == topic)
.map(|t| t.ontologies.iter().map(|s| parse_binding(s)).collect())
.unwrap_or_default()
}
}
fn parse_binding(binding: &str) -> TopicBinding {
binding.split_once('@').map_or_else(
|| TopicBinding {
id: binding.to_string(),
pinned_version: None,
},
|(id, version)| TopicBinding {
id: id.to_string(),
pinned_version: Some(version.to_string()),
},
)
}
#[cfg(test)]
mod tests {
use std::io::Write as _;
use super::{HarnessConfig, TopicBinding};
#[test]
fn parses_bare_and_version_pinned_bindings() {
let mut file = tempfile::NamedTempFile::new().unwrap();
file.write_all(
br#"{"topics":[
{"id":"edu","ontologies":["edu-fixture"]},
{"id":"eng","ontologies":["software-engineering@0.5.0"]},
{"id":"bare","ontologies":[]}
]}"#,
)
.unwrap();
let config = HarnessConfig::load(file.path()).unwrap();
assert_eq!(
config.topic_bindings("edu"),
[TopicBinding {
id: "edu-fixture".to_string(),
pinned_version: None
}]
);
assert_eq!(
config.topic_bindings("eng"),
[TopicBinding {
id: "software-engineering".to_string(),
pinned_version: Some("0.5.0".to_string())
}]
);
assert_eq!(config.topic_bindings("bare"), []);
}
#[test]
fn unconfigured_topic_binds_nothing_rather_than_erroring() {
let mut file = tempfile::NamedTempFile::new().unwrap();
file.write_all(br#"{"topics":[]}"#).unwrap();
let config = HarnessConfig::load(file.path()).unwrap();
assert_eq!(config.topic_bindings("unknown"), []);
}
#[test]
fn reports_missing_config() {
let error = HarnessConfig::load(std::path::Path::new("/nonexistent/harness.config.json"))
.unwrap_err();
assert!(matches!(error, super::MifRhError::ConfigMissing { .. }));
}
}