ocpi-tariffs 0.49.0

OCPI tariff calculations
Documentation
use std::path::Path;

use crate::{
    cdr,
    price::{
        self,
        test::{Expect, ExpectFields, UnwrapReport as _},
    },
    tariff,
    test::{self, IntoFields as _},
    timezone, Version,
};

#[test_each::file(
    glob = "ocpi-tariffs/test_data/v211/real_world/*/cdr*.json",
    name(segments = 2)
)]
fn should_price_v211_cdr(cdr_json: &str, path: &Path) {
    test::setup();

    price_cdr(Version::V211, cdr_json, path);
}

#[test_each::file(
    glob = "ocpi-tariffs/test_data/v221/real_world/*/cdr*.json",
    name(segments = 2)
)]
fn should_price_v221_cdr(cdr_json: &str, path: &Path) {
    test::setup();

    price_cdr(Version::V221, cdr_json, path);
}

#[track_caller]
fn price_cdr(version: Version, cdr_json: &str, path: &Path) {
    let expect_json = test::read_expect_json(path, "price");
    let expect = test::parse_expect_json::<Expect>(expect_json.as_deref());

    let ExpectFields {
        timezone_find_expect,
        tariff_parse_expect,
        cdr_parse_expect,
        cdr_price_expect,
    } = expect.into_fields();

    let tariff_json = std::fs::read_to_string(path.parent().unwrap().join("tariff.json")).ok();
    let tariff = tariff_json.as_deref().map(|json| {
        let doc = crate::json::parse_object(json).unwrap();
        tariff::build(doc, version).into_parts()
    });

    let tariff = if let Some((tariff, warnings)) = tariff {
        price::test::assert_parse_report(
            test::ObjectType::Tariff,
            warnings.unexpected_fields(),
            tariff_parse_expect,
        );
        price::TariffSource::Override(vec![tariff])
    } else {
        assert!(tariff_parse_expect.value.is_none(), "There is no separate tariff to parse so there is no need to define a `tariff_parse` expectation");
        price::TariffSource::UseCdr
    };

    let mut cdr_json = cdr_json.to_owned();
    json_strip_comments::strip(&mut cdr_json).unwrap();
    let doc = crate::json::parse_object(&cdr_json).unwrap();
    let (cdr, warnings) = cdr::build(doc, version).into_parts();
    price::test::assert_parse_report(
        test::ObjectType::Cdr,
        warnings.unexpected_fields(),
        cdr_parse_expect,
    );

    let (timezone_source, warnings) = timezone::find_or_infer(&cdr).unwrap().into_parts();

    timezone::test::assert_find_or_infer_outcome(timezone_source, timezone_find_expect, &warnings);

    // The v221 tariff location does not contain a timezone field, this timezone should be
    // used from the `Location`.
    //
    // The tariff's time related fields are in UTC and can be converted to local time by using
    // the timezone from the `Location` object.
    //
    // > `start_date_time`:
    // >
    // > The time when this tariff becomes active, in UTC, time_zone field of the Location can be used to convert to local time.
    // >
    // > See: <https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/mod_tariffs.asciidoc#131-tariff-object>
    //
    // See: <https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/mod_locations.asciidoc>
    let report =
        cdr::price(&cdr, tariff, timezone_source.into_timezone()).unwrap_report(cdr.as_json_str());
    price::test::assert_price_report(report, cdr_price_expect);
}