use axum::body::Bytes;
use axum::extract::{DefaultBodyLimit, Path, Query};
use axum::http::{HeaderMap, Method};
use axum::response::IntoResponse;
use axum::routing::any;
use axum::{Extension, Router};
use surrealdb_core::api::err::ApiError;
use surrealdb_core::api::request::ApiRequest;
use surrealdb_core::catalog::ApiMethod;
use surrealdb_core::dbs::Session;
use surrealdb_core::dbs::capabilities::RouteTarget;
use surrealdb_types::Value;
use tower_http::limit::RequestBodyLimitLayer;
use uuid::Uuid;
use super::AppState;
use super::error::{ApiHandlerError, ResponseError};
use crate::cnf::HTTP_MAX_API_BODY_SIZE;
use crate::ntw::error::Error as NetError;
use crate::ntw::params::Params;
pub fn router<S>() -> Router<S>
where
S: Clone + Send + Sync + 'static,
{
Router::new()
.route("/api/{ns}/{db}/{*path}", any(handler))
.route_layer(DefaultBodyLimit::disable())
.layer(RequestBodyLimitLayer::new(*HTTP_MAX_API_BODY_SIZE))
}
async fn handler(
Extension(state): Extension<AppState>,
Extension(session): Extension<Session>,
Path((ns, db, path)): Path<(String, String, String)>,
headers: HeaderMap,
Query(Params {
inner: query,
}): Query<Params>,
method: Method,
body: Bytes,
) -> Result<impl IntoResponse, ApiHandlerError> {
let request_id = Uuid::new_v4().to_string();
trace!(
request_id = %request_id,
method = %method,
path = %path,
ns = %ns,
db = %db,
"API request received"
);
let url = format!("/api/{ns}/{db}/{path}");
let ds = &state.datastore;
let session = session.with_ns(&ns).with_db(&db);
if !ds.allows_http_route(&RouteTarget::Api) {
warn!(
request_id = %request_id,
"Capabilities denied HTTP route request attempt, target: '{}'",
&RouteTarget::Api
);
return Err(ApiHandlerError(
NetError::ForbiddenRoute(RouteTarget::Api.to_string()).into(),
request_id,
));
}
let method = match method {
Method::DELETE => ApiMethod::Delete,
Method::GET => ApiMethod::Get,
Method::PATCH => ApiMethod::Patch,
Method::POST => ApiMethod::Post,
Method::PUT => ApiMethod::Put,
Method::TRACE => ApiMethod::Trace,
_ => {
warn!(
request_id = %request_id,
method = %method,
"API route does not support HTTP method"
);
return Err(ApiHandlerError(NetError::NotFound(url).into(), request_id));
}
};
let body = Value::Bytes(body.into());
let req = ApiRequest {
method,
headers,
body,
query,
request_id: request_id.clone(),
..Default::default()
};
debug!(
request_id = %request_id,
path = %path,
"Invoking API handler"
);
let res = ds
.invoke_api_handler(&ns, &db, &path, &session, req)
.await
.map_err(|e| ApiHandlerError(ResponseError(e), request_id.clone()))?;
trace!(
request_id = %request_id,
status = %res.status,
"API handler completed"
);
let res_body = match res.body {
Value::None => Vec::new(),
Value::Bytes(x) => x.into_inner().to_vec(),
Value::String(s) => s.into_bytes(),
_ => {
return Err(ApiHandlerError(
ApiError::InvalidApiResponse(
"HTTP API response body must be None, bytes, or string; other values are not supported".into(),
)
.into(),
request_id,
));
}
};
Ok((res.status, res.headers, res_body))
}