codetics-ocpp16 0.1.0

Codetics OCPP: OCPP 1.6 (OCPP-J) message types with validation and typed dispatch
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Kevin Vavelin, Codetics (https://codetics.fr)

//! OCPP 1.6 composite datatypes.

use ocpp_core::DateTime;
use ocpp_core::validate::{self as v, Path, Validate, Violation};
use serde::{Deserialize, Serialize};

use crate::enums::*;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AuthorizationData {
    #[serde(rename = "idTag")]
    pub id_tag: String,
    #[serde(rename = "idTagInfo", default, skip_serializing_if = "Option::is_none")]
    pub id_tag_info: Option<IdTagInfo>,
}

impl Validate for AuthorizationData {
    fn validate(&self, path: &Path<'_>) -> Result<(), Violation> {
        {
            let x = &self.id_tag;
            let p = path.field("idTag");
            v::max_length(x, 20, &p)?;
        }
        if let Some(x) = &self.id_tag_info {
            let p = path.field("idTagInfo");
            x.validate(&p)?;
        }
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ChargingProfile {
    #[serde(rename = "chargingProfileId")]
    pub charging_profile_id: i64,
    #[serde(
        rename = "transactionId",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub transaction_id: Option<i64>,
    #[serde(rename = "stackLevel")]
    pub stack_level: i64,
    #[serde(rename = "chargingProfilePurpose")]
    pub charging_profile_purpose: ChargingProfilePurposeType,
    #[serde(rename = "chargingProfileKind")]
    pub charging_profile_kind: ChargingProfileKindType,
    #[serde(
        rename = "recurrencyKind",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub recurrency_kind: Option<RecurrencyKindType>,
    #[serde(rename = "validFrom", default, skip_serializing_if = "Option::is_none")]
    pub valid_from: Option<DateTime>,
    #[serde(rename = "validTo", default, skip_serializing_if = "Option::is_none")]
    pub valid_to: Option<DateTime>,
    #[serde(rename = "chargingSchedule")]
    pub charging_schedule: ChargingSchedule,
}

impl Validate for ChargingProfile {
    fn validate(&self, path: &Path<'_>) -> Result<(), Violation> {
        {
            let x = &self.charging_schedule;
            let p = path.field("chargingSchedule");
            x.validate(&p)?;
        }
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ChargingSchedule {
    #[serde(rename = "duration", default, skip_serializing_if = "Option::is_none")]
    pub duration: Option<i64>,
    #[serde(
        rename = "startSchedule",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub start_schedule: Option<DateTime>,
    #[serde(rename = "chargingRateUnit")]
    pub charging_rate_unit: ChargingRateUnitType,
    #[serde(rename = "chargingSchedulePeriod")]
    pub charging_schedule_period: Vec<ChargingSchedulePeriod>,
    #[serde(
        rename = "minChargingRate",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub min_charging_rate: Option<f64>,
}

impl Validate for ChargingSchedule {
    fn validate(&self, path: &Path<'_>) -> Result<(), Violation> {
        {
            let x = &self.charging_schedule_period;
            let p = path.field("chargingSchedulePeriod");
            for (i0, it0) in x.iter().enumerate() {
                let p0 = p.index(i0);
                it0.validate(&p0)?;
            }
        }
        if let Some(x) = &self.min_charging_rate {
            let p = path.field("minChargingRate");
            v::multiple_of(*x, 0.1, &p)?;
        }
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ChargingSchedulePeriod {
    #[serde(rename = "startPeriod")]
    pub start_period: i64,
    #[serde(rename = "limit")]
    pub limit: f64,
    #[serde(
        rename = "numberPhases",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub number_phases: Option<i64>,
}

impl Validate for ChargingSchedulePeriod {
    fn validate(&self, path: &Path<'_>) -> Result<(), Violation> {
        {
            let x = &self.limit;
            let p = path.field("limit");
            v::multiple_of(*x, 0.1, &p)?;
        }
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct IdTagInfo {
    #[serde(
        rename = "expiryDate",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub expiry_date: Option<DateTime>,
    #[serde(
        rename = "parentIdTag",
        default,
        skip_serializing_if = "Option::is_none"
    )]
    pub parent_id_tag: Option<String>,
    #[serde(rename = "status")]
    pub status: AuthorizationStatus,
}

impl Validate for IdTagInfo {
    fn validate(&self, path: &Path<'_>) -> Result<(), Violation> {
        if let Some(x) = &self.parent_id_tag {
            let p = path.field("parentIdTag");
            v::max_length(x, 20, &p)?;
        }
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct KeyValue {
    #[serde(rename = "key")]
    pub key: String,
    #[serde(rename = "readonly")]
    pub readonly: bool,
    #[serde(rename = "value", default, skip_serializing_if = "Option::is_none")]
    pub value: Option<String>,
}

impl Validate for KeyValue {
    fn validate(&self, path: &Path<'_>) -> Result<(), Violation> {
        {
            let x = &self.key;
            let p = path.field("key");
            v::max_length(x, 50, &p)?;
        }
        if let Some(x) = &self.value {
            let p = path.field("value");
            v::max_length(x, 500, &p)?;
        }
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MeterValue {
    #[serde(rename = "timestamp")]
    pub timestamp: DateTime,
    #[serde(rename = "sampledValue")]
    pub sampled_value: Vec<SampledValue>,
}

impl Validate for MeterValue {
    fn validate(&self, _path: &Path<'_>) -> Result<(), Violation> {
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SampledValue {
    #[serde(rename = "value")]
    pub value: String,
    #[serde(rename = "context", default, skip_serializing_if = "Option::is_none")]
    pub context: Option<ReadingContext>,
    #[serde(rename = "format", default, skip_serializing_if = "Option::is_none")]
    pub format: Option<ValueFormat>,
    #[serde(rename = "measurand", default, skip_serializing_if = "Option::is_none")]
    pub measurand: Option<Measurand>,
    #[serde(rename = "phase", default, skip_serializing_if = "Option::is_none")]
    pub phase: Option<Phase>,
    #[serde(rename = "location", default, skip_serializing_if = "Option::is_none")]
    pub location: Option<Location>,
    #[serde(rename = "unit", default, skip_serializing_if = "Option::is_none")]
    pub unit: Option<UnitOfMeasure>,
}

impl Validate for SampledValue {
    fn validate(&self, _path: &Path<'_>) -> Result<(), Violation> {
        Ok(())
    }
}