#[cfg(test)]
mod test;
#[cfg(test)]
mod test_guess_cdr;
#[cfg(test)]
mod test_guess_tariff;
#[cfg(test)]
mod test_real_world;
use std::fmt;
use tracing::debug;
use crate::{cdr, json, tariff, v211, v221, ParseError, Unversioned, Versioned};
pub type CdrVersion<'buf> = Version<cdr::Versioned<'buf>, cdr::Unversioned<'buf>>;
pub type CdrReport<'buf> = Report<'buf, cdr::Versioned<'buf>, cdr::Unversioned<'buf>>;
pub(crate) fn cdr_version(cdr_json: &str) -> Result<CdrVersion<'_>, ParseError> {
guess_cdr_version(cdr_json)
}
pub(crate) fn cdr_version_and_report(cdr_json: &str) -> Result<CdrReport<'_>, ParseError> {
let version = guess_cdr_version(cdr_json)?;
let Version::Certain(cdr) = version else {
return Ok(Report {
version,
unexpected_fields: json::UnexpectedFields::empty(),
});
};
let schema = match cdr.version() {
crate::Version::V211 => &v211::CDR_SCHEMA,
crate::Version::V221 => &v221::CDR_SCHEMA,
};
let report = json::parse_with_schema(cdr_json, schema).map_err(ParseError::from_cdr_err)?;
let json::ParseReport {
element: _,
unexpected_fields,
} = report;
Ok(CdrReport {
unexpected_fields,
version: Version::Certain(cdr),
})
}
fn guess_cdr_version(source: &str) -> Result<CdrVersion<'_>, ParseError> {
const V211_EXCLUSIVE_FIELDS: &[&str] = &["stop_date_time"];
const V221_EXCLUSIVE_FIELDS: &[&str] = &["end_date_time", "cdr_location", "cdr_token"];
let element = json::parse(source).map_err(ParseError::from_cdr_err)?;
let value = element.value();
let json::Value::Object(fields) = value else {
return Err(ParseError::cdr_should_be_object());
};
for field in fields {
let key = field.key().as_raw();
if V211_EXCLUSIVE_FIELDS.contains(&key) {
return Ok(Version::Certain(cdr::Versioned::new(
source,
element,
crate::Version::V211,
)));
} else if V221_EXCLUSIVE_FIELDS.contains(&key) {
return Ok(Version::Certain(cdr::Versioned::new(
source,
element,
crate::Version::V221,
)));
}
}
Ok(Version::Uncertain(cdr::Unversioned::new(source, element)))
}
pub type TariffVersion<'buf> = Version<tariff::Versioned<'buf>, tariff::Unversioned<'buf>>;
pub type TariffReport<'buf> = Report<'buf, tariff::Versioned<'buf>, tariff::Unversioned<'buf>>;
pub(super) fn tariff_version(tariff_json: &str) -> Result<TariffVersion<'_>, ParseError> {
guess_tariff_version(tariff_json)
}
pub(super) fn tariff_version_with_report(
tariff_json: &str,
) -> Result<TariffReport<'_>, ParseError> {
let version = guess_tariff_version(tariff_json)?;
let Version::Certain(object) = version else {
return Ok(Report {
version,
unexpected_fields: json::UnexpectedFields::empty(),
});
};
let schema = match object.version() {
crate::Version::V211 => &v211::TARIFF_SCHEMA,
crate::Version::V221 => &v221::TARIFF_SCHEMA,
};
let report =
json::parse_with_schema(tariff_json, schema).map_err(ParseError::from_tariff_err)?;
let json::ParseReport {
element: _,
unexpected_fields,
} = report;
Ok(TariffReport {
unexpected_fields,
version: Version::Certain(object),
})
}
fn guess_tariff_version(source: &str) -> Result<TariffVersion<'_>, ParseError> {
const V221_EXCLUSIVE_FIELDS: &[&str] = &[
"country_code",
"party_id",
"type",
"min_price",
"max_price",
"start_date_time",
"end_date_time",
];
const V211_V221_SHARED_FIELDS: &[&str] = &[
"id",
"currency",
"tariff_alt_text",
"tariff_alt_url",
"elements",
"energy_mix",
"last_updated",
];
let element = json::parse(source).map_err(ParseError::from_tariff_err)?;
let value = element.value();
let json::Value::Object(fields) = value else {
return Err(ParseError::tariff_should_be_object());
};
for field in fields {
let key = field.key().as_raw();
if V221_EXCLUSIVE_FIELDS.contains(&key) {
debug!("Tariff is v221 because of field: `{key}`");
return Ok(TariffVersion::Certain(tariff::Versioned::new(
source,
element,
crate::Version::V221,
)));
}
}
for field in fields {
let key = field.key().as_raw();
if V211_V221_SHARED_FIELDS.contains(&key) {
return Ok(TariffVersion::Certain(tariff::Versioned::new(
source,
element,
crate::Version::V211,
)));
}
}
Ok(TariffVersion::Uncertain(tariff::Unversioned::new(
source, element,
)))
}
#[derive(Debug)]
pub enum Version<V, U>
where
V: Versioned,
U: fmt::Debug,
{
Certain(V),
Uncertain(U),
}
impl<V, U> Version<V, U>
where
V: Versioned,
U: Unversioned<Versioned = V>,
{
pub fn into_version(self) -> Version<crate::Version, ()> {
match self {
Version::Certain(v) => Version::Certain(v.version()),
Version::Uncertain(_) => Version::Uncertain(()),
}
}
pub fn as_version(&self) -> Version<crate::Version, ()> {
match self {
Version::Certain(v) => Version::Certain(v.version()),
Version::Uncertain(_) => Version::Uncertain(()),
}
}
pub fn certain_or(self, fallback: crate::Version) -> V {
match self {
Version::Certain(v) => v,
Version::Uncertain(u) => u.force_into_versioned(fallback),
}
}
pub fn certain_or_none(self) -> Option<V> {
match self {
Version::Certain(v) => Some(v),
Version::Uncertain(_) => None,
}
}
}
#[derive(Debug)]
pub struct Report<'buf, V, U>
where
V: Versioned,
U: fmt::Debug,
{
pub unexpected_fields: json::UnexpectedFields<'buf>,
pub version: Version<V, U>,
}