use axum::{ extract::FromRequestParts, http::request::Parts, response::{IntoResponse as _, Redirect, Response}, }; use derive_builder::Builder; use sqlx::postgres::types::Oid; use uuid::Uuid; use crate::{app::App, errors::AppError}; pub(crate) trait NavigatorPage { /// Returns the path component of the URL to the corresonding web page. /// Starts with a "/" character and the `root_path` if one is specified for /// [`crate::settings::Settings`]. fn get_path(&self) -> String; /// Wrap [`Self::get_path`] in an Axum HTTP 303 redirection response for /// convenience. fn redirect_to(&self) -> Response { Redirect::to(&self.get_path()).into_response() } } /// Helper type for semantically generating URI paths, e.g. for redirects. #[derive(Clone, Debug)] pub(crate) struct Navigator { root_path: String, } impl Navigator { pub(crate) fn workspace_page(&self) -> WorkspacePageBuilder { WorkspacePageBuilder { root_path: Some(&self.root_path), ..Default::default() } } pub(crate) fn portal_page(&self) -> PortalPageBuilder { PortalPageBuilder { root_path: Some(&self.root_path), ..Default::default() } } pub(crate) fn form_page(&self, portal_id: Uuid) -> FormPageBuilder { FormPageBuilder { root_path: Some(&self.root_path), portal_id: Some(portal_id), } } /// Returns a [`NavigatorPage`] builder for navigating to a relation's /// "settings" page. pub(crate) fn rel_page(&self) -> RelPageBuilder { RelPageBuilder { root_path: Some(&self.root_path), ..Default::default() } } pub(crate) fn get_root_path(&self) -> String { self.root_path.clone() } } impl FromRequestParts for Navigator { type Rejection = AppError; async fn from_request_parts(_: &mut Parts, state: &App) -> Result { Ok(Navigator { root_path: state.settings.root_path.clone(), }) } } #[derive(Builder, Clone, Debug)] pub(crate) struct WorkspacePage<'a> { #[builder(setter(custom))] root_path: &'a str, #[builder(default, setter(strip_option))] suffix: Option<&'a str>, workspace_id: Uuid, } impl<'a> NavigatorPage for WorkspacePage<'a> { fn get_path(&self) -> String { format!( "{root_path}/w/{workspace_id}/{suffix}", root_path = self.root_path, workspace_id = self.workspace_id.simple(), suffix = self.suffix.unwrap_or_default(), ) } } #[derive(Builder, Clone, Debug)] pub(crate) struct PortalPage<'a> { portal_id: Uuid, rel_oid: Oid, #[builder(setter(custom))] root_path: &'a str, /// Any value provided for `suffix` will be appended (without %-encoding) to /// the final path value. This may be used for sub-paths and/or search /// parameters. #[builder(default, setter(strip_option))] suffix: Option<&'a str>, workspace_id: Uuid, } impl<'a> NavigatorPage for PortalPage<'a> { fn get_path(&self) -> String { format!( "{root_path}/w/{workspace_id}/r/{rel_oid}/p/{portal_id}/{suffix}", root_path = self.root_path, workspace_id = self.workspace_id.simple(), rel_oid = self.rel_oid.0, portal_id = self.portal_id.simple(), suffix = self.suffix.unwrap_or_default(), ) } } #[derive(Builder, Clone, Debug)] pub(crate) struct FormPage<'a> { portal_id: Uuid, #[builder(setter(custom))] root_path: &'a str, } impl<'a> NavigatorPage for FormPage<'a> { fn get_path(&self) -> String { format!( "{root_path}/f/{portal_id}", root_path = self.root_path, portal_id = self.portal_id, ) } } #[derive(Builder, Clone, Debug)] pub(crate) struct RelPage<'a> { rel_oid: Oid, #[builder(setter(custom))] root_path: &'a str, /// Any value provided for `suffix` will be appended (without %-encoding) to /// the final path value. This may be used for sub-paths and/or search /// parameters. #[builder(default, setter(strip_option))] suffix: Option<&'a str>, workspace_id: Uuid, } impl<'a> NavigatorPage for RelPage<'a> { fn get_path(&self) -> String { format!( "{root_path}/w/{workspace_id}/r/{rel_oid}/{suffix}", root_path = self.root_path, workspace_id = self.workspace_id.simple(), rel_oid = self.rel_oid.0, suffix = self.suffix.unwrap_or_default(), ) } }