ocpi-kit

Enums, open and closed

How ocpi-kit models OCPI closed enums, OpenEnums, and values a peer invented after your last release.

On this page

OCPI 2.3.0 distinguishes two kinds of enumeration, and the distinction has real consequences.

A closed enum may only contain the values listed. An OpenEnum may be extended with values not in the specification.

ocpi-kit models them with three macros, producing three behaviours.

ocpi_enum! — closed

The value set is fixed. An unknown value is a decode error, because the specification says nothing else is valid and silently accepting one would hide a real interop problem.

Used for things like InterfaceRole and the command result types.

ocpi_open_enum! — open

The generated type has a Custom(String) variant that keeps the unknown value's text verbatim. Decoding succeeds; validation is silent, because an extension value is legal here.

let connector: ConnectorType = serde_json::from_str("\"ACME_PROPRIETARY\"")?;
assert_eq!(connector, ConnectorType::Custom("ACME_PROPRIETARY".into()));
assert_eq!(serde_json::to_string(&connector)?, "\"ACME_PROPRIETARY\"");  // written back intact

This is what lets a hub relay a vendor extension it has never seen. See Extensions.

ocpi_lenient_enum! — closed, but survivable

OCPI 2.2.1 and 2.1.1 declare every enum closed — there is no OpenEnum in 2.2.1 at all. But a 2.2.1 peer that has started sending a value 2.3.0 added (MCS, SAE_J3400, EMAID, the new parking restrictions) is a fact of deployment, not a hypothetical.

So the 2.2.1 and 2.1.1 enums decode an unknown value into Custom(String) and report it from validate(). You get the object, and you get told it is not conformant for that version. Nothing is hidden and nothing is lost.

The Custom variant, not Other

The catch-all is named Custom because several OCPI enums have a legitimate spec value called OTHERImageCategory::Other, TokenType::Other. A catch-all named Other would have collided with a real value and made the API ambiguous.

Equality goes through the wire value

A value that reached Custom by one route still equals the variant it names:

assert_eq!(ConnectorType::Custom("CHADEMO".into()), ConnectorType::Chademo);

Eq, Hash and Ord all route through as_str(), so a HashSet or a BTreeMap keyed on an open enum behaves the way you would expect regardless of which side of the boundary a value came from.

What actually differs between versions

Diffing the value sets programmatically: the only enums whose members differ between OCPI 2.2.1 and 2.3.0 are ConnectorType (2.3.0 adds MCS and SAE_J3400), ParkingRestriction (adds EMPLOYEES, TAXIS, TENANTS) and TokenType (adds EMAID). Everything else is identical, which is why the 2.2.1 model can re-export most of the 2.3.0 types rather than duplicating them.

Improve this page on GitHub