codetics-ocpp21 0.1.0

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

//! OCPP 2.1 protocol messages (Edition 2).
//!
//! Pure Rust definitions of every OCPP 2.1 message, including the SEND
//! message type (see [`StationSend`]). Type names are the specification
//! datatype names without their trailing `Type`.
//!
//! - [`enums`], [`datatypes`], [`messages`]: payload types with serde mappings
//!   and [`ocpp_core::Validate`] impls for length / range / cardinality constraints.
//! - [`actions`]: [`Action`] with message direction, and the typed
//!   [`StationCall`] / [`CsmsCall`] / [`StationSend`] dispatch enums.

pub mod actions;
pub mod datatypes;
pub mod enums;
pub mod messages;

pub use actions::*;
pub use datatypes::*;
pub use enums::*;
pub use messages::*;

/// WebSocket subprotocol of this version.
pub const SUBPROTOCOL: &str = "ocpp2.1";

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn send_message() {
        // The Part 4 ยง4.2.4 example uses timestamps for `t` and omits `basetime`; the
        // normative message definition makes `t` as a numeric offset from a required `basetime`.
        let p = r#"{"id":123,"pending":0,"basetime":"2024-08-27T12:30:40Z","data":[{"t":0,"v":"230.4"},{"t":5,"v":"230.2"}]}"#;
        assert!(
            StationSend::decode(
                "NotifyPeriodicEventStream",
                r#"{"id":123,"pending":0,"data":[{"t":"2024-08-27T12:30:40Z","v":"230.4"}]}"#
            )
            .is_err()
        );
        let StationSend::NotifyPeriodicEventStream(s) =
            StationSend::decode("NotifyPeriodicEventStream", p).unwrap();
        assert_eq!(s.data.len(), 2);
        assert!(Action::NotifyPeriodicEventStream.is_send());
        assert!(!Action::BootNotification.is_send());
    }

    #[test]
    fn new_21_actions_directions() {
        assert_eq!(Action::BatterySwap.direction(), Direction::StationToCsms);
        assert_eq!(Action::AFRRSignal.direction(), Direction::CsmsToStation);
        assert_eq!(Action::DataTransfer.direction(), Direction::Both);
        assert!(
            StationCall::decode("VatNumberValidation", r#"{"vatNumber":"NL123","evseId":1}"#)
                .is_ok()
        );
    }

    #[test]
    fn minimum_constraint() {
        let Err(DecodeError::Invalid(v)) =
            CsmsCall::decode("AdjustPeriodicEventStream", r#"{"id":-1,"params":{}}"#)
        else {
            panic!()
        };
        assert_eq!(v.kind, ocpp_core::ViolationKind::Property);
    }
}

/// Compiles and runs the examples of the README.
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
pub struct ReadmeDoctests;