ocpi_kit/lib.rs
1//! A toolkit for the [OCPI](https://evroaming.org/ocpi/) (Open Charge Point Interface) protocol
2//! used for EV roaming between Charge Point Operators, e-Mobility Service Providers and roaming
3//! hubs.
4//!
5//! # What is here
6//!
7//! | Layer | Feature | What it gives you |
8//! |---|---|---|
9//! | [`types`] | *(always)* | `CiString`, `DateTime`, `Number`, `Url`, `Extensions`, validation |
10//! | [`v2_3_0`] | `v2_3_0` | the OCPI 2.3.0 wire model, all ten modules |
11//! | [`v2_2_1`] | `v2_2_1` | the OCPI 2.2.1 wire model |
12//! | [`v2_1_1`] | `v2_1_1` | the OCPI 2.1.1 wire model |
13//! | [`convert`] | `convert` | `Upgrade`/`Downgrade` between versions, with loss accounting |
14//! | [`transport`] | `transport` | envelope, headers, credentials tokens, pagination, routing, PATCH |
15//! | [`client`] | `client` | an async client over `reqwest`, with the registration handshake |
16//! | [`server`] | `server` | an `axum` router driven by one trait per module and interface |
17//! | [`hub`] | `hub` | routing, broadcast push, open routing, GET All, version bridging |
18//! | [`tariffs`] | `tariffs` | an auditable pricing engine over CDRs and Sessions |
19//! | [`testkit`] | `testkit` | sample objects, in-memory stores and a conformant mock peer |
20//!
21//! # Four properties worth knowing about
22//!
23//! **Money is never a float.** Every `number` in every object is a [`types::Number`], an exact
24//! decimal. No public field of any OCPI object in this crate is an `f32` or `f64`, and the
25//! modules where money is computed deny floats by lint.
26//!
27//! **Nothing a peer sent is thrown away.** Undocumented JSON fields land in
28//! [`types::Extensions`] and are written back verbatim; an open-enum value this crate does not
29//! know keeps its text in a `Custom` variant. A hub built on `ocpi-kit` forwards a vendor
30//! extension it has never seen without damaging it, which is what OCPI 2.3.0's extensibility
31//! chapter asks for.
32//!
33//! **Parsing and conformance are separate questions.** A peer that overruns a `string(45)`
34//! cannot make a whole page of Locations undecodable; the value arrives, and
35//! [`types::Validate::validate`] reports it with a JSON Pointer. See [`types::validate`].
36//!
37//! **The peer's OCPI version is not your problem.** [`client`], [`server`] and [`hub`] speak the
38//! canonical [`v2_3_0`] model and translate at the wire, so a 2.2.1 peer — most of the market —
39//! reads and writes as 2.3.0 objects, and anything that cannot cross is reported rather than
40//! dropped. See [`convert`].
41//!
42//! # Getting started
43//!
44// The example needs the 2.3.0 model, which is a default feature but can be switched off; the
45// fence becomes `ignore` rather than the example disappearing, so the docs read the same either
46// way.
47#![cfg_attr(feature = "v2_3_0", doc = "```rust")]
48#![cfg_attr(not(feature = "v2_3_0"), doc = "```rust,ignore")]
49//! use ocpi_kit::types::Validate;
50//! use ocpi_kit::v2_3_0::locations::Location;
51//!
52//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
53//! let json = std::fs::read_to_string("fixtures/2.3.0/location_example.json")?;
54//! let location: Location = serde_json::from_str(&json)?;
55//!
56//! assert_eq!(location.country_code.as_str(), "BE");
57//! location.validate()?; // every length limit and cross-field rule of the spec
58//! # Ok(())
59//! # }
60//! ```
61//!
62//! # Spec traceability
63//!
64//! Every public item carries a `Spec: <version> §<anchor>` line naming the AsciiDoc anchor in
65//! the OCPI source it implements, so a reviewer — or a partner's compliance team — can go from
66//! a Rust type straight to the sentence that defines it.
67//!
68//! # Further reading
69//!
70//! The [guide](https://hupe1980.github.io/ocpi-kit/docs/) covers the concepts behind these APIs —
71//! the parse/validate/construct rule, open enums, extensions, version bridging — plus per-layer
72//! walkthroughs, the interop quirks registry and the specification errata.
73//!
74//! OCPI is a protocol owned and maintained by the [EVRoaming Foundation](https://evroaming.org/).
75//! This project is not affiliated with the EVRoaming Foundation.
76
77#![cfg_attr(docsrs, feature(doc_cfg))]
78
79pub mod types;
80
81#[cfg(feature = "v2_3_0")]
82#[cfg_attr(docsrs, doc(cfg(feature = "v2_3_0")))]
83pub mod v2_3_0;
84
85#[cfg(feature = "v2_2_1")]
86#[cfg_attr(docsrs, doc(cfg(feature = "v2_2_1")))]
87pub mod v2_2_1;
88
89#[cfg(feature = "v2_1_1")]
90#[cfg_attr(docsrs, doc(cfg(feature = "v2_1_1")))]
91pub mod v2_1_1;
92
93#[cfg(feature = "convert")]
94#[cfg_attr(docsrs, doc(cfg(feature = "convert")))]
95pub mod convert;
96
97#[cfg(feature = "transport")]
98#[cfg_attr(docsrs, doc(cfg(feature = "transport")))]
99pub mod transport;
100
101#[cfg(feature = "client")]
102#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
103pub mod client;
104
105#[cfg(feature = "server")]
106#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
107pub mod server;
108
109#[cfg(feature = "hub")]
110#[cfg_attr(docsrs, doc(cfg(feature = "hub")))]
111pub mod hub;
112
113#[cfg(feature = "tariffs")]
114#[cfg_attr(docsrs, doc(cfg(feature = "tariffs")))]
115pub mod tariffs;
116
117#[cfg(feature = "testkit")]
118#[cfg_attr(docsrs, doc(cfg(feature = "testkit")))]
119pub mod testkit;
120
121mod version;
122
123pub use version::{InterfaceRole, ModuleId, VersionNumber};
124
125/// The version of OCPI this crate treats as canonical: every other version is described as a
126/// delta from it.
127#[cfg(feature = "v2_3_0")]
128pub const CANONICAL_VERSION: VersionNumber = VersionNumber::V2_3_0;