sofie_http/area.rs
1use sofie_core::Result;
2use sofie_db::{Aggregate, Resolve};
3
4use crate::RequestContext;
5
6/// Its principal resolves only in its own area.
7pub trait Area: Send + Sync + 'static {
8 /// The folder under a module's `src/areas/`, such as `web`.
9 const NAME: &'static str;
10 const PREFIX: &'static str;
11 const SIGN_IN: &'static str;
12 /// Accepts `Authorization: Bearer` beside the session cookie.
13 #[cfg(feature = "tokens")]
14 const TOKENS: bool = false;
15 /// Read by `sofie gen` and the build script, which compile the area's screens for it.
16 const CLIENT: Client = Client::Html;
17 /// Fixes the area's access, so the two cannot disagree.
18 type Principal: AreaPrincipal;
19}
20
21/// What an area's pages are: HTML over the wire, or the same templates compiled into a client
22/// framework's components when the app is built.
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
24pub enum Client {
25 #[default]
26 Html,
27 Svelte,
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum Access {
32 /// Each endpoint states its own limits.
33 Public,
34 /// Another owned record is not found, and a write to one is refused.
35 Owned,
36 #[cfg(feature = "permissions")]
37 Permitted,
38}
39
40/// An aggregate is its own principal, owning its records; a marker such as sofie-identity's
41/// `Permitted` names the aggregate that signs in under another access.
42pub trait AreaPrincipal: Send + 'static {
43 const ACCESS: Access;
44 type Record: 'static;
45}
46
47impl<T: Aggregate> AreaPrincipal for T {
48 const ACCESS: Access = Access::Owned;
49 type Record = T;
50}
51
52pub struct Web;
53
54impl Area for Web {
55 const NAME: &'static str = "web";
56 const PREFIX: &'static str = "";
57 const SIGN_IN: &'static str = "/";
58 type Principal = Visitor;
59}
60
61/// Another area cannot resolve it:
62///
63/// ```compile_fail
64/// # use sofie_http::{Area, Routes, Visitor};
65/// struct Console;
66/// impl Area for Console {
67/// const NAME: &'static str = "console";
68/// const PREFIX: &'static str = "/console";
69/// const SIGN_IN: &'static str = "/";
70/// type Principal = Visitor;
71/// }
72/// async fn greet(_visitor: Visitor) -> sofie_core::Result<()> { Ok(()) }
73/// fn routes(routes: &mut Routes<Console>) { routes.get("/", greet); }
74/// ```
75#[derive(Debug)]
76pub struct Visitor(());
77
78/// No one signs in as one, so its `Principal` is never found.
79impl AreaPrincipal for Visitor {
80 const ACCESS: Access = Access::Public;
81 type Record = Self;
82}
83
84impl<M> Resolve<RequestContext<Web, M>> for Visitor {
85 fn resolve(_context: &mut RequestContext<Web, M>) -> impl Future<Output = Result<Self>> + Send {
86 std::future::ready(Ok(Self(())))
87 }
88}