Skip to main content

saluki_api/
lib.rs

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