ocpi-tariffs 0.49.0

OCPI tariff calculations
Documentation
use super::{Warning, CDR, TARIFF, TARIFF_TYPE_VALUES};
use crate::{json, schema};

#[test]
fn tariff_schema_fields_are_sorted() {
    let doc = json::parse("{}".into()).unwrap();
    // The debug_assert inside walk_object checks field sorting.
    drop(schema::walk(&doc, &TARIFF).into_warnings());
}

#[test]
fn cdr_schema_fields_are_sorted() {
    let doc = json::parse("{}".into()).unwrap();
    drop(schema::walk(&doc, &CDR).into_warnings());
}

#[test]
fn minimal_valid_tariff_is_clean() {
    // `elements` is `+`, so a valid Tariff carries at least one element.
    let src = r#"{
        "country_code": "NL",
        "currency": "EUR",
        "elements": [
            {"price_components": [{"price": 0.25, "step_size": 1, "type": "ENERGY"}]}
        ],
        "id": "T1",
        "last_updated": "2024-01-01T00:00:00Z",
        "party_id": "CPO"
    }"#;
    let doc = json::parse(src.into()).unwrap();
    let warnings = schema::walk(&doc, &TARIFF).into_warnings();
    assert!(warnings.is_empty());
}

#[test]
fn tariff_typo_reports_unexpected_and_missing() {
    let src = r#"{
        "country_code": "NL",
        "currencyy": "EUR",
        "elements": [
            {"price_components": [{"price": 0.25, "step_size": 1, "type": "ENERGY"}]}
        ],
        "id": "T1",
        "last_updated": "2024-01-01T00:00:00Z",
        "party_id": "CPO"
    }"#;
    let doc = json::parse(src.into()).unwrap();
    let warnings = schema::walk(&doc, &TARIFF).into_warnings();
    let by_path = warnings.path_map();
    assert_eq!(warnings.len_warnings(), 2);
    assert_eq!(by_path["$.currencyy"], vec![&Warning::UnexpectedField]);
    assert_eq!(
        by_path["$"],
        vec![&Warning::MissingField { name: "currency" }]
    );
}

#[test]
fn cdr_missing_id_reported() {
    let src = r#"{
        "auth_method": "AUTH_REQUEST",
        "cdr_location": {
            "address": "Main St 1",
            "city": "Amsterdam",
            "connector_format": "SOCKET",
            "connector_id": "1",
            "connector_power_type": "AC_1_PHASE",
            "connector_standard": "IEC_62196_T2",
            "coordinates": {"latitude": "52.3", "longitude": "4.9"},
            "country": "NL",
            "evse_id": "NL*CPO*E001",
            "evse_uid": "E001",
            "id": "LOC1"
        },
        "cdr_token": {
            "contract_id": "NL-CPO-C001",
            "country_code": "NL",
            "party_id": "CPO",
            "type": "RFID",
            "uid": "RFID001"
        },
        "charging_periods": [
            {
                "start_date_time": "2024-01-01T09:00:00Z",
                "dimensions": [{"type": "ENERGY", "volume": 10.0}]
            }
        ],
        "country_code": "NL",
        "currency": "EUR",
        "end_date_time": "2024-01-01T10:00:00Z",
        "last_updated": "2024-01-01T10:00:00Z",
        "party_id": "CPO",
        "start_date_time": "2024-01-01T09:00:00Z",
        "total_cost": {"excl_vat": 5.0},
        "total_energy": 10.0,
        "total_time": 1.0
    }"#;
    let doc = json::parse(src.into()).unwrap();
    let warnings = schema::walk(&doc, &CDR).into_warnings();
    assert_eq!(warnings.len_warnings(), 1);
    assert_eq!(
        warnings.path_map()["$"],
        vec![&Warning::MissingField { name: "id" }]
    );
}

#[test]
fn day_of_week_non_string_element_reported() {
    // `day_of_week` is an array of `DayOfWeek` enum strings; a numeric element
    // is a type mismatch.
    let src = r#"{
        "country_code": "NL",
        "currency": "EUR",
        "elements": [
            {
                "price_components": [{"price": 0.25, "step_size": 1, "type": "ENERGY"}],
                "restrictions": {"day_of_week": ["MONDAY", 5]}
            }
        ],
        "id": "T1",
        "last_updated": "2024-01-01T00:00:00Z",
        "party_id": "CPO"
    }"#;
    let doc = json::parse(src.into()).unwrap();
    let warnings = schema::walk(&doc, &TARIFF).into_warnings();
    assert_eq!(warnings.len_warnings(), 1);
    assert_eq!(
        warnings.path_map()["$.elements[0].restrictions.day_of_week[1]"],
        vec![&Warning::TypeMismatch {
            expected: json::ValueKind::String,
            actual: json::ValueKind::Number,
        }]
    );
}

#[test]
fn tariff_unknown_type_reported() {
    // "SUBSCRIPTION" is not a valid `TariffType` variant.
    let src = r#"{
        "country_code": "NL",
        "currency": "EUR",
        "elements": [
            {"price_components": [{"price": 0.25, "step_size": 1, "type": "ENERGY"}]}
        ],
        "id": "T1",
        "last_updated": "2024-01-01T00:00:00Z",
        "party_id": "CPO",
        "type": "SUBSCRIPTION"
    }"#;
    let doc = json::parse(src.into()).unwrap();
    let warnings = schema::walk(&doc, &TARIFF).into_warnings();
    assert_eq!(warnings.len_warnings(), 1);
    assert_eq!(
        warnings.path_map()["$.type"],
        vec![&Warning::FieldInvalidValue {
            expected: TARIFF_TYPE_VALUES,
            actual: "SUBSCRIPTION".to_owned(),
        }]
    );
}

#[test]
fn cdr_auth_method_lowercase_is_clean() {
    // The enum value check is case-insensitive: "auth_request" matches.
    let src = r#"{
        "auth_method": "auth_request",
        "cdr_location": {
            "address": "Main St 1",
            "city": "Amsterdam",
            "connector_format": "SOCKET",
            "connector_id": "1",
            "connector_power_type": "AC_1_PHASE",
            "connector_standard": "IEC_62196_T2",
            "coordinates": {"latitude": "52.3", "longitude": "4.9"},
            "country": "NL",
            "evse_id": "NL*CPO*E001",
            "evse_uid": "E001",
            "id": "LOC1"
        },
        "cdr_token": {
            "contract_id": "NL-CPO-C001",
            "country_code": "NL",
            "party_id": "CPO",
            "type": "RFID",
            "uid": "RFID001"
        },
        "charging_periods": [
            {
                "start_date_time": "2024-01-01T09:00:00Z",
                "dimensions": [{"type": "ENERGY", "volume": 10.0}]
            }
        ],
        "country_code": "NL",
        "currency": "EUR",
        "end_date_time": "2024-01-01T10:00:00Z",
        "id": "CDR001",
        "last_updated": "2024-01-01T10:00:00Z",
        "party_id": "CPO",
        "start_date_time": "2024-01-01T09:00:00Z",
        "total_cost": {"excl_vat": 5.0},
        "total_energy": 10.0,
        "total_time": 1.0
    }"#;
    let doc = json::parse(src.into()).unwrap();
    let warnings = schema::walk(&doc, &CDR).into_warnings();
    assert!(warnings.is_empty());
}