use std::sync::Arc;
use std::sync::PoisonError;
use std::sync::RwLock;
use axum::Router;
use axum::body::Body;
use axum::extract::Request;
use axum::http::HeaderValue;
use axum::http::StatusCode;
use axum::http::header;
use axum::response::IntoResponse;
use axum::response::Response;
use axum::routing::post;
use leptoskit_db::Db;
use serde_json::Value;
use super::Anonymous;
use super::Identify;
use super::Invocation;
use super::Registration;
use crate::Error;
use crate::Kind;
pub const IDEMPOTENCY_KEY: &str = "idempotency-key";
const BODY_LIMIT: usize = 2 * 1024 * 1024;
#[derive(Clone)]
pub struct Server {
inner: Arc<Inner>,
}
struct Inner {
db: Db,
identify: Arc<dyn Identify>,
}
impl Server {
pub fn new(db: Db) -> Self {
Self {
inner: Arc::new(Inner {
db,
identify: Arc::new(Anonymous),
}),
}
}
#[must_use]
pub fn identify(self, identify: impl Identify) -> Self {
Self {
inner: Arc::new(Inner {
db: self.inner.db.clone(),
identify: Arc::new(identify),
}),
}
}
pub fn db(&self) -> &Db {
&self.inner.db
}
#[cfg(feature = "live")]
pub fn live(&self) -> super::Live {
super::Live::new(self.inner.db.clone()).identify_arc(Arc::clone(&self.inner.identify))
}
pub fn install(&self) {
*INSTALLED.write().unwrap_or_else(PoisonError::into_inner) = Some(self.clone());
}
pub fn router<S>(&self) -> Router<S>
where
S: Clone + Send + Sync + 'static,
{
let registrations = super::registrations().unwrap_or_else(|error| panic!("{error}"));
self.install();
let mut router = Router::new();
for registration in registrations {
if registration.kind == Kind::Job {
continue;
}
let server = self.clone();
router = router.route(
&format!("/api/{}", registration.name),
post(move |request: Request| {
let server = server.clone();
async move { server.serve(registration, request).await }
}),
);
}
router
}
async fn serve(&self, registration: &'static Registration, request: Request) -> Response {
let (parts, body) = request.into_parts();
let Ok(bytes) = axum::body::to_bytes(body, BODY_LIMIT).await else {
return failure(&Error::invalid(
"the request body is too large, or didn't arrive",
));
};
let arguments = if bytes.iter().all(u8::is_ascii_whitespace) {
Value::Object(serde_json::Map::new())
} else {
match serde_json::from_slice::<Value>(&bytes) {
Ok(value @ Value::Object(_)) => value,
Ok(_) | Err(_) => {
return failure(&Error::invalid(
"send the arguments as one JSON object, such as `{\"limit\": 10}`",
));
}
}
};
let request_id = (registration.kind == Kind::Mutation)
.then(|| {
parts
.headers
.get(IDEMPOTENCY_KEY)
.map(|value| value.to_str().map(str::to_owned))
})
.flatten()
.transpose();
let Ok(request_id) = request_id else {
return failure(&Error::invalid(
"the idempotency key must be printable ASCII",
));
};
match self.call(&parts, registration, arguments, request_id).await {
Ok(value) => {
(
[(
header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
)],
value.to_string(),
)
.into_response()
}
Err(error) => failure(&error),
}
}
pub(crate) async fn call(
&self,
parts: &http::request::Parts,
registration: &'static Registration,
arguments: Value,
request_id: Option<String>,
) -> crate::Result<Value> {
let identity = self.inner.identify.identify(parts).await?;
let db = self.inner.db.with_context(identity.context.clone());
(registration.handler)(Invocation {
db,
identity,
request_id,
arguments,
reads: None,
})
.await
}
}
impl std::fmt::Debug for Server {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Server").finish_non_exhaustive()
}
}
static INSTALLED: RwLock<Option<Server>> = RwLock::new(None);
pub(crate) fn installed() -> Option<Server> {
INSTALLED
.read()
.unwrap_or_else(PoisonError::into_inner)
.clone()
}
pub(crate) fn failure(error: &Error) -> Response {
let status = StatusCode::from_u16(error.status()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
let mut response = Response::new(Body::from(error.to_wire()));
*response.status_mut() = status;
response
.headers_mut()
.insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain"));
response
}