#[cfg(test)]
mod test_every_field_set;
#[cfg(test)]
mod test_tariffs;
use std::fmt;
use chrono_tz::Tz;
use crate::{
generate, guess, json, price, schema, tariff,
warning::{self, Caveat, GatherWarnings as _, IntoCaveat as _},
FromSchema as _, Verdict,
};
pub fn infer_version(json: json::Document<'_>) -> guess::CdrVersion<'_> {
guess::cdr_version(json)
}
pub fn from_json(
json: json::Document<'_>,
version: crate::Version,
) -> Caveat<Versioned<'_>, schema::Warning> {
let (version, warnings) = match version {
crate::Version::V221 => {
let (cdr, warnings) = schema::v221::build_cdr(&json).into_parts();
(Version::V221(cdr), warnings)
}
crate::Version::V211 => {
let (cdr, warnings) = schema::v211::build_cdr(&json).into_parts();
(Version::V211(cdr), warnings)
}
};
let versioned = Versioned { doc: json, version };
versioned.into_caveat(warnings)
}
pub fn from_versioned_json(json: VersionedJson<'_>) -> Caveat<Versioned<'_>, schema::Warning> {
let VersionedJson { doc, version } = json;
from_json(doc, version)
}
pub fn generate_from_tariff(
tariff: &tariff::Versioned<'_>,
config: &generate::Config,
) -> Verdict<generate::Report, generate::Warning> {
generate::cdr_from_tariff(tariff, config)
}
pub fn price(
cdr: &Versioned<'_>,
tariff_source: price::TariffSource<'_>,
timezone: Tz,
) -> Verdict<price::Report, price::Warning> {
price::cdr(cdr, tariff_source, timezone)
}
#[derive(Clone)]
pub struct Versioned<'buf> {
doc: json::Document<'buf>,
version: Version<'buf>,
}
impl fmt::Debug for Versioned<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
match &self.version {
Version::V211(cdr) => fmt::Debug::fmt(&cdr, f),
Version::V221(cdr) => fmt::Debug::fmt(&cdr, f),
}
} else {
match &self.version {
Version::V211(_) => f.write_str("V211"),
Version::V221(_) => f.write_str("V221"),
}
}
}
}
impl crate::Versioned for Versioned<'_> {
fn version(&self) -> crate::Version {
match self.version {
Version::V211(_) => crate::Version::V211,
Version::V221(_) => crate::Version::V221,
}
}
}
impl<'buf> Versioned<'buf> {
pub(crate) fn to_v221(&self) -> Verdict<price::v221::Cdr, price::Warning> {
let mut warnings = warning::Set::new();
let cdr = match &self.version {
Version::V211(cdr) => {
let cdr = price::v211::Cdr::from_schema(cdr)?.gather_warnings_into(&mut warnings);
price::v221::Cdr::from(cdr)
}
Version::V221(cdr) => {
price::v221::Cdr::from_schema(cdr)?.gather_warnings_into(&mut warnings)
}
};
if cdr.charging_periods.is_empty() {
return warnings.bail(self.as_element(), price::Warning::NoPeriods);
}
let cdr_range = cdr.start_date_time..cdr.end_date_time;
let period_range = match cdr.charging_periods.as_slice() {
[] => None,
[period] => Some(price::PeriodRange::Single(period.start_date_time)),
[earliest, .., latest] => Some(price::PeriodRange::Many(
earliest.start_date_time..latest.start_date_time,
)),
};
let outside = match &period_range {
None => false,
Some(price::PeriodRange::Single(start)) => !cdr_range.contains(start),
Some(price::PeriodRange::Many(range)) => {
!(cdr_range.contains(&range.start) && cdr_range.contains(&range.end))
}
};
if let (true, Some(period_range)) = (outside, period_range) {
warnings.insert(
self.as_element(),
price::Warning::PeriodsOutsideStartEndDateTime {
cdr_range,
period_range,
},
);
}
Ok(cdr.into_caveat(warnings))
}
pub(crate) fn tariffs_to_v221(
&self,
) -> Verdict<Vec<Caveat<tariff::v221::Tariff<'buf>, tariff::Warning>>, tariff::Warning> {
let mut warnings = warning::Set::new();
let mut lowered = Vec::new();
match &self.version {
Version::V211(cdr) => {
let tariffs = warnings.ok_or_bail(&cdr.tariffs)?;
for tariff in tariffs.iter().flatten() {
let tariff = warnings.ok_or_bail(tariff)?;
let tariff = tariff::v211::Tariff::from_schema(tariff)?;
lowered.push(tariff.map(tariff::v221::Tariff::from));
}
}
Version::V221(cdr) => {
let tariffs = warnings.ok_or_bail(&cdr.tariffs)?;
for tariff in tariffs.iter().flatten() {
let tariff = warnings.ok_or_bail(tariff)?;
lowered.push(tariff::v221::Tariff::from_schema(tariff)?);
}
}
}
if lowered.iter().any(|tariff| tariff.elements.is_empty()) {
return warnings.bail(self.as_element(), tariff::Warning::NoElements);
}
Ok(lowered.into_caveat(warnings))
}
pub(crate) fn schema(&self) -> &Version<'buf> {
&self.version
}
pub fn into_doc(self) -> json::Document<'buf> {
self.doc
}
pub fn as_element(&self) -> &json::Element<'buf> {
self.doc.root()
}
pub fn as_doc(&self) -> &json::Document<'buf> {
&self.doc
}
pub fn as_json_str(&self) -> &'buf str {
self.doc.source()
}
}
#[expect(
clippy::large_enum_variant,
reason = "the v2.1.1 and v2.2.1 CDR IRs differ in size; this short-lived versioned \
value is not worth boxing"
)]
#[derive(Clone)]
pub(crate) enum Version<'buf> {
V211(schema::v211::Cdr<'buf>),
V221(schema::v221::Cdr<'buf>),
}
pub struct VersionedJson<'buf> {
doc: json::Document<'buf>,
version: crate::Version,
}
impl fmt::Debug for VersionedJson<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
fmt::Debug::fmt(&self.doc, f)
} else {
match self.version {
crate::Version::V211 => f.write_str("V211"),
crate::Version::V221 => f.write_str("V221"),
}
}
}
}
impl crate::Versioned for VersionedJson<'_> {
fn version(&self) -> crate::Version {
self.version
}
}
impl<'buf> VersionedJson<'buf> {
pub(crate) fn new(element: json::Document<'buf>, version: crate::Version) -> Self {
Self {
doc: element,
version,
}
}
pub fn into_doc(self) -> json::Document<'buf> {
self.doc
}
pub fn as_element(&self) -> &json::Element<'buf> {
self.doc.root()
}
pub fn as_doc(&self) -> &json::Document<'buf> {
&self.doc
}
pub fn as_json_str(&self) -> &'buf str {
self.doc.source()
}
}
#[derive(Debug)]
pub struct Unversioned<'buf> {
doc: json::Document<'buf>,
}
impl<'buf> Unversioned<'buf> {
pub(crate) fn new(doc: json::Document<'buf>) -> Self {
Self { doc }
}
pub fn into_doc(self) -> json::Document<'buf> {
self.doc
}
pub fn as_element(&self) -> &json::Element<'buf> {
self.doc.root()
}
}
impl<'buf> crate::Unversioned for Unversioned<'buf> {
type Versioned = VersionedJson<'buf>;
fn force_into_versioned(self, version: crate::Version) -> VersionedJson<'buf> {
let Self { doc } = self;
VersionedJson { doc, version }
}
}