Skip to main content

ocpp_types/
lib.rs

1//! Strongly typed OCPP message types for 1.6J, 2.0.1, and 2.1.
2//!
3//! `ocpp-types` provides the request/response payload types for the Open
4//! Charge Point Protocol, generated from the official JSON schemas. It is
5//! `no_std` and allocation-free: field sizes are bounded at the type level
6//! with [`heapless`] collections, sized to the limits stated in each
7//! version's specification.
8//!
9//! Each protocol version lives in its own module -- [`v16`], [`v201`],
10//! [`v21`] -- since the same message name can differ in shape across
11//! versions.
12//!
13//! # Fields with no spec-given bound
14//!
15//! A handful of fields (free-text strings, a few arrays) have no
16//! `maxLength`/`maxItems` in the spec, so there's no size to give a
17//! `heapless` collection without guessing one. These become a const
18//! generic the caller picks (with a default, so most code never needs to
19//! think about it):
20//!
21//! ```
22//! # #[cfg(not(feature = "alloc"))]
23//! # {
24//! use ocpp_types::v16::HeartbeatResponse;
25//!
26//! // Uses the default capacity (1024):
27//! let response: HeartbeatResponse = HeartbeatResponse {
28//!     current_time: heapless::String::try_from("2024-01-01T00:00:00Z").unwrap(),
29//! };
30//!
31//! // Or pick a smaller one explicitly:
32//! let response: HeartbeatResponse<64> = HeartbeatResponse {
33//!     current_time: heapless::String::try_from("2024-01-01T00:00:00Z").unwrap(),
34//! };
35//! # }
36//! ```
37//!
38//! With the `alloc` feature enabled, these fields become plain
39//! `alloc::string::String`/`alloc::vec::Vec<T>` instead, and the const
40//! generic disappears entirely -- useful on targets with a real allocator
41//! (a CSMS backend, a simulator) that would rather not pick a bound at all.
42//!
43//! # Example
44//!
45//! ```
46//! use ocpp_types::Action;
47//! use ocpp_types::v16::{AuthorizeRequest, IdTag};
48//!
49//! let request = AuthorizeRequest {
50//!     id_tag: IdTag::try_from("ABC123").unwrap(),
51//! };
52//!
53//! assert_eq!(AuthorizeRequest::ACTION, "Authorize");
54//! ```
55//!
56//! # Serialization
57//!
58//! With the `serde` feature enabled, every message implements
59//! `serde::Serialize`/`serde::Deserialize`, and [`Action`] gains
60//! zero-allocation JSON helpers backed by
61//! [`serde-json-core`](https://docs.rs/serde-json-core) -- the caller owns
62//! the buffer, nothing is heap-allocated:
63//!
64//! ```ignore
65//! use ocpp_types::Action;
66//!
67//! let mut buf = [0u8; 256];
68//! let json: &str = request.to_json_str(&mut buf)?;
69//! let parsed = AuthorizeRequest::from_json_str(json)?;
70//! ```
71//!
72//! # RPC errors
73//!
74//! Each version also exposes an `RpcErrorCode` enum covering the
75//! `CALLERROR` codes defined by that version's OCPP-J specification (e.g.
76//! [`v16::RpcErrorCode`]), implementing [`core::error::Error`].
77//!
78//! # WebSocket envelopes
79//!
80//! With `serde`, `Call`/`CallResult`/`CallError` model the OCPP-J
81//! array-based envelope every message travels in (`[2, messageId, action,
82//! payload]`, etc.) -- generic over the payload type, so no per-version
83//! duplication is needed. `CallResultError`/`SendMessage` cover 2.1's
84//! additional `CALLRESULTERROR`/`SEND` message types (the shapes work for
85//! any version; whether a given deployment actually uses them is a
86//! protocol-level concern, not something the types enforce). See
87//! `examples/envelope.rs`.
88#![no_std]
89
90#[cfg(feature = "alloc")]
91extern crate alloc;
92
93mod action;
94mod envelope;
95
96#[cfg(test)]
97mod generics_test;
98
99pub mod v16;
100pub mod v201;
101pub mod v21;
102
103pub use action::Action;
104#[cfg(feature = "serde")]
105pub use envelope::{Call, CallError, CallResult, CallResultError, EmptyPayload, SendMessage};
106pub use envelope::MessageId;
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn id_tag_round_trips_a_short_string() {
114        let tag = v16::IdTag::try_from("ABC123").unwrap();
115        assert_eq!(tag.as_str(), "ABC123");
116    }
117
118    #[test]
119    fn id_tag_rejects_strings_over_20_bytes() {
120        let too_long = "A".repeat(21);
121        assert!(v16::IdTag::try_from(too_long.as_str()).is_err());
122    }
123
124    struct DummyRequest;
125
126    impl Action for DummyRequest {
127        const ACTION: &'static str = "Dummy";
128    }
129
130    #[test]
131    fn action_trait_exposes_action_name() {
132        assert_eq!(DummyRequest::ACTION, "Dummy");
133    }
134}