use std::sync::Arc;
use axum::body::Body;
use axum::extract::{Request, State};
use axum::middleware::Next;
use axum::response::{IntoResponse, Response};
use http::StatusCode as HttpStatus;
use crate::convert::wire::{ObjectKind, Payload};
use crate::transport::OcpiError;
use crate::{InterfaceRole, ModuleId, VersionNumber};
use super::error::OcpiErrorResponse;
use super::router::OcpiState;
const MAX_BRIDGED_BODY: usize = 8 * 1024 * 1024;
pub(crate) async fn translate(State(state): State<Arc<OcpiState>>, request: Request, next: Next) -> Response {
let theirs = state.version().clone();
let path = request.uri().path().to_owned();
let Some((module, interface, below)) = state.endpoint_of(&path) else {
return next.run(request).await;
};
let request = match rewrite_request(request, &theirs, &module, interface, &below).await {
Ok(request) => request,
Err(error) => return OcpiErrorResponse::new(error).into_response(),
};
let response = next.run(request).await;
match rewrite_response(response, &theirs, &module, interface, &below).await {
Ok(response) => response,
Err(error) => OcpiErrorResponse::new(error).into_response(),
}
}
async fn rewrite_request(
request: Request,
theirs: &VersionNumber,
module: &ModuleId,
interface: InterfaceRole,
below: &str,
) -> Result<Request, OcpiError> {
let Some(kind) = ObjectKind::for_endpoint(module, interface, below, Payload::Request) else {
return Ok(request);
};
if request.method() == http::Method::PATCH {
return check_patch(request, theirs, kind).await;
}
let (parts, body) = request.into_parts();
let bytes = read(body).await?;
if bytes.is_empty() {
return Ok(Request::from_parts(parts, Body::from(bytes)));
}
let value: serde_json::Value =
serde_json::from_slice(&bytes).map_err(|e| OcpiError::MalformedJson(e.to_string()))?;
let converted = kind
.bridge(theirs, &crate::CANONICAL_VERSION, value)
.map_err(|e| OcpiError::Decode { path: "/".to_owned(), message: e.to_string() })?;
let bytes = serde_json::to_vec(&converted.value).map_err(|e| OcpiError::MalformedJson(e.to_string()))?;
Ok(Request::from_parts(parts, Body::from(bytes)))
}
async fn check_patch(
request: Request,
theirs: &VersionNumber,
kind: ObjectKind,
) -> Result<Request, OcpiError> {
let (parts, body) = request.into_parts();
let bytes = read(body).await?;
let value: serde_json::Value =
serde_json::from_slice(&bytes).map_err(|e| OcpiError::MalformedJson(e.to_string()))?;
let fields: Vec<&str> =
value.as_object().map(|o| o.keys().map(String::as_str).collect()).unwrap_or_default();
if !kind.patch_crosses_unchanged(&fields) {
return Err(OcpiError::Unsupported(format!(
"this PATCH writes {fields:?}, and a {kind} does not carry {} the same way in OCPI \
{theirs} as in OCPI {}; GET the object and PUT it back instead, which is the \
recovery the specification prescribes for a refused PATCH",
kind.divergent_fields().join(", "),
crate::CANONICAL_VERSION,
)));
}
Ok(Request::from_parts(parts, Body::from(bytes)))
}
async fn rewrite_response(
response: Response,
theirs: &VersionNumber,
module: &ModuleId,
interface: InterfaceRole,
below: &str,
) -> Result<Response, OcpiError> {
let Some(kind) = ObjectKind::for_endpoint(module, interface, below, Payload::Response) else {
return Ok(response);
};
let (parts, body) = response.into_parts();
let bytes = read(body).await?;
if bytes.is_empty() {
return Ok(Response::from_parts(parts, Body::from(bytes)));
}
let mut envelope: serde_json::Value = match serde_json::from_slice(&bytes) {
Ok(value) => value,
Err(_) => return Ok(Response::from_parts(parts, Body::from(bytes))),
};
let Some(data) = envelope.get_mut("data").map(serde_json::Value::take) else {
return Ok(Response::from_parts(parts, Body::from(bytes)));
};
let converted = kind
.bridge(&crate::CANONICAL_VERSION, theirs, data)
.map_err(|e| OcpiError::Transport(e.to_string()))?;
envelope["data"] = converted.value;
if let Some(note) = converted.lossy.to_status_message() {
tracing::warn!(ocpi.peer_version = %theirs, ocpi.object = %kind, "{note}");
}
let bytes = serde_json::to_vec(&envelope).map_err(|e| OcpiError::Transport(e.to_string()))?;
let mut parts = parts;
parts.headers.remove(http::header::CONTENT_LENGTH);
Ok(Response::from_parts(parts, Body::from(bytes)))
}
async fn read(body: Body) -> Result<axum::body::Bytes, OcpiError> {
axum::body::to_bytes(body, MAX_BRIDGED_BODY).await.map_err(|e| {
OcpiError::MalformedJson(format!(
"could not read a body to translate it between OCPI versions ({}): {e}",
HttpStatus::PAYLOAD_TOO_LARGE
))
})
}
impl OcpiState {
pub(crate) fn endpoint_of(&self, path: &str) -> Option<(ModuleId, InterfaceRole, String)> {
let mut segments = path.split('/').filter(|s| !s.is_empty()).peekable();
let mut prefixed = false;
if let Some(prefix) = self.config().receiver_path_prefix.as_deref()
&& segments.peek() == Some(&prefix)
{
segments.next();
prefixed = true;
}
let module = ModuleId::from(segments.next()?);
let below = segments.collect::<Vec<_>>().join("/");
let interface = if prefixed {
InterfaceRole::Receiver
} else if self.mounted().contains(&module, InterfaceRole::Sender) {
InterfaceRole::Sender
} else {
InterfaceRole::Receiver
};
Some((module, interface, below))
}
}