#[cfg(test)]
mod test_null_fields;
#[cfg(test)]
mod test_real_world;
use chrono::{Duration, NaiveDate, NaiveTime, TimeDelta};
use rust_decimal::Decimal;
use crate::{
currency,
duration::Seconds,
energy::{Kw, Kwh},
expect_array_or_bail, expect_object_or_bail, into_caveat, into_caveat_all,
json::{self, FieldsAsExt},
number::FromDecimal,
parse_nullable_or_bail, required_field_or_bail, string,
tariff::v2x,
warning::{self, GatherWarnings as _, IntoCaveat},
Enum, Money, VatApplicable, Verdict, Weekday,
};
use super::{v221, Warning};
#[derive(Debug)]
pub(crate) struct Tariff<'buf> {
pub id: string::CiMaxLen<'buf, 36>,
pub currency: currency::Code,
pub elements: Vec<Element>,
}
#[derive(Debug)]
pub(crate) struct Element {
pub price_components: Vec<PriceComponent>,
pub restrictions: Option<Restrictions>,
}
#[derive(Debug)]
pub(crate) struct PriceComponent {
pub dimension_type: v2x::DimensionType,
pub price: Money,
pub step_size: u64,
}
#[derive(Debug)]
pub(crate) struct Restrictions {
start_time: Option<NaiveTime>,
end_time: Option<NaiveTime>,
start_date: Option<NaiveDate>,
end_date: Option<NaiveDate>,
min_kwh: Option<Kwh>,
max_kwh: Option<Kwh>,
min_power: Option<Kw>,
max_power: Option<Kw>,
min_duration: Option<Duration>,
max_duration: Option<Duration>,
day_of_week: Option<Vec<Weekday>>,
}
into_caveat_all!(Element, Restrictions, PriceComponent);
into_caveat!(Tariff<'buf>);
impl<'a> From<Tariff<'a>> for v221::Tariff<'a> {
fn from(tariff: Tariff<'a>) -> Self {
let Tariff {
id,
currency,
elements,
} = tariff;
let elements = elements.into_iter().map(v221::Element::from).collect();
v221::Tariff {
country_code: None,
party_id: None,
id,
currency,
min_price: None,
max_price: None,
start_date_time: None,
end_date_time: None,
elements,
}
}
}
impl From<Element> for v221::Element {
fn from(element: Element) -> Self {
let Element {
price_components,
restrictions,
} = element;
v221::Element {
price_components: price_components
.into_iter()
.map(v221::PriceComponent::from)
.collect(),
restrictions: restrictions.map(v221::Restrictions::from),
}
}
}
impl From<PriceComponent> for v221::PriceComponent {
fn from(value: PriceComponent) -> Self {
let PriceComponent {
dimension_type,
price,
step_size,
} = value;
v221::PriceComponent {
dimension_type,
vat: VatApplicable::Unknown,
price,
step_size,
}
}
}
impl From<Restrictions> for v221::Restrictions {
fn from(restrictions: Restrictions) -> Self {
let Restrictions {
start_time,
end_time,
start_date,
end_date,
min_kwh,
max_kwh,
min_power,
max_power,
min_duration,
max_duration,
day_of_week,
} = restrictions;
v221::Restrictions {
start_time,
end_time,
start_date,
end_date,
min_kwh,
max_kwh,
min_current: None,
max_current: None,
min_power,
max_power,
min_duration,
max_duration,
day_of_week,
}
}
}
impl<'buf, 'caller: 'buf> json::FromJson<'caller, 'buf> for Tariff<'buf> {
type Warning = Warning;
fn from_json(elem: &'caller json::Element<'buf>) -> Verdict<Self, Self::Warning> {
let mut warnings = warning::Set::<Warning>::new();
let fields = expect_object_or_bail!(elem, warnings);
let fields = fields.as_raw_map();
let currency_elem = required_field_or_bail!(elem, fields, "currency", warnings);
let elements_elem = required_field_or_bail!(elem, fields, "elements", warnings);
let id_elem = required_field_or_bail!(elem, fields, "id", warnings);
let currency_code =
currency::Code::from_json(currency_elem)?.gather_warnings_into(&mut warnings);
let id = string::CiMaxLen::from_json(id_elem)?.gather_warnings_into(&mut warnings);
let elements = expect_array_or_bail!(elements_elem, warnings)
.iter()
.map(Element::from_json)
.collect::<Result<Vec<_>, _>>()?;
let elements = elements.gather_warnings_into(&mut warnings);
if elements.is_empty() {
return warnings.bail(Warning::NoElements, elements_elem);
}
let tariff = Tariff {
currency: currency_code,
id,
elements,
};
Ok(tariff.into_caveat(warnings))
}
}
impl json::FromJson<'_, '_> for Element {
type Warning = Warning;
fn from_json(elem: &'_ json::Element<'_>) -> Verdict<Self, Self::Warning> {
let mut warnings = warning::Set::<Warning>::new();
let fields = expect_object_or_bail!(elem, warnings);
let fields = fields.as_raw_map();
let price_components_elem =
required_field_or_bail!(elem, fields, "price_components", warnings);
let restrictions_elem = fields.get("restrictions");
let price_components = expect_array_or_bail!(price_components_elem, warnings)
.iter()
.map(Option::<PriceComponent>::from_json)
.collect::<Result<Vec<_>, _>>()?;
let price_components = price_components
.gather_warnings_into(&mut warnings)
.into_iter()
.flatten()
.collect();
let restrictions = if let Some(elem) = restrictions_elem {
Some(Restrictions::from_json(elem)?.gather_warnings_into(&mut warnings))
} else {
None
};
let elem = Element {
price_components,
restrictions,
};
Ok(elem.into_caveat(warnings))
}
}
impl json::FromJson<'_, '_> for Option<PriceComponent> {
type Warning = Warning;
fn from_json(elem: &'_ json::Element<'_>) -> Verdict<Self, Self::Warning> {
let mut warnings = warning::Set::<Warning>::new();
let fields = expect_object_or_bail!(elem, warnings);
let fields = fields.as_raw_map();
let type_elem = required_field_or_bail!(elem, fields, "type", warnings);
let price_elem = required_field_or_bail!(elem, fields, "price", warnings);
let step_size_elem = required_field_or_bail!(elem, fields, "step_size", warnings);
let dimension_type =
Enum::<v2x::DimensionType>::from_json(type_elem)?.gather_warnings_into(&mut warnings);
let dimension_type = match dimension_type {
Enum::Known(v) => v,
Enum::Unknown(s) => {
warnings.insert(
Warning::field_invalid_value(s, "A tariff DimensionType should be one of `ENERGY`, `FLAT`, `PACKING_TIME` or `TIME`"),
elem
);
return Ok(None.into_caveat(warnings));
}
};
let price = Decimal::from_json(price_elem)?.gather_warnings_into(&mut warnings);
let step_size = u64::from_json(step_size_elem)?.gather_warnings_into(&mut warnings);
let comp = PriceComponent {
dimension_type,
price: Money::from_decimal(price),
step_size,
};
Ok(Some(comp).into_caveat(warnings))
}
}
impl json::FromJson<'_, '_> for Restrictions {
type Warning = Warning;
fn from_json(elem: &'_ json::Element<'_>) -> Verdict<Self, Self::Warning> {
let mut warnings = warning::Set::<Warning>::new();
let fields = expect_object_or_bail!(elem, warnings);
let fields = fields.as_raw_map();
let start_time = parse_nullable_or_bail!(fields, "start_time", NaiveTime, warnings);
let end_time = parse_nullable_or_bail!(fields, "end_time", NaiveTime, warnings);
let start_date = parse_nullable_or_bail!(fields, "start_date", NaiveDate, warnings);
let end_date = parse_nullable_or_bail!(fields, "end_date", NaiveDate, warnings);
let min_kwh = parse_nullable_or_bail!(fields, "min_kwh", Kwh, warnings);
let max_kwh = parse_nullable_or_bail!(fields, "max_kwh", Kwh, warnings);
let min_power = parse_nullable_or_bail!(fields, "min_power", Kw, warnings);
let max_power = parse_nullable_or_bail!(fields, "max_power", Kw, warnings);
let min_duration = parse_nullable_or_bail!(fields, "min_duration", Seconds, warnings);
let max_duration = parse_nullable_or_bail!(fields, "max_duration", Seconds, warnings);
let day_of_week_elem = fields.get("day_of_week");
let day_of_week = if let Some(elem) = day_of_week_elem {
let list = expect_array_or_bail!(elem, warnings)
.iter()
.map(Weekday::from_json)
.collect::<Result<Vec<_>, _>>()?;
Some(list.gather_warnings_into(&mut warnings))
} else {
None
};
let res = Restrictions {
start_time,
end_time,
start_date,
end_date,
min_kwh,
max_kwh,
min_power,
max_power,
min_duration: min_duration.map(TimeDelta::from),
max_duration: max_duration.map(TimeDelta::from),
day_of_week,
};
Ok(res.into_caveat(warnings))
}
}