codetics-ocpp-service 0.1.0

Codetics OCPP: Charging Station Management System (CSMS) service for OCPP 1.6J, 2.0.1 and 2.1, with TLS security profiles, smart charging, reservations and tariffs
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Kevin Vavelin, Codetics (https://codetics.fr)

//! Reservation endpoints.

use axum::Json;
use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use serde::Deserialize;
use serde_json::{Value, json};

use super::stations::load;
use crate::calls;
use crate::error::AppError;
use crate::reservation::{self, ReserveRequest};
use crate::state::App;

pub async fn create(
    State(app): State<App>,
    Path(identity): Path<String>,
    Json(req): Json<ReserveRequest>,
) -> Result<(StatusCode, Json<Value>), AppError> {
    let version = calls::connected_version(&app, &identity).await?;
    let station = load(&app, &identity).await?;
    let (r, response) = reservation::reserve(&app, &station, version, &req).await?;
    let code = if r.status == "Accepted" {
        StatusCode::CREATED
    } else {
        StatusCode::OK
    };
    Ok((
        code,
        Json(json!({ "reservation": r, "response": response })),
    ))
}

#[derive(Debug, Deserialize)]
pub struct ListQuery {
    pub active: Option<bool>,
    pub limit: Option<i64>,
}

pub async fn list(
    State(app): State<App>,
    Path(identity): Path<String>,
    Query(q): Query<ListQuery>,
) -> Result<Json<Value>, AppError> {
    let station = load(&app, &identity).await?;
    let list = crate::db::reservations::list(
        &app.db,
        station.id,
        q.active.unwrap_or(false),
        q.limit.unwrap_or(100).clamp(1, 1000),
    )
    .await?;
    Ok(Json(json!(list)))
}

pub async fn get(
    State(app): State<App>,
    Path((identity, id)): Path<(String, i32)>,
) -> Result<Json<Value>, AppError> {
    let station = load(&app, &identity).await?;
    let r = crate::db::reservations::get(&app.db, station.id, id)
        .await?
        .ok_or_else(|| AppError::NotFound(format!("reservation {id} not found")))?;
    Ok(Json(json!(r)))
}

pub async fn cancel(
    State(app): State<App>,
    Path((identity, id)): Path<(String, i32)>,
) -> Result<Json<Value>, AppError> {
    let version = calls::connected_version(&app, &identity).await?;
    let station = load(&app, &identity).await?;
    let (r, response) = reservation::cancel(&app, &station, version, id).await?;
    Ok(Json(json!({ "reservation": r, "response": response })))
}