phonograph/interim-server/src/navigator.rs

175 lines
4.6 KiB
Rust
Raw Normal View History

2025-07-18 16:20:03 -07:00
use axum::{
extract::FromRequestParts,
http::request::Parts,
response::{IntoResponse as _, Redirect, Response},
};
2025-09-23 13:15:53 -07:00
use derive_builder::Builder;
use sqlx::postgres::types::Oid;
use uuid::Uuid;
2025-07-18 16:20:03 -07:00
use crate::{app::App, errors::AppError};
2025-07-18 16:20:03 -07:00
2025-09-23 13:15:53 -07:00
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()
}
}
2025-07-18 16:20:03 -07:00
/// Helper type for semantically generating URI paths, e.g. for redirects.
#[derive(Clone, Debug)]
2025-09-23 13:15:53 -07:00
pub(crate) struct Navigator {
2025-07-18 16:20:03 -07:00
root_path: String,
}
impl Navigator {
pub(crate) fn workspace_page(&self) -> WorkspacePageBuilder {
WorkspacePageBuilder {
root_path: Some(&self.root_path),
..Default::default()
}
}
2025-10-01 22:36:19 -07:00
pub(crate) fn portal_page(&self) -> PortalPageBuilder {
PortalPageBuilder {
root_path: Some(&self.root_path),
2025-10-01 22:36:19 -07:00
..Default::default()
2025-07-18 16:20:03 -07:00
}
}
2025-10-01 22:37:11 -07:00
pub(crate) fn form_page(&self, portal_id: Uuid) -> FormPageBuilder {
FormPageBuilder {
root_path: Some(&self.root_path),
2025-10-01 22:37:11 -07:00
portal_id: Some(portal_id),
}
}
2025-09-23 13:15:53 -07:00
/// 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),
2025-09-23 13:15:53 -07:00
..Default::default()
}
}
pub(crate) fn get_root_path(&self) -> String {
self.root_path.clone()
2025-07-18 16:20:03 -07:00
}
}
impl FromRequestParts<App> for Navigator {
2025-07-18 16:20:03 -07:00
type Rejection = AppError;
async fn from_request_parts(_: &mut Parts, state: &App) -> Result<Self, Self::Rejection> {
2025-07-18 16:20:03 -07:00
Ok(Navigator {
root_path: state.settings.root_path.clone(),
2025-07-18 16:20:03 -07:00
})
}
}
2025-09-23 13:15:53 -07:00
2025-10-01 22:36:19 -07:00
#[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,
suffix = self.suffix.unwrap_or_default(),
)
}
}
#[derive(Builder, Clone, Debug)]
pub(crate) struct PortalPage<'a> {
2025-10-01 22:36:19 -07:00
portal_id: Uuid,
rel_oid: Oid,
#[builder(setter(custom))]
root_path: &'a str,
2025-10-01 22:36:19 -07:00
/// 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>,
2025-10-01 22:36:19 -07:00
workspace_id: Uuid,
}
impl<'a> NavigatorPage for PortalPage<'a> {
2025-10-01 22:36:19 -07:00
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(),
2025-10-01 22:36:19 -07:00
)
}
}
2025-10-01 22:37:11 -07:00
#[derive(Builder, Clone, Debug)]
pub(crate) struct FormPage<'a> {
2025-10-01 22:37:11 -07:00
portal_id: Uuid,
#[builder(setter(custom))]
root_path: &'a str,
2025-10-01 22:37:11 -07:00
}
impl<'a> NavigatorPage for FormPage<'a> {
2025-10-01 22:37:11 -07:00
fn get_path(&self) -> String {
format!(
"{root_path}/f/{portal_id}",
root_path = self.root_path,
portal_id = self.portal_id,
2025-10-01 22:37:11 -07:00
)
}
}
2025-10-01 22:36:19 -07:00
#[derive(Builder, Clone, Debug)]
pub(crate) struct RelPage<'a> {
2025-09-23 13:15:53 -07:00
rel_oid: Oid,
#[builder(setter(custom))]
root_path: &'a str,
2025-09-23 13:15:53 -07:00
/// 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>,
2025-09-23 13:15:53 -07:00
workspace_id: Uuid,
}
impl<'a> NavigatorPage for RelPage<'a> {
2025-09-23 13:15:53 -07:00
fn get_path(&self) -> String {
format!(
"{root_path}/w/{workspace_id}/r/{rel_oid}/{suffix}",
2025-09-23 13:15:53 -07:00
root_path = self.root_path,
workspace_id = self.workspace_id.simple(),
rel_oid = self.rel_oid.0,
suffix = self.suffix.clone().unwrap_or_default(),
)
}
}