Skip to main content

ocpi_kit/v2_1_1/
mod.rs

1//! The OCPI **2.1.1** wire model: the legacy version, and still in the field.
2//!
3//! OCPI 2.1.1 predates almost everything the later versions are built on. It has:
4//!
5//! * **no owner fields on objects** — `country_code` and `party_id` came with 2.2, so in 2.1.1
6//!   the owner is known only from the URL and the credentials handshake;
7//! * **no message routing** — the four `OCPI-*` headers do not exist, so a connection is
8//!   strictly peer-to-peer;
9//! * **a flat `Credentials` object** — one party, one role, no `roles` list;
10//! * **no `Price`** — a cost is a bare `number` excluding VAT, and a `PriceComponent` has no
11//!   `vat` field at all;
12//! * **no `CommandResult`** — the asynchronous callback reuses `CommandResponse`, whose
13//!   `CommandResponseType` therefore includes `TIMEOUT`;
14//! * **seven modules** — no Hub Client Info, no Charging Profiles, no Payments;
15//! * **`start_datetime`/`end_datetime`** on a Session, without the second underscore every later
16//!   version uses.
17//!
18//! Everything here is decoded leniently: 2.1.1 declares every enum closed, and a peer still
19//! running it in 2026 will have plugs, token types and dimensions that the 2015 list does not
20//! contain. See [`ocpi_lenient_enum!`](crate::ocpi_lenient_enum).
21//!
22//! # Talking to a 2.1.1 peer
23//!
24//! [`Quirks::for_version`](crate::transport::Quirks::for_version) sets the two flags such a peer
25//! needs: it does not Base64-encode the `Authorization` token, and it has no routing headers.
26//!
27//! Spec: <https://github.com/ocpi/ocpi>, `release-2.1.1-bugfixes`
28
29pub mod cdrs;
30pub mod commands;
31pub mod credentials;
32pub mod locations;
33pub mod sessions;
34pub mod tariffs;
35pub mod tokens;
36
37/// The *Versions* module of OCPI 2.1.1.
38///
39/// Wire-identical to the later versions except that `Endpoint` has no `role` field:
40///
41/// > *NOTE: OCPI 2.2 introduced the role field in the version details. Older versions of OCPI do
42/// > not support this.*
43///
44/// This crate models `Endpoint.role` as required and defaults it to `SENDER` when a 2.1.1 peer
45/// omits it, which is what the specification advises for the credentials module and the only
46/// sensible reading for a version with no interface roles at all.
47///
48/// Spec: 2.1.1 §version_information_endpoint
49pub mod versions {
50    use serde::{Deserialize, Serialize};
51
52    use crate::types::validate_fields;
53    use crate::types::{Extensions, Url, Validate, Validator};
54    use crate::{InterfaceRole, ModuleId, VersionNumber};
55
56    pub use crate::v2_3_0::versions::Version;
57
58    /// The endpoints a 2.1.1 party implements for one version.
59    ///
60    /// Spec: 2.1.1 §version_information_endpoint
61    #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
62    #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
63    pub struct VersionDetails {
64        /// The version number these endpoints belong to.
65        pub version: VersionNumber,
66        /// The supported endpoints for this version.
67        pub endpoints: Vec<Endpoint>,
68        /// Undocumented JSON fields, preserved verbatim.
69        #[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
70        pub extensions: Extensions,
71    }
72
73    impl VersionDetails {
74        /// The URL of a module's endpoint.
75        ///
76        /// There are no interface roles in 2.1.1, so a module has at most one endpoint.
77        #[must_use]
78        pub fn url(&self, module: &ModuleId) -> Option<&Url> {
79            self.endpoints.iter().find(|e| e.identifier.matches(module)).map(|e| &e.url)
80        }
81    }
82
83    impl Validate for VersionDetails {
84        fn validate_in(&self, v: &mut Validator) {
85            validate_fields!(self, v, version, endpoints);
86        }
87    }
88
89    /// One module endpoint of a 2.1.1 party.
90    ///
91    /// Spec: 2.1.1 §version_information_endpoint
92    #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
93    #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
94    pub struct Endpoint {
95        /// Endpoint identifier.
96        pub identifier: ModuleId,
97        /// URL to the endpoint.
98        pub url: Url,
99        /// Undocumented JSON fields, preserved verbatim.
100        #[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
101        pub extensions: Extensions,
102    }
103
104    impl Endpoint {
105        /// Creates an endpoint entry.
106        #[must_use]
107        pub fn new(identifier: ModuleId, url: Url) -> Self {
108            Self { identifier, url, extensions: Extensions::new() }
109        }
110
111        /// The interface role this endpoint would have in OCPI 2.2 and later.
112        ///
113        /// Always `SENDER`, since 2.1.1 has no roles and the specification advises sending
114        /// `SENDER` where one is required.
115        #[must_use]
116        pub const fn assumed_role(&self) -> InterfaceRole {
117            InterfaceRole::Sender
118        }
119    }
120
121    impl Validate for Endpoint {
122        fn validate_in(&self, v: &mut Validator) {
123            validate_fields!(self, v, identifier, url);
124        }
125    }
126
127    #[cfg(test)]
128    mod tests {
129        use super::*;
130
131        #[test]
132        fn a_2_1_1_endpoint_has_no_role_field() {
133            let json = r#"{"version":"2.1.1","endpoints":[{"identifier":"credentials","url":"https://example.com/ocpi/2.1.1/credentials"}]}"#;
134            let details: VersionDetails = serde_json::from_str(json).unwrap();
135            assert_eq!(details.endpoints[0].assumed_role(), InterfaceRole::Sender);
136            assert!(details.url(&ModuleId::Credentials).is_some());
137            assert_eq!(serde_json::to_string(&details).unwrap(), json);
138        }
139    }
140}
141
142pub use cdrs::Cdr;
143pub use credentials::Credentials;
144pub use locations::{Connector, Evse, Location};
145pub use sessions::Session;
146pub use tariffs::Tariff;
147pub use tokens::Token;
148pub use versions::{Endpoint, Version, VersionDetails};
149
150/// The version number this module implements.
151pub const VERSION: crate::VersionNumber = crate::VersionNumber::V2_1_1;