use std::path::Path;
use super::MEMBER_COLUMNS;
use crate::reader::{Rf2Error, Rf2Reader};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RefsetKind {
Language,
OwlExpression,
ModuleDependency,
Content,
}
impl RefsetKind {
#[must_use]
pub fn of_columns(columns: &[String]) -> Self {
let has = |name: &str| columns.iter().any(|column| column == name);
if has("acceptabilityId") {
Self::Language
} else if has("owlExpression") {
Self::OwlExpression
} else if has("sourceEffectiveTime") && has("targetEffectiveTime") {
Self::ModuleDependency
} else {
Self::Content
}
}
}
pub fn read(path: &Path) -> Result<RefsetKind, Rf2Error> {
let file = std::fs::File::open(path).map_err(|source| Rf2Error::Io {
path: path.to_path_buf(),
source,
})?;
let reader = Rf2Reader::new_with(path, std::io::BufReader::new(file), |actual| {
if actual.len() >= MEMBER_COLUMNS.len()
&& actual.iter().zip(MEMBER_COLUMNS).all(|(a, e)| a == e)
{
Ok(())
} else {
Err(Rf2Error::Header {
path: path.to_path_buf(),
expected: MEMBER_COLUMNS.iter().map(|c| (*c).to_owned()).collect(),
actual: actual.to_vec(),
})
}
})?;
let additional: Vec<String> = reader
.columns()
.iter()
.skip(MEMBER_COLUMNS.len())
.cloned()
.collect();
Ok(RefsetKind::of_columns(&additional))
}
#[cfg(test)]
mod tests {
use super::super::tests::{file, header};
use super::{MEMBER_COLUMNS, RefsetKind, Rf2Error, read};
fn columns(names: &[&str]) -> Vec<String> {
names.iter().map(|n| (*n).to_owned()).collect()
}
fn kind_of(header: &str) -> Result<RefsetKind, Rf2Error> {
let dir = tempfile::tempdir().expect("a directory to write the header into");
let path = dir
.path()
.join("der2_Refset_SimpleSnapshot_INT_20260101.txt");
std::fs::write(&path, file(&[header])).expect("the header is written");
read(&path)
}
#[test]
fn the_additional_columns_name_the_kind() {
assert_eq!(
RefsetKind::of_columns(&columns(&["acceptabilityId"])),
RefsetKind::Language
);
assert_eq!(
RefsetKind::of_columns(&columns(&["owlExpression"])),
RefsetKind::OwlExpression
);
assert_eq!(
RefsetKind::of_columns(&columns(&["sourceEffectiveTime", "targetEffectiveTime"])),
RefsetKind::ModuleDependency
);
assert_eq!(
RefsetKind::of_columns(&columns(&["sourceEffectiveTime"])),
RefsetKind::Content,
"one of the two effective times is not the module dependency layout"
);
for additional in [
vec!["targetComponentId"],
vec!["valueId"],
vec!["mapTarget"],
vec![
"mapGroup",
"mapPriority",
"mapRule",
"mapAdvice",
"mapTarget",
"correlationId",
"mapCategoryId",
],
vec![],
] {
assert_eq!(
RefsetKind::of_columns(&columns(&additional)),
RefsetKind::Content,
"{additional:?} is a content layout"
);
}
}
#[test]
fn a_kind_is_read_from_the_header_of_a_real_file() {
assert_eq!(
kind_of(&header(&["acceptabilityId"])).expect("a language refset header"),
RefsetKind::Language
);
assert_eq!(
kind_of(&header(&[])).expect("a simple refset header"),
RefsetKind::Content
);
}
#[test]
fn a_header_that_does_not_start_with_the_member_columns_is_refused() {
let wrong = "id\teffectiveTime\tactive\tmoduleId\trefsetId\tconceptId";
let error = kind_of(wrong).expect_err("the sixth column is not a member column");
let Rf2Error::Header {
expected, actual, ..
} = error
else {
panic!("a header error, not {error}");
};
assert_eq!(expected, MEMBER_COLUMNS.map(str::to_owned).to_vec());
assert_eq!(
actual.last().map(String::as_str),
Some("conceptId"),
"the error carries what the file actually had"
);
}
#[test]
fn a_header_shorter_than_the_member_columns_is_refused() {
let error =
kind_of("id\teffectiveTime\tactive").expect_err("three columns is not a member");
assert!(matches!(error, Rf2Error::Header { .. }), "{error}");
}
}