Skip to main content

saluki_api/
lib.rs

1pub use axum::response;
2pub use axum::routing;
3pub use axum::Json;
4use axum::Router;
5pub use http::StatusCode;
6use tonic::service::Routes;
7
8pub mod extract {
9    pub use axum::extract::*;
10    pub use axum_extra::extract::Query;
11}
12
13// An API handler.
14//
15// API handlers define the initial state and routes for a portion of an API.
16pub trait APIHandler {
17    type State: Clone + Send + Sync + 'static;
18
19    fn generate_initial_state(&self) -> Self::State;
20    fn generate_routes(&self) -> Router<Self::State>;
21}
22
23impl<T: APIHandler> APIHandler for &T {
24    type State = T::State;
25
26    fn generate_initial_state(&self) -> Self::State {
27        (*self).generate_initial_state()
28    }
29
30    fn generate_routes(&self) -> Router<Self::State> {
31        (*self).generate_routes()
32    }
33}
34
35/// API endpoint type.
36///
37/// Identifies whether or not a route should be exposed on the unprivileged or privileged API endpoint.
38#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
39pub enum EndpointType {
40    /// The unprivileged (plain HTTP) API endpoint.
41    Unprivileged,
42
43    /// The privileged (TLS-protected) API endpoint.
44    Privileged,
45}
46
47impl EndpointType {
48    /// Returns a human-readable name for this endpoint type.
49    pub fn name(&self) -> &'static str {
50        match self {
51            Self::Unprivileged => "unprivileged",
52            Self::Privileged => "privileged",
53        }
54    }
55}
56
57/// API endpoint protocol.
58///
59/// Identifies which application protocol the route should be exposed to.
60#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
61pub enum EndpointProtocol {
62    /// HTTP.
63    Http,
64
65    /// gRPC.
66    Grpc,
67}
68
69impl EndpointProtocol {
70    /// Returns a human-readable name for this endpoint protocol.
71    pub fn name(&self) -> &'static str {
72        match self {
73            Self::Http => "HTTP",
74            Self::Grpc => "gRPC",
75        }
76    }
77}
78
79/// A set of dynamic API routes.
80///
81/// Dynamic routes allow for processes to dynamically register/unregister themselves from running API endpoints,
82/// adapting to changes in the process state and without requiring all routes to be known and declared upfront.
83#[derive(Clone, Debug)]
84pub struct DynamicRoute {
85    /// Which API endpoint these routes target.
86    endpoint_type: EndpointType,
87
88    /// Which API protocol these routes target.
89    endpoint_protocol: EndpointProtocol,
90
91    /// The routes to serve.
92    router: Router<()>,
93}
94
95impl DynamicRoute {
96    /// Creates a dynamic HTTP route from the given API handler.
97    pub fn http<T: APIHandler>(endpoint_type: EndpointType, handler: T) -> Self {
98        let router = handler.generate_routes().with_state(handler.generate_initial_state());
99        Self::new(endpoint_type, EndpointProtocol::Http, router)
100    }
101
102    /// Creates a dynamic gRPC route from the given Tonic routes.
103    pub fn grpc(endpoint_type: EndpointType, routes: Routes) -> Self {
104        let router = routes.prepare().into_axum_router();
105        Self::new(endpoint_type, EndpointProtocol::Grpc, router)
106    }
107
108    fn new(endpoint_type: EndpointType, endpoint_protocol: EndpointProtocol, router: Router<()>) -> Self {
109        Self {
110            endpoint_type,
111            endpoint_protocol,
112            router,
113        }
114    }
115
116    /// Returns the type of endpoint these routes target.
117    pub fn endpoint_type(&self) -> EndpointType {
118        self.endpoint_type
119    }
120
121    /// Returns the protocol of endpoint these routes target.
122    pub fn endpoint_protocol(&self) -> EndpointProtocol {
123        self.endpoint_protocol
124    }
125
126    /// Consumes this route and returns the underlying router.
127    pub fn into_router(self) -> Router<()> {
128        self.router
129    }
130}