summaryrefslogtreecommitdiff
path: root/src/setup/required.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/setup/required.rs')
-rw-r--r--src/setup/required.rs107
1 files changed, 107 insertions, 0 deletions
diff --git a/src/setup/required.rs b/src/setup/required.rs
new file mode 100644
index 0000000..2112e4b
--- /dev/null
+++ b/src/setup/required.rs
@@ -0,0 +1,107 @@
+use axum::{
+ extract::Request,
+ http::StatusCode,
+ response::{IntoResponse, Response},
+};
+use std::pin::Pin;
+use std::task::{Context, Poll};
+use tower::{Layer, Service};
+
+use crate::{app::App, error::Internal};
+
+#[derive(Clone)]
+pub struct Required(pub App);
+
+impl Required {
+ pub fn with_fallback<F>(self, fallback: F) -> WithFallback<F> {
+ let Self(app) = self;
+ WithFallback { app, fallback }
+ }
+}
+
+impl<S> Layer<S> for Required {
+ type Service = Middleware<S, Unavailable>;
+
+ fn layer(&self, inner: S) -> Self::Service {
+ let Self(app) = self.clone();
+ Middleware {
+ inner,
+ app,
+ fallback: Unavailable,
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct WithFallback<F> {
+ app: App,
+ fallback: F,
+}
+
+impl<S, F> Layer<S> for WithFallback<F>
+where
+ Self: Clone,
+{
+ type Service = Middleware<S, F>;
+
+ fn layer(&self, inner: S) -> Self::Service {
+ let Self { app, fallback } = self.clone();
+ Middleware {
+ inner,
+ app,
+ fallback,
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct Middleware<S, F> {
+ inner: S,
+ app: App,
+ fallback: F,
+}
+
+impl<S, F> Service<Request> for Middleware<S, F>
+where
+ Self: Clone,
+ S: Service<Request, Response = Response> + Send + 'static,
+ S::Future: Send,
+ F: IntoResponse + Clone + Send + 'static,
+{
+ type Response = S::Response;
+ type Error = S::Error;
+ type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
+
+ fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+ self.inner.poll_ready(ctx)
+ }
+
+ fn call(&mut self, req: Request) -> Self::Future {
+ let Self {
+ mut inner,
+ app,
+ fallback,
+ } = self.clone();
+
+ Box::pin(async move {
+ match app.setup().completed().await {
+ Ok(true) => inner.call(req).await,
+ Ok(false) => Ok(fallback.into_response()),
+ Err(error) => Ok(Internal::from(error).into_response()),
+ }
+ })
+ }
+}
+
+#[derive(Clone)]
+pub struct Unavailable;
+
+impl IntoResponse for Unavailable {
+ fn into_response(self) -> Response {
+ (
+ StatusCode::SERVICE_UNAVAILABLE,
+ "initial setup not completed",
+ )
+ .into_response()
+ }
+}