#![forbid(unsafe_code)]
#![cfg_attr(
not(test),
deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)
)]
pub mod auth;
pub mod body_credentials;
pub mod config;
pub mod cors;
pub mod params;
pub mod request;
pub mod response;
pub mod routes;
pub mod state;
use std::sync::Arc;
use axum::extract::FromRequestParts;
use axum::response::{IntoResponse, Response};
use axum::routing::{delete, get, post};
use axum::Router;
pub use auth::{Authority, Credentials, HeaderRejection};
pub use config::{ProtectedFieldsConfig, ServerConfig};
pub use request::RequestContext;
pub use state::AppState;
#[axum::async_trait]
impl<S> FromRequestParts<S> for Authority
where
Arc<ServerConfig>: axum::extract::FromRef<S>,
S: Send + Sync,
{
type Rejection = Response;
async fn from_request_parts(
parts: &mut http::request::Parts,
state: &S,
) -> Result<Self, Self::Rejection> {
let config = <Arc<ServerConfig> as axum::extract::FromRef<S>>::from_ref(state);
auth::resolve(&config, &parts.headers).map_err(|HeaderRejection::Unauthorized| {
response::HttpError::unauthorized().into_response()
})
}
}
pub fn router(state: AppState) -> Router {
let mount = state.config().mount_path.clone();
let cors_state = state.clone();
let api = Router::new()
.route("/serverInfo", get(routes::http::server_info))
.route(
"/health",
get(routes::http::health).post(routes::http::health),
)
.route("/users", post(routes::http::users_collection))
.route(
"/users/me",
get(routes::http::users_me).post(routes::http::users_me),
)
.route("/login", post(routes::http::login))
.route("/logout", post(routes::http::logout))
.route(
"/classes/:className",
get(routes::http::classes_collection).post(routes::http::classes_collection),
)
.route(
"/classes/:className/:objectId",
get(routes::http::classes_object)
.put(routes::http::classes_object)
.delete(routes::http::classes_object)
.post(routes::http::classes_object),
)
.route(
"/roles",
get(routes::http::roles_collection).post(routes::http::roles_collection),
)
.route(
"/roles/:objectId",
get(routes::http::roles_object)
.put(routes::http::roles_object)
.delete(routes::http::roles_object)
.post(routes::http::roles_object),
)
.route(
"/sessions/me",
get(routes::http::sessions_me).post(routes::http::sessions_me),
)
.route(
"/sessions",
get(routes::http::sessions_collection).post(routes::http::sessions_collection),
)
.route(
"/sessions/:objectId",
get(routes::http::sessions_object)
.delete(routes::http::sessions_object)
.post(routes::http::sessions_object),
)
.route(
"/schemas",
get(routes::http::schemas_collection).post(routes::http::schemas_collection),
)
.route(
"/schemas/:className",
get(routes::http::schemas_class)
.post(routes::http::schemas_class)
.put(routes::http::schemas_class)
.delete(routes::http::schemas_class),
)
.route(
"/purge/:className",
delete(routes::http::purge).post(routes::http::purge),
)
.route("/batch", post(routes::http::batch))
.with_state(state);
Router::new()
.nest(&mount, api)
.layer(axum::middleware::from_fn(body_credentials::extract))
.layer(axum::middleware::from_fn_with_state(
cors_state,
cors::layer,
))
}
pub async fn serve(
state: AppState,
addr: std::net::SocketAddr,
) -> std::io::Result<(
std::net::SocketAddr,
impl std::future::Future<Output = std::io::Result<()>>,
)> {
state
.ensure_indexes()
.await
.map_err(|e| std::io::Error::other(e.to_string()))?;
let listener = tokio::net::TcpListener::bind(addr).await?;
let bound = listener.local_addr()?;
let app = router(state);
Ok((bound, async move { axum::serve(listener, app).await }))
}