Skip to main content

mise_server/
mise.rs

1use crate::{
2    routes::{RequestProcessor, RouteContext, RouteMethod},
3    server::Server,
4};
5use serde::{Serialize, de::DeserializeOwned};
6use serde_json::Value;
7use std::{collections::HashMap, error::Error, net::SocketAddr};
8
9pub struct Request(pub http::Request<Value>);
10pub struct Response(pub http::Response<Value>);
11
12/// Final routes are always in JSON.
13pub trait Route: FnMut(Request) -> Response + 'static {}
14impl<T> Route for T where T: FnMut(Request) -> Response + 'static {}
15
16/// In some cases it's possible to install a route that only returns text.
17/// this is used for certain side behavior such as returning prometheus scrape
18/// renders. These are meant to be static and have no parameter and respond only
19/// on absolute paths: no request object is available and the response is always
20/// a string.
21pub trait TextRoute: FnMut() -> String + 'static {}
22impl<T> TextRoute for T where T: FnMut() -> String + 'static {}
23
24/// Server resource and entry point.
25///
26/// Example:
27///
28/// ```no_run
29/// use mise_server::prelude::*;
30/// use serde_json::json;
31///
32/// Mise::new()
33///     .get("/found", |_| json!("hello world").into())
34///     .get("/not", |_| StatusCode::NOT_FOUND.into())
35///     .get("/param", |r| json!(r.query_param("a").unwrap()).into())
36///     .get("/error", |_| panic!("error"))
37///     .text("/text", || "result".to_string())
38///     .post("/echo", |r| r.body().clone().into())
39///     .get("/get_echo/*", |r| json!(r.name()).into())
40///     .serve("127.0.0.1:8080".parse().unwrap());
41/// ```
42#[derive(Default)]
43pub struct Mise {
44    routes: HashMap<RouteMethod, HashMap<String, RouteContext>>,
45    text_routes: HashMap<String, Box<dyn TextRoute>>,
46}
47
48impl Mise {
49    /// Create a new default server.
50    #[must_use]
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    /// Register a text result only.
56    /// This is used for cases such as prometheus scrape renders.
57    /// Text routes always take precedence even if the route is already defined
58    /// for any other methods.
59    #[must_use]
60    pub fn text<F: TextRoute>(mut self, path: &str, f: F) -> Self {
61        self.text_routes.insert(path.to_string(), Box::new(f));
62        self
63    }
64
65    /// Register a get route.
66    #[must_use]
67    pub fn get<F: Route>(mut self, path: &str, f: F) -> Self {
68        self.regiser_method(RouteMethod::Get, path, f);
69        self
70    }
71
72    /// Register a delete route.
73    #[must_use]
74    pub fn delete<F: Route>(mut self, path: &str, f: F) -> Self {
75        self.regiser_method(RouteMethod::Delete, path, f);
76        self
77    }
78
79    /// Registers a post route. Body is obtained from [`Request::body`] and it is
80    /// always a json [Value].
81    #[must_use]
82    pub fn post<F: Route>(mut self, path: &str, f: F) -> Self {
83        self.regiser_method(RouteMethod::Post, path, f);
84        self
85    }
86
87    /// Registers a put route. Body is obtained from [`Request::body`] and it is
88    /// always a json [Value].
89    #[must_use]
90    pub fn put<F: Route>(mut self, path: &str, f: F) -> Self {
91        self.regiser_method(RouteMethod::Put, path, f);
92        self
93    }
94
95    /// Registers a patch route. Body is obtained from [`Request::body`] and it is
96    /// always a json [Value].
97    #[must_use]
98    pub fn patch<F: Route>(mut self, path: &str, f: F) -> Self {
99        self.regiser_method(RouteMethod::Patch, path, f);
100        self
101    }
102
103    /// Starts the server. Blocks until the server quits.
104    /// Can panic if cannot bind the server.
105    pub fn serve(self, addr: SocketAddr) {
106        Server::serve(
107            RequestProcessor {
108                routes: self.routes,
109                text_routes: self.text_routes,
110            },
111            addr,
112        )
113        .run();
114    }
115
116    fn regiser_method<F: Route>(&mut self, method: RouteMethod, path: &str, f: F) {
117        let routes = self.routes.entry(method).or_default();
118        let d: Box<dyn Route> = Box::new(f);
119        routes.insert(path.to_string(), (path, d).into());
120    }
121}
122
123impl From<Request> for Value {
124    fn from(value: Request) -> Self {
125        value.0.body().to_owned()
126    }
127}
128
129impl From<Value> for Response {
130    fn from(value: Value) -> Self {
131        Response(http::Response::new(value))
132    }
133}
134
135impl From<http::StatusCode> for Response {
136    fn from(value: http::StatusCode) -> Self {
137        Response(
138            http::Response::builder()
139                .status(value)
140                .body(Value::Null)
141                .expect("Statically built body should not fail"),
142        )
143    }
144}
145
146pub trait Serializable: Serialize {
147    /// # Errors
148    ///
149    /// Errors if is not serializable.
150    fn to_response(&self) -> Result<Response, Box<dyn Error>> {
151        Ok(Response(http::Response::new(serde_json::to_value(self)?)))
152    }
153}
154impl<T> Serializable for T where T: Serialize {}
155
156pub trait Deserializable: DeserializeOwned {
157    /// # Errors
158    ///
159    /// Errors if is not serializable.
160    fn from_request(r: Request) -> Result<Self, Box<dyn Error>> {
161        Ok(serde_json::from_value(r.body().to_owned())?)
162    }
163}
164impl<T> Deserializable for T where T: DeserializeOwned {}
165
166impl Request {
167    /// Returns the uri path, without the query params
168    pub fn path(&self) -> &str {
169        self.0.uri().path()
170    }
171
172    pub fn query(&self) -> Option<&str> {
173        self.0.uri().query()
174    }
175
176    /// Returns the query param by name.
177    pub fn query_param(&self, name: &str) -> Option<&str> {
178        let q = self.0.uri().query()?;
179        let f = format!("{name}=");
180        let idx = q.find(&f)?;
181        let end = q[idx..].find('&').unwrap_or(q.len());
182        Some(&q[idx + f.len()..end])
183    }
184
185    /// Returns the base of the path which is the path without the last item
186    ///
187    /// eg from /p1/p2/p3 returns '/p1/p2'
188    ///
189    /// empty string when is unavailable ("/a" = "")
190    pub fn base(&self) -> &str {
191        let p = self.0.uri().path();
192        let n = self.name();
193        p[..p.len() - n.len()].trim_end_matches('/')
194    }
195
196    /// Returns the last item of the path
197    ///
198    /// eg from /p1/p2/p3 returns 'p3'
199    ///
200    /// empty string when is unavailable
201    pub fn name(&self) -> &str {
202        if !self.0.uri().path().contains('/') {
203            return "";
204        }
205        self.0.uri().path().split('/').next_back().unwrap_or("")
206    }
207
208    /// If there is a body in the request, then this will be the json of that
209    ///
210    /// If not available returns [`Value::Null`]
211    pub fn body(&self) -> &Value {
212        self.0.body()
213    }
214
215    pub(crate) fn base_star(&self) -> Option<String> {
216        if self.name().is_empty() {
217            // Cannot have a wildcard: no last item
218            return None;
219        }
220        Some(format!("{}/*", self.base()))
221    }
222}
223
224#[cfg(test)]
225mod test {
226    use super::*;
227    use serde::Deserialize;
228    use serde_json::json;
229
230    #[derive(Serialize, Deserialize)]
231    struct My {
232        val: usize,
233    }
234
235    #[test]
236    fn test_serde() {
237        let m = My { val: 1 };
238        let r = m.to_response().unwrap();
239        assert_eq!(r.0.body()["val"].clone(), 1);
240
241        let r = Request(http::Request::new(json!({"val":2})));
242        let m = My::from_request(r).unwrap();
243        assert_eq!(m.val, 2);
244    }
245
246    #[test]
247    fn test_paths() {
248        assert_eq!(requri("/").name(), "");
249        assert_eq!(requri("/a").name(), "a");
250        assert_eq!(requri("/a/b").name(), "b");
251
252        assert_eq!(requri("/").base(), "");
253        assert_eq!(requri("/a").base(), "");
254        assert_eq!(requri("/a/b").base(), "/a");
255
256        assert_eq!(requri("/").base_star(), None);
257        assert_eq!(requri("/a").base_star(), Some("/*".to_string()));
258        assert_eq!(requri("/a/b").base_star(), Some("/a/*".to_string()));
259
260        assert_eq!(requri("/?b=a").query_param("b"), Some("a"));
261    }
262
263    fn requri(uri: &str) -> Request {
264        Request(http::Request::builder().uri(uri).body(Value::Null).unwrap())
265    }
266}