use std::sync::Arc;
use axum::Router;
use laterite_core::{Db, Text};
#[derive(Clone)]
pub struct RouteCtx {
db: Db,
base_path: Arc<str>,
admin_path: Arc<str>,
}
impl RouteCtx {
pub(crate) fn new(db: Db, base_path: &str, admin_path: &str) -> Self {
Self {
db,
base_path: Arc::from(base_path),
admin_path: Arc::from(admin_path),
}
}
pub fn db(&self) -> &Db {
&self.db
}
pub fn base_path(&self) -> &str {
&self.base_path
}
pub fn admin_path(&self) -> &str {
&self.admin_path
}
pub fn url(&self, relative: &str) -> String {
join(&self.base_path, relative)
}
}
fn join(base: &str, relative: &str) -> String {
let base = base.trim_end_matches('/');
let relative = relative.trim_start_matches('/');
if relative.is_empty() {
base.to_string()
} else {
format!("{base}/{relative}")
}
}
pub trait Screen: Send + Sync + 'static {
fn mount(&self, ctx: &RouteCtx) -> Router;
}
pub struct ScreenReg {
pub base_path: String,
pub nav_label: Option<Text>,
pub permission: String,
pub screen: Arc<dyn Screen>,
}
impl ScreenReg {
pub fn new(
base_path: impl Into<String>,
permission: impl Into<String>,
screen: Arc<dyn Screen>,
) -> Self {
Self {
base_path: base_path.into(),
nav_label: None,
permission: permission.into(),
screen,
}
}
pub fn in_menu(mut self, label: impl Into<Text>) -> Self {
self.nav_label = Some(label.into());
self
}
}
pub trait PublicRoute: Send + Sync + 'static {
fn mount(&self, ctx: &RouteCtx) -> Router;
}
pub struct PublicRouteReg {
pub path: String,
pub route: Arc<dyn PublicRoute>,
}
impl PublicRouteReg {
pub fn new(path: impl Into<String>, route: Arc<dyn PublicRoute>) -> Self {
Self {
path: path.into(),
route,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_self_link_builds_from_where_the_screen_actually_mounted() {
let base = "/admin/rainmill/location/import";
assert_eq!(
join(base, "/step2"),
"/admin/rainmill/location/import/step2"
);
assert_eq!(join(base, "step2"), "/admin/rainmill/location/import/step2");
assert_eq!(join(base, ""), "/admin/rainmill/location/import");
}
#[test]
fn a_moved_screen_moves_its_links_with_it() {
assert_eq!(join("/admin/places", "/step2"), "/admin/places/step2");
}
#[test]
fn a_registration_defaults_to_no_menu_entry() {
struct Noop;
impl Screen for Noop {
fn mount(&self, _ctx: &RouteCtx) -> Router {
Router::new()
}
}
let reg = ScreenReg::new("/import", "acme.import", Arc::new(Noop));
assert!(reg.nav_label.is_none());
assert!(reg.in_menu("Import").nav_label.is_some());
}
}