#[cfg(test)]
pub(crate) mod test;
#[cfg(test)]
mod test_real_world;
pub(crate) mod v211;
pub(crate) mod v221;
pub(crate) mod v2x;
use std::{borrow::Cow, fmt};
use crate::{
country, currency, datetime, duration, enumeration, explain, from_warning_all, guess, json,
lint, money, number, schema, string,
warning::{self, Caveat, IntoCaveat as _},
};
#[derive(Debug)]
pub enum Warning {
Country(country::Warning),
Currency(currency::Warning),
DateTime(datetime::Warning),
Decode(json::decode::Warning),
Duration(duration::Warning),
Enum(enumeration::Warning),
FieldInvalidType {
expected_type: json::ValueKind,
},
FieldInvalidValue {
value: String,
message: Cow<'static, str>,
},
FieldRequired {
field_name: Cow<'static, str>,
},
Money(money::Warning),
ReservationElementSkipped,
TotalCostClampedToMin,
TotalCostClampedToMax,
NoElements,
NotActive,
Number(number::Warning),
String(string::Warning),
}
impl Warning {
fn field_invalid_value(
value: impl Into<String>,
message: impl Into<Cow<'static, str>>,
) -> Self {
Warning::FieldInvalidValue {
value: value.into(),
message: message.into(),
}
}
}
impl fmt::Display for Warning {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::String(warning_kind) => write!(f, "{warning_kind}"),
Self::Country(warning_kind) => write!(f, "{warning_kind}"),
Self::Currency(warning_kind) => write!(f, "{warning_kind}"),
Self::DateTime(warning_kind) => write!(f, "{warning_kind}"),
Self::Decode(warning_kind) => write!(f, "{warning_kind}"),
Self::Duration(warning_kind) => write!(f, "{warning_kind}"),
Self::Enum(warning_kind) => write!(f, "{warning_kind}"),
Self::FieldInvalidType { expected_type } => {
write!(f, "Field has invalid type. Expected type `{expected_type}`")
}
Self::FieldInvalidValue { value, message } => {
write!(f, "Field has invalid value `{value}`: {message}")
}
Self::FieldRequired { field_name } => {
write!(f, "Field is required: `{field_name}`")
}
Self::Money(warning_kind) => write!(f, "{warning_kind}"),
Self::NoElements => f.write_str("The tariff has no `elements`"),
Self::NotActive => f.write_str("The tariff is not active for `Cdr::start_date_time`"),
Self::Number(warning_kind) => write!(f, "{warning_kind}"),
Self::ReservationElementSkipped => f.write_str(
"A tariff element has a `reservation` restriction and will not apply to regular \
charging sessions. Reservation pricing is not supported.",
),
Self::TotalCostClampedToMin => write!(
f,
"The given tariff has a `min_price` set and the `total_cost` fell below it."
),
Self::TotalCostClampedToMax => write!(
f,
"The given tariff has a `max_price` set and the `total_cost` exceeded it."
),
}
}
}
impl crate::Warning for Warning {
fn id(&self) -> warning::Id {
match self {
Self::String(warning) => warning.id(),
Self::Country(warning) => warning.id(),
Self::Currency(warning) => warning.id(),
Self::DateTime(warning) => warning.id(),
Self::Decode(warning) => warning.id(),
Self::Duration(warning) => warning.id(),
Self::Enum(warning) => warning.id(),
Self::FieldInvalidType { expected_type } => {
warning::Id::from_string(format!("field_invalid_type({expected_type})"))
}
Self::FieldInvalidValue { value, .. } => {
warning::Id::from_string(format!("field_invalid_value({value})"))
}
Self::FieldRequired { field_name } => {
warning::Id::from_string(format!("field_required({field_name})"))
}
Self::Money(warning) => warning.id(),
Self::NoElements => warning::Id::from_static("no_elements"),
Self::NotActive => warning::Id::from_static("not_active"),
Self::Number(warning) => warning.id(),
Self::ReservationElementSkipped => {
warning::Id::from_static("reservation_element_skipped")
}
Self::TotalCostClampedToMin => warning::Id::from_static("total_cost_clamped_to_min"),
Self::TotalCostClampedToMax => warning::Id::from_static("total_cost_clamped_to_max"),
}
}
}
from_warning_all!(
country::Warning => Warning::Country,
currency::Warning => Warning::Currency,
datetime::Warning => Warning::DateTime,
duration::Warning => Warning::Duration,
enumeration::Warning => Warning::Enum,
json::decode::Warning => Warning::Decode,
money::Warning => Warning::Money,
number::Warning => Warning::Number,
string::Warning => Warning::String
);
#[derive(Clone, Debug)]
pub(crate) struct CpoId<'buf> {
pub country_code: country::Code,
pub id: string::CiExactLen<'buf, 3>,
}
pub fn infer_version(json: json::Document<'_>) -> guess::TariffVersion<'_> {
guess::tariff_version(json)
}
pub fn build(
json: json::Document<'_>,
version: crate::Version,
) -> Caveat<Versioned<'_>, schema::Warning> {
let (version, warnings) = match version {
crate::Version::V221 => {
let (tariff, warnings) = schema::v221::build_tariff(&json).into_parts();
(Version::V221(tariff), warnings)
}
crate::Version::V211 => {
let (tariff, warnings) = schema::v211::build_tariff(&json).into_parts();
(Version::V211(tariff), warnings)
}
};
let versioned = Versioned { doc: json, version };
versioned.into_caveat(warnings)
}
pub fn build_versioned(json: VersionedJson<'_>) -> Caveat<Versioned<'_>, schema::Warning> {
let VersionedJson { doc, version } = json;
build(doc, version)
}
#[derive(Clone)]
pub struct VersionedJson<'buf> {
doc: json::Document<'buf>,
version: crate::Version,
}
#[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(tariff) => fmt::Debug::fmt(&tariff, f),
Version::V221(tariff) => fmt::Debug::fmt(&tariff, 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 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 tariff IRs differ in size; this short-lived versioned \
value is not worth boxing"
)]
#[derive(Clone)]
enum Version<'buf> {
V211(schema::v211::Tariff<'buf>),
V221(schema::v221::Tariff<'buf>),
}
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(doc: json::Document<'buf>, version: crate::Version) -> Self {
Self { doc, 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(elem: json::Document<'buf>) -> Self {
Self { doc: elem }
}
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 }
}
}
pub fn lint(tariff: &Versioned<'_>) -> lint::tariff::Report {
lint::tariff(tariff)
}
pub fn explain(tariff: &Versioned<'_>) -> crate::Verdict<String, Warning> {
explain::tariff(tariff)
}