1#![forbid(missing_docs)]
2#![forbid(unsafe_code)]
3#![warn(missing_debug_implementations)]
4#![doc = include_str!("../README.md")]
5
6use std::sync::Arc;
7
8use axum::{
9 body::{Body, Bytes},
10 extract::Request,
11 http::StatusCode,
12 middleware::Next,
13 response::{IntoResponse, Response},
14 routing::{get, put},
15 Router,
16};
17use http_body_util::BodyExt;
18
19mod app_config;
20mod routes;
21
22pub use app_config::{AppConfig, FileOrString, MockResource, MockResourceMethod, ServerConfig, WebwareConfig, WebservicesConfig, CredentialsConfig};
23use routes::{
24 exec_json::exec_json,
25 service_pass::{handle_deregister, handle_register},
26};
27
28#[derive(axum::extract::FromRef, Clone)]
29struct AppState {
30 pub config: Arc<AppConfig>,
31}
32
33#[cfg(not(tarpaulin_include))]
34async fn logging_middleware(
35 request: Request,
36 next: Next,
37) -> Result<impl IntoResponse, (StatusCode, String)> {
38 let (parts, body) = request.into_parts();
39 let bytes =
40 buffer_and_print(&format!("--> {} {}", parts.method, parts.uri.path()), body).await?;
41 let req = Request::from_parts(parts, Body::from(bytes));
42 let res = next.run(req).await;
43
44 let (parts, body) = res.into_parts();
45 let bytes = buffer_and_print(&format!("<-- {}", parts.status), body).await?;
46 let res = Response::from_parts(parts, Body::from(bytes));
47
48 Ok(res)
49}
50
51#[cfg(not(tarpaulin_include))]
52async fn buffer_and_print<B>(direction: &str, body: B) -> Result<Bytes, (StatusCode, String)>
53where
54 B: axum::body::HttpBody<Data = Bytes>,
55 B::Error: std::fmt::Display,
56{
57 let bytes = match body.collect().await {
58 Ok(collected) => collected.to_bytes(),
59 Err(err) => {
60 return Err((
61 StatusCode::BAD_REQUEST,
62 format!("failed to read {direction} body: {err}"),
63 ));
64 }
65 };
66
67 if let Ok(body) = std::str::from_utf8(&bytes) {
68 tracing::debug!("{} {}", direction, body);
69 }
70
71 Ok(bytes)
72}
73
74#[derive(serde::Serialize, Debug)]
76pub struct OptionalJson(
77 #[serde(skip_serializing_if = "Option::is_none")] Option<serde_json::Value>,
78);
79
80pub async fn app(config: &AppConfig) -> anyhow::Result<Router> {
89 let registering_routes = Router::new()
90 .route(
91 "/REGISTER/:vendor_hash/:app_hash/:secret/:revision/",
92 get(handle_register),
93 )
94 .route("/DEREGISTER/:service_pass/", get(handle_deregister));
95
96 let wwsvc_router = Router::new()
97 .route(
98 "/EXECJSON/",
99 put(exec_json).post(exec_json).delete(exec_json),
100 )
101 .route(
102 "/EXECJSON",
103 put(exec_json).post(exec_json).delete(exec_json),
104 )
105 .nest("/WWSERVICE", registering_routes);
106
107 let mut router = Router::new()
108 .nest("/WWSVC", wwsvc_router)
109 .with_state(AppState {
110 config: Arc::new(config.clone()),
111 });
112
113 if config.debug {
114 router = router.layer(axum::middleware::from_fn(logging_middleware));
115 }
116
117 Ok(router)
118}