use std::future::Future;
use std::sync::Arc;
use axum::extract::{Query, State};
use axum::http::{HeaderMap, StatusCode};
use axum::response::{IntoResponse, Response};
use axum::{Json, Router, routing};
use serde::Deserialize;
use crate::error::Error;
use crate::models::electricity::{
IdentificationParameter, MaloIdentResultNegative, MaloIdentResultPositive,
};
#[allow(async_fn_in_trait)]
pub trait MaloRegistry: Send + Sync + 'static {
async fn lookup(
&self,
tenant_id: &str,
params: &IdentificationParameter,
) -> Result<Option<MaloIdentResultPositive>, Error>;
}
#[derive(Debug, Clone, Default)]
pub struct NoopMaloRegistry;
impl MaloRegistry for NoopMaloRegistry {
async fn lookup(
&self,
_tenant_id: &str,
_params: &IdentificationParameter,
) -> Result<Option<MaloIdentResultPositive>, Error> {
Ok(None)
}
}
#[derive(Debug, Clone, Default)]
pub struct StaticMaloRegistry {
entries: std::collections::HashMap<(String, String), MaloIdentResultPositive>,
}
impl StaticMaloRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn insert(
&mut self,
tenant_id: impl Into<String>,
malo_id: impl Into<String>,
result: MaloIdentResultPositive,
) {
self.entries
.insert((tenant_id.into(), malo_id.into()), result);
}
}
impl MaloRegistry for StaticMaloRegistry {
async fn lookup(
&self,
tenant_id: &str,
params: &IdentificationParameter,
) -> Result<Option<MaloIdentResultPositive>, Error> {
let malo_id = params
.identification_parameter_id
.as_ref()
.and_then(|id| id.malo_id.as_ref())
.map(|m| m.0.as_str())
.unwrap_or("");
Ok(self
.entries
.get(&(tenant_id.to_owned(), malo_id.to_owned()))
.cloned())
}
}
pub trait MaloIdentHandler: Send + Sync + 'static {
fn on_request(
&self,
_tx_id: String,
_creation_dt: String,
_sender_market_partner_id: String,
_params: IdentificationParameter,
) -> impl Future<Output = Result<(), Error>> + Send {
async {
Err(Error::Http {
status: 501,
body: "not implemented".into(),
})
}
}
fn on_positive_result(
&self,
_tx_id: String,
_creation_dt: String,
_reference_id: String,
_result: MaloIdentResultPositive,
) -> impl Future<Output = Result<(), Error>> + Send {
async {
Err(Error::Http {
status: 501,
body: "not implemented".into(),
})
}
}
fn on_negative_result(
&self,
_tx_id: String,
_creation_dt: String,
_reference_id: String,
_result: MaloIdentResultNegative,
) -> impl Future<Output = Result<(), Error>> + Send {
async {
Err(Error::Http {
status: 501,
body: "not implemented".into(),
})
}
}
}
pub fn router<S>(state: Arc<S>) -> Router
where
S: MaloIdentHandler + Clone,
{
Router::new()
.route("/maloId/request/v1", routing::post(handle_request::<S>))
.route(
"/maloId/dataForMarketLocationPositive/v1",
routing::post(handle_positive::<S>),
)
.route(
"/maloId/dataForMarketLocationNegative/v1",
routing::post(handle_negative::<S>),
)
.with_state(state)
}
fn str_header(h: &HeaderMap, name: &str) -> String {
h.get(name)
.and_then(|v| v.to_str().ok())
.unwrap_or("")
.to_owned()
}
#[derive(Deserialize)]
struct ReferenceQuery {
#[serde(rename = "referenceId")]
reference_id: String,
}
async fn handle_request<S: MaloIdentHandler>(
State(svc): State<Arc<S>>,
headers: HeaderMap,
Json(params): Json<IdentificationParameter>,
) -> Response {
match svc
.on_request(
str_header(&headers, "transactionId"),
str_header(&headers, "creationDateTime"),
str_header(&headers, "marketPartnerId"),
params,
)
.await
{
Ok(()) => StatusCode::ACCEPTED.into_response(),
Err(e) => handler_error(e),
}
}
async fn handle_positive<S: MaloIdentHandler>(
State(svc): State<Arc<S>>,
headers: HeaderMap,
Query(q): Query<ReferenceQuery>,
Json(result): Json<MaloIdentResultPositive>,
) -> Response {
match svc
.on_positive_result(
str_header(&headers, "transactionId"),
str_header(&headers, "creationDateTime"),
q.reference_id,
result,
)
.await
{
Ok(()) => StatusCode::ACCEPTED.into_response(),
Err(e) => handler_error(e),
}
}
async fn handle_negative<S: MaloIdentHandler>(
State(svc): State<Arc<S>>,
headers: HeaderMap,
Query(q): Query<ReferenceQuery>,
Json(result): Json<MaloIdentResultNegative>,
) -> Response {
match svc
.on_negative_result(
str_header(&headers, "transactionId"),
str_header(&headers, "creationDateTime"),
q.reference_id,
result,
)
.await
{
Ok(()) => StatusCode::ACCEPTED.into_response(),
Err(e) => handler_error(e),
}
}
fn handler_error(e: Error) -> Response {
match e {
Error::Http { status, body } => (
StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
body,
)
.into_response(),
other => (StatusCode::INTERNAL_SERVER_ERROR, other.to_string()).into_response(),
}
}