ocpi-kit

Your first request

Decode and validate an OCPI document, register with a peer, and serve an OCPI API.

On this page

Decode and validate a file

Nothing here needs a network or an async runtime — the default features are enough.

use ocpi_kit::types::Validate;
use ocpi_kit::v2_3_0::locations::Location;

let json = std::fs::read_to_string("location.json")?;
let location: Location = serde_json::from_str(&json)?;

assert_eq!(location.country_code.as_str(), "BE");

// Decoding said the shape is right. Validation says whether it is conformant.
match location.validate() {
    Ok(()) => println!("conformant"),
    Err(violations) => {
        for v in violations.iter() {
            println!("{} {:?}: {}", v.pointer, v.code, v.message);
        }
    }
}

The two questions are deliberately separate; see Parse, validate, construct.

Register with a peer and pull

With the client feature:

use ocpi_kit::client::{OcpiClient, Registration};
use ocpi_kit::transport::{CredentialsToken, PageQuery};
use ocpi_kit::types::{PartyRef, Url};
use ocpi_kit::{InterfaceRole, ModuleId};

let client = OcpiClient::new()?;
let me = PartyRef::new("NL", "TNM")?;

let peer = Registration::new(
        Url::new("https://cpo.example.com/ocpi/versions")?,
        CredentialsToken::new("token-a-received-out-of-band")?,
    )
    .discover(client.transport()).await?     // GET /versions
    .select_best(client.transport()).await?; // GET the details of the newest common version

// Check before you commit: refuse a peer that does not implement what you need.
peer.require(&[(ModuleId::Locations, InterfaceRole::Sender)])?;

let peer = peer.register(client.transport(), &my_credentials).await?; // POST /credentials

let mut locations = peer.locations(client.transport(), me).list(PageQuery::new())?;
while let Some(location) = locations.next().await? {
    println!("{} {}", location.id, location.name.as_deref().unwrap_or(""));
}

RegistrationDiscoveredSelectedPeer is a typestate: the compiler will not let you call a module endpoint before discovery, or keep using CREDENTIALS_TOKEN_A after registration. See Client.

select_best negotiates the newest version you both speak — often OCPI 2.2.1, which is still most of the market. Nothing after that line changes: location above is a v2_3_0::locations::Location either way, because the client translates at the wire. See Versions and conversion.

Serve an OCPI API

With the server feature you implement one trait per module and interface, and mount it:

use ocpi_kit::server::{InMemoryTokenStore, OcpiRouter};
use ocpi_kit::{types::Url, VersionNumber};

let app = OcpiRouter::new(
        VersionNumber::V2_3_0,
        Url::new("https://cpo.example.com/ocpi/cpo/2.3.0")?,
        std::sync::Arc::new(InMemoryTokenStore::new()),
    )
    .credentials(my_credentials_handler)
    .locations_sender(my_locations)
    .build();

axum::serve(tokio::net::TcpListener::bind("0.0.0.0:8080").await?, app).await?;

/versions and the version-details endpoint are generated from exactly what was mounted, so discovery cannot disagree with reality. See Server.

Improve this page on GitHub