use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct InstanceId(Box<str>);
impl InstanceId {
#[must_use]
pub fn new(value: &str) -> Option<Self> {
(!value.is_empty() && value.bytes().all(|byte| byte.is_ascii_digit()))
.then(|| Self(value.into()))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<u64> for InstanceId {
fn from(value: u64) -> Self {
Self(value.to_string().into())
}
}
impl fmt::Display for InstanceId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "#{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Parameter<S = String> {
Null,
Derived,
Bool(bool),
LogicalUnknown,
Integer(S),
Real(S),
Text(S),
Binary(S),
Enum(S),
Ref(InstanceId),
List(Vec<Self>),
Typed {
type_name: S,
value: Box<Self>,
},
}
impl<S> Parameter<S> {
#[must_use]
pub fn as_reference(&self) -> Option<InstanceId> {
match self {
Self::Ref(id) => Some(id.clone()),
_ => None,
}
}
#[must_use]
pub fn as_list(&self) -> Option<&[Self]> {
match self {
Self::List(items) => Some(items),
_ => None,
}
}
#[must_use]
pub fn unwrap_typed(&self) -> &Self {
match self {
Self::Typed { value, .. } => value.unwrap_typed(),
value => value,
}
}
}
impl<S: AsRef<str>> Parameter<S> {
#[must_use]
pub fn as_text(&self) -> Option<&str> {
match self {
Self::Text(text) => Some(text.as_ref()),
_ => None,
}
}
#[must_use]
pub fn as_f64(&self) -> Option<f64> {
match self {
Self::Integer(value) | Self::Real(value) => value.as_ref().parse().ok(),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HeaderRecord<S = String> {
pub name: S,
pub parameters: Vec<Parameter<S>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Record<S = String> {
pub name: S,
pub parameters: Vec<Parameter<S>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DataRecord<S = String> {
pub id: InstanceId,
pub records: Vec<Record<S>>,
}
impl<S> DataRecord<S> {
#[must_use]
pub fn simple(id: InstanceId, name: S, parameters: Vec<Parameter<S>>) -> Self {
Self {
id,
records: vec![Record { name, parameters }],
}
}
#[must_use]
pub fn as_simple(&self) -> Option<&Record<S>> {
(self.records.len() == 1).then(|| &self.records[0])
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct HeaderSection<S = String> {
pub records: Vec<HeaderRecord<S>>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct StandardHeader {
pub description: Option<Vec<String>>,
pub implementation_level: Option<String>,
pub name: Option<String>,
pub time_stamp: Option<String>,
pub author: Option<Vec<String>>,
pub organization: Option<Vec<String>>,
pub preprocessor_version: Option<String>,
pub originating_system: Option<String>,
pub authorization: Option<String>,
pub schema: Option<Vec<String>>,
}
impl<S: AsRef<str>> HeaderSection<S> {
#[must_use]
pub fn standard(&self) -> StandardHeader {
let mut header = StandardHeader::default();
for record in &self.records {
match record.name.as_ref().to_ascii_uppercase().as_str() {
"FILE_DESCRIPTION" => {
header.description = text_list(record.parameters.first());
header.implementation_level = text(record.parameters.get(1));
}
"FILE_NAME" => {
header.name = text(record.parameters.first());
header.time_stamp = text(record.parameters.get(1));
header.author = text_list(record.parameters.get(2));
header.organization = text_list(record.parameters.get(3));
header.preprocessor_version = text(record.parameters.get(4));
header.originating_system = text(record.parameters.get(5));
header.authorization = text(record.parameters.get(6));
}
"FILE_SCHEMA" => header.schema = text_list(record.parameters.first()),
_ => {}
}
}
header
}
}
fn text<S: AsRef<str>>(parameter: Option<&Parameter<S>>) -> Option<String> {
parameter?.as_text().map(ToOwned::to_owned)
}
fn text_list<S: AsRef<str>>(parameter: Option<&Parameter<S>>) -> Option<Vec<String>> {
Some(
parameter?
.as_list()?
.iter()
.filter_map(Parameter::as_text)
.map(ToOwned::to_owned)
.collect(),
)
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct DataSection<S = String> {
pub records: Vec<DataRecord<S>>,
}
impl<S> DataSection<S> {
#[must_use]
pub fn get(&self, id: &InstanceId) -> Option<&DataRecord<S>> {
self.records.iter().find(|record| &record.id == id)
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Exchange<S = String> {
pub header: HeaderSection<S>,
pub data: DataSection<S>,
}