#[cfg(test)]
mod test_real_world;
#[cfg(test)]
mod test_every_field_set;
use chrono::{DateTime, NaiveDate, NaiveTime, TimeDelta, Utc};
use rust_decimal::Decimal;
use crate::{
country, currency,
duration::Seconds,
energy::{Ampere, Kw, Kwh},
expect_array_or_bail, expect_object_or_bail, into_caveat, into_caveat_all,
json::{self, FieldsAsExt},
number::FromDecimal,
parse_nullable_or_bail, parse_required_or_bail, required_field_or_bail, string,
warning::{self, GatherWarnings as _, IntoCaveat},
Enum, Money, Price, VatApplicable, Verdict, Weekday,
};
use super::{v2x, Warning};
#[derive(Debug)]
pub(crate) struct Tariff<'buf> {
pub country_code: Option<country::Code>,
pub party_id: Option<string::CiExactLen<'buf, 3>>,
pub id: string::CiMaxLen<'buf, 36>,
pub currency: currency::Code,
pub min_price: Option<Price>,
pub max_price: Option<Price>,
pub start_date_time: Option<DateTime<Utc>>,
pub end_date_time: Option<DateTime<Utc>>,
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 vat: VatApplicable,
pub price: Money,
pub step_size: u64,
}
#[derive(Debug)]
pub(crate) struct Restrictions {
pub start_time: Option<NaiveTime>,
pub end_time: Option<NaiveTime>,
pub start_date: Option<NaiveDate>,
pub end_date: Option<NaiveDate>,
pub min_kwh: Option<Kwh>,
pub max_kwh: Option<Kwh>,
pub min_current: Option<Ampere>,
pub max_current: Option<Ampere>,
pub min_power: Option<Kw>,
pub max_power: Option<Kw>,
pub min_duration: Option<TimeDelta>,
pub max_duration: Option<TimeDelta>,
pub day_of_week: Option<Vec<Weekday>>,
}
into_caveat_all!(Element, Restrictions, PriceComponent);
into_caveat!(Tariff<'buf>);
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 code_set =
parse_required_or_bail!(elem, fields, "country_code", country::CodeSet, warnings);
let currency_code =
parse_required_or_bail!(elem, fields, "currency", currency::Code, warnings);
let elements_elem = required_field_or_bail!(elem, fields, "elements", warnings);
let id = parse_required_or_bail!(elem, fields, "id", string::CiMaxLen::<'_, 36>, warnings);
let party_id = parse_required_or_bail!(
elem,
fields,
"party_id",
string::CiExactLen::<'_, 3>,
warnings
);
let min_price = parse_nullable_or_bail!(fields, "min_price", Price, warnings);
let max_price = parse_nullable_or_bail!(fields, "max_price", Price, warnings);
let start_date_time =
parse_nullable_or_bail!(fields, "start_date_time", DateTime<Utc>, warnings);
let end_date_time =
parse_nullable_or_bail!(fields, "end_date_time", DateTime<Utc>, warnings);
let country_code = match code_set {
country::CodeSet::Alpha2(code) | country::CodeSet::Alpha3(code) => code,
};
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 {
country_code: Some(country_code),
currency: currency_code,
party_id: Some(party_id),
id,
min_price,
max_price,
start_date_time,
end_date_time,
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 vat_elem = fields.get("vat");
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 vat = vat_elem
.map(|e| VatApplicable::from_json(e))
.transpose()?
.gather_warnings_into(&mut warnings)
.unwrap_or(VatApplicable::Inapplicable);
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,
vat,
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_current = parse_nullable_or_bail!(fields, "min_current", Ampere, warnings);
let max_current = parse_nullable_or_bail!(fields, "max_current", Ampere, 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_current,
max_current,
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))
}
}