use std::io::Read;
use std::path::Path;
use crate::file::FieldKind;
use crate::id::{ConceptId, MemberId, ModuleId, RefsetId, Sctid};
use crate::reader::{FieldError, Record, Rf2Error, Rf2Reader};
use crate::time::EffectiveTime;
pub const MEMBER_COLUMNS: [&str; 6] = [
"id",
"effectiveTime",
"active",
"moduleId",
"refsetId",
"referencedComponentId",
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FieldValue {
Component(Sctid),
Integer(i64),
String(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Member {
pub id: MemberId,
pub effective_time: EffectiveTime,
pub active: bool,
pub module_id: ModuleId,
pub refset_id: RefsetId,
pub referenced_component_id: Sctid,
pub fields: Vec<(String, FieldValue)>,
}
impl Member {
#[must_use]
pub fn field(&self, column: &str) -> Option<&FieldValue> {
self.fields
.iter()
.find(|(name, _)| name == column)
.map(|(_, value)| value)
}
fn component(&self, column: &str) -> Result<Sctid, ViewError> {
match self.field(column) {
Some(FieldValue::Component(id)) => Ok(*id),
other => Err(ViewError::field(column, other)),
}
}
fn concept(&self, column: &str) -> Result<ConceptId, ViewError> {
let id = self.component(column)?;
let Ok(id) = ConceptId::try_from(id) else {
return Err(ViewError::field(column, self.field(column)));
};
Ok(id)
}
fn string(&self, column: &str) -> Result<&str, ViewError> {
match self.field(column) {
Some(FieldValue::String(text)) => Ok(text),
other => Err(ViewError::field(column, other)),
}
}
fn integer(&self, column: &str) -> Result<i64, ViewError> {
match self.field(column) {
Some(FieldValue::Integer(value)) => Ok(*value),
other => Err(ViewError::field(column, other)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ViewError {
#[error("column {column} is {found}")]
Field {
column: String,
found: String,
},
#[error("column {column}: {source}")]
Value {
column: String,
#[source]
source: FieldError,
},
}
impl ViewError {
fn field(column: &str, found: Option<&FieldValue>) -> Self {
Self::Field {
column: column.to_owned(),
found: match found {
None => String::from("absent"),
Some(FieldValue::Component(_)) => String::from("a component reference"),
Some(FieldValue::Integer(_)) => String::from("an integer"),
Some(FieldValue::String(_)) => String::from("a string"),
},
}
}
}
#[derive(Debug)]
pub struct Members<R: Read> {
reader: Rf2Reader<R>,
kinds: Vec<FieldKind>,
}
impl Members<std::io::BufReader<std::fs::File>> {
pub fn open(path: &Path, kinds: &[FieldKind]) -> Result<Self, Rf2Error> {
let file = std::fs::File::open(path).map_err(|source| Rf2Error::Io {
path: path.to_path_buf(),
source,
})?;
Self::new(path, std::io::BufReader::new(file), kinds)
}
}
impl<R: Read> Members<R> {
pub fn new(path: &Path, read: R, kinds: &[FieldKind]) -> Result<Self, Rf2Error> {
let reader = Rf2Reader::new_with(path, read, |actual| {
let fixed_ok = actual.len() == MEMBER_COLUMNS.len() + kinds.len()
&& actual.iter().zip(MEMBER_COLUMNS).all(|(a, e)| a == e);
if fixed_ok {
Ok(())
} else {
let mut expected: Vec<String> =
MEMBER_COLUMNS.iter().map(|c| (*c).to_owned()).collect();
expected.extend(kinds.iter().map(|k| format!("<{k:?}>")));
Err(Rf2Error::Header {
path: path.to_path_buf(),
expected,
actual: actual.to_vec(),
})
}
})?;
Ok(Self {
reader,
kinds: kinds.to_vec(),
})
}
fn member(kinds: &[FieldKind], record: &Record<'_>) -> Result<Member, Rf2Error> {
let mut fields = Vec::with_capacity(kinds.len());
for (offset, kind) in kinds.iter().enumerate() {
let index = MEMBER_COLUMNS.len() + offset;
let name = record.column_name(index).unwrap_or_default().to_owned();
let value = match kind {
FieldKind::Component => FieldValue::Component(record.parse(index, Sctid::parse)?),
FieldKind::Integer => FieldValue::Integer(record.integer(index)?),
FieldKind::String => FieldValue::String(record.text(index)?.to_owned()),
};
fields.push((name, value));
}
Ok(Member {
id: record.parse(0, MemberId::parse)?,
effective_time: record.parse(1, EffectiveTime::parse)?,
active: record.boolean(2)?,
module_id: record.parse(3, ModuleId::parse)?,
refset_id: record.parse(4, RefsetId::parse)?,
referenced_component_id: record.parse(5, Sctid::parse)?,
fields,
})
}
}
impl<R: Read> Iterator for Members<R> {
type Item = Result<Member, Rf2Error>;
fn next(&mut self) -> Option<Self::Item> {
match self.reader.next_record() {
Ok(Some(record)) => Some(Self::member(&self.kinds, &record)),
Ok(None) => None,
Err(e) => Some(Err(e)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LanguageMember {
pub member: Member,
pub acceptability_id: ConceptId,
}
impl TryFrom<Member> for LanguageMember {
type Error = ViewError;
fn try_from(member: Member) -> Result<Self, ViewError> {
let acceptability_id = member.concept("acceptabilityId")?;
Ok(Self {
member,
acceptability_id,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AssociationMember {
pub member: Member,
pub target_component_id: Sctid,
}
impl TryFrom<Member> for AssociationMember {
type Error = ViewError;
fn try_from(member: Member) -> Result<Self, ViewError> {
let target_component_id = member.component("targetComponentId")?;
Ok(Self {
member,
target_component_id,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AttributeValueMember {
pub member: Member,
pub value_id: ConceptId,
}
impl TryFrom<Member> for AttributeValueMember {
type Error = ViewError;
fn try_from(member: Member) -> Result<Self, ViewError> {
let value_id = member.concept("valueId")?;
Ok(Self { member, value_id })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SimpleMapMember {
pub member: Member,
pub map_target: String,
}
impl TryFrom<Member> for SimpleMapMember {
type Error = ViewError;
fn try_from(member: Member) -> Result<Self, ViewError> {
let map_target = member.string("mapTarget")?.to_owned();
Ok(Self { member, map_target })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtendedMapMember {
pub member: Member,
pub map_group: i64,
pub map_priority: i64,
pub map_rule: String,
pub map_advice: String,
pub map_target: String,
pub correlation_id: ConceptId,
pub map_category_id: ConceptId,
}
impl TryFrom<Member> for ExtendedMapMember {
type Error = ViewError;
fn try_from(member: Member) -> Result<Self, ViewError> {
Ok(Self {
map_group: member.integer("mapGroup")?,
map_priority: member.integer("mapPriority")?,
map_rule: member.string("mapRule")?.to_owned(),
map_advice: member.string("mapAdvice")?.to_owned(),
map_target: member.string("mapTarget")?.to_owned(),
correlation_id: member.concept("correlationId")?,
map_category_id: member.concept("mapCategoryId")?,
member,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ModuleDependencyMember {
pub member: Member,
pub source_effective_time: EffectiveTime,
pub target_effective_time: EffectiveTime,
}
impl TryFrom<Member> for ModuleDependencyMember {
type Error = ViewError;
fn try_from(member: Member) -> Result<Self, ViewError> {
let time = |column: &str| -> Result<EffectiveTime, ViewError> {
EffectiveTime::parse(member.string(column)?).map_err(|source| ViewError::Value {
column: column.to_owned(),
source: source.into(),
})
};
Ok(Self {
source_effective_time: time("sourceEffectiveTime")?,
target_effective_time: time("targetEffectiveTime")?,
member,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OwlExpressionMember {
pub member: Member,
pub owl_expression: String,
}
impl TryFrom<Member> for OwlExpressionMember {
type Error = ViewError;
fn try_from(member: Member) -> Result<Self, ViewError> {
let owl_expression = member.string("owlExpression")?.to_owned();
Ok(Self {
member,
owl_expression,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DescriptorMember {
pub member: Member,
pub attribute_description: ConceptId,
pub attribute_type: ConceptId,
pub attribute_order: i64,
}
impl TryFrom<Member> for DescriptorMember {
type Error = ViewError;
fn try_from(member: Member) -> Result<Self, ViewError> {
Ok(Self {
attribute_description: member.concept("attributeDescription")?,
attribute_type: member.concept("attributeType")?,
attribute_order: member.integer("attributeOrder")?,
member,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DescriptionTypeMember {
pub member: Member,
pub description_format: ConceptId,
pub description_length: i64,
}
impl TryFrom<Member> for DescriptionTypeMember {
type Error = ViewError;
fn try_from(member: Member) -> Result<Self, ViewError> {
Ok(Self {
description_format: member.concept("descriptionFormat")?,
description_length: member.integer("descriptionLength")?,
member,
})
}
}