use crate::prelude::*;
use core::fmt;
pub mod mapping;
pub use mapping::{FieldMapping, FieldRule, FieldValue, Fields};
#[derive(Clone, Debug)]
pub enum CrosswalkError {
MissingRequiredField(String),
TransformationFailed {
field: String,
reason: String,
},
BuildFailed(String),
ParseFailed(String),
SerializeFailed(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FieldLossType {
NoEquivalent,
Approximated,
Simplified,
Omitted,
BlockNotSelected,
}
#[derive(Clone, Debug)]
pub struct ConversionWarning {
pub from: &'static str,
pub to: &'static str,
pub field: String,
pub loss_type: FieldLossType,
pub details: Option<String>,
}
impl fmt::Display for ConversionWarning {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} → {}: {} ({}) {}",
self.from,
self.to,
self.field,
self.loss_type,
self.details.as_deref().unwrap_or("")
)
}
}
impl ConversionWarning {
pub fn no_equivalent(from: &'static str, to: &'static str, field: impl Into<String>) -> Self {
Self {
from,
to,
field: field.into(),
details: None,
loss_type: FieldLossType::NoEquivalent,
}
}
pub fn approximated(from: &'static str, to: &'static str, field: impl Into<String>, details: impl Into<String>) -> Self {
Self {
from,
to,
field: field.into(),
details: Some(details.into()),
loss_type: FieldLossType::Approximated,
}
}
pub fn simplified(from: &'static str, to: &'static str, field: impl Into<String>, details: impl Into<String>) -> Self {
Self {
from,
to,
field: field.into(),
details: Some(details.into()),
loss_type: FieldLossType::Simplified,
}
}
pub fn omitted(from: &'static str, to: &'static str, field: impl Into<String>, details: impl Into<String>) -> Self {
Self {
from,
to,
field: field.into(),
details: Some(details.into()),
loss_type: FieldLossType::Omitted,
}
}
}
impl fmt::Display for CrosswalkError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
| Self::MissingRequiredField(field) => write!(f, "missing required field — {field}"),
| Self::TransformationFailed { field, reason } => {
write!(f, "failed to transform field '{field}' — {reason}")
}
| Self::BuildFailed(reason) => write!(f, "build failed — {reason}"),
| Self::ParseFailed(reason) => write!(f, "content parse failed — {reason}"),
| Self::SerializeFailed(reason) => write!(f, "output serialize failed — {reason}"),
}
}
}
impl From<String> for CrosswalkError {
fn from(err: String) -> Self {
CrosswalkError::BuildFailed(err)
}
}
impl fmt::Display for FieldLossType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
| Self::NoEquivalent => write!(f, "no equivalent field"),
| Self::Approximated => write!(f, "approximated"),
| Self::Simplified => write!(f, "simplified"),
| Self::Omitted => write!(f, "omitted"),
| Self::BlockNotSelected => write!(f, "metadata block not selected"),
}
}
}
pub trait Crosswalk<T>: Sized {
fn crosswalk(&self) -> Result<(T, Vec<ConversionWarning>), CrosswalkError>;
}
pub trait SchemaExtractor {
fn extract_fields(&self) -> Fields;
}
pub trait SchemaBuilder: Sized {
fn build_from_fields(fields: &Fields) -> Result<Self, CrosswalkError>;
}
pub fn convert<S, T>(source: &S, mapping: &FieldMapping) -> Result<(T, Vec<ConversionWarning>), CrosswalkError>
where
S: SchemaExtractor,
T: SchemaBuilder,
{
let source_fields = source.extract_fields();
let mut target_fields = Fields::new();
let warnings = Vec::new();
mapping.apply(&source_fields, &mut target_fields)?;
let target = T::build_from_fields(&target_fields)?;
Ok((target, warnings))
}
#[cfg(test)]
mod tests;