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    pub fn new() -> Self {
51        Self::default()
52    }
53
54    /// Register a text result only.
55    /// This is used for cases such as prometheus scrape renders.
56    /// Text routes always take precedence even if the route is already defined
57    /// for any other methods.
58    pub fn text<F: TextRoute>(mut self, path: &str, f: F) -> Self {
59        self.text_routes.insert(path.to_string(), Box::new(f));
60        self
61    }
62
63    /// Register a get route.
64    pub fn get<F: Route>(mut self, path: &str, f: F) -> Self {
65        let routes = self.routes.entry(RouteMethod::Get).or_default();
66        let d: Box<dyn Route> = Box::new(f);
67        routes.insert(path.to_string(), (path, d).into());
68        self
69    }
70
71    /// Registers a post route. Body is obtained from [Request::body] and it is
72    /// always a json [Value].
73    pub fn post<F: Route>(mut self, path: &str, f: F) -> Self {
74        let routes = self.routes.entry(RouteMethod::Post).or_default();
75        let d: Box<dyn Route> = Box::new(f);
76        routes.insert(path.to_string(), (path, d).into());
77        self
78    }
79
80    /// Starts the server. Blocks until the server quits.
81    /// Can panic if cannot bind the server.
82    pub fn serve(self, addr: SocketAddr) {
83        Server::serve(
84            RequestProcessor {
85                routes: self.routes,
86                text_routes: self.text_routes,
87            },
88            addr,
89        )
90        .run();
91    }
92}
93
94impl From<Request> for Value {
95    fn from(value: Request) -> Self {
96        value.0.body().to_owned()
97    }
98}
99
100impl From<Value> for Response {
101    fn from(value: Value) -> Self {
102        Response(http::Response::new(value))
103    }
104}
105
106impl From<http::StatusCode> for Response {
107    fn from(value: http::StatusCode) -> Self {
108        Response(
109            http::Response::builder()
110                .status(value)
111                .body(Value::Null)
112                .expect("Statically built body should not fail"),
113        )
114    }
115}
116
117pub trait Serializable: Serialize {
118    fn to_response(&self) -> Result<Response, Box<dyn Error>> {
119        Ok(Response(http::Response::new(serde_json::to_value(self)?)))
120    }
121}
122impl<T> Serializable for T where T: Serialize {}
123
124pub trait Deserializable: DeserializeOwned {
125    fn from_request(r: Request) -> Result<Self, Box<dyn Error>> {
126        Ok(serde_json::from_value(r.body().to_owned())?)
127    }
128}
129impl<T> Deserializable for T where T: DeserializeOwned {}
130
131impl Request {
132    /// Returns the uri path, without the query params
133    pub fn path(&self) -> &str {
134        self.0.uri().path()
135    }
136
137    pub fn query(&self) -> Option<&str> {
138        self.0.uri().query()
139    }
140
141    /// Returns the query param by name.
142    pub fn query_param(&self, name: &str) -> Option<&str> {
143        let q = self.0.uri().query()?;
144        let f = format!("{name}=");
145        let idx = q.find(&f)?;
146        let end = q[idx..].find('&').unwrap_or(q.len());
147        Some(&q[idx + f.len()..end])
148    }
149
150    /// Returns the base of the path which is the path without the last item
151    ///
152    /// eg from /p1/p2/p3 returns '/p1/p2'
153    ///
154    /// empty string when is unavailable ("/a" = "")
155    pub fn base(&self) -> &str {
156        let p = self.0.uri().path();
157        let n = self.name();
158        p[..p.len() - n.len()].trim_end_matches("/")
159    }
160
161    /// Returns the last item of the path
162    ///
163    /// eg from /p1/p2/p3 returns 'p3'
164    ///
165    /// empty string when is unavailable
166    pub fn name(&self) -> &str {
167        if !self.0.uri().path().contains("/") {
168            return "";
169        }
170        self.0.uri().path().split("/").last().unwrap_or("")
171    }
172
173    /// If there is a body in the request, then this will be the json of that
174    ///
175    /// If not available returns [Value::Null]
176    pub fn body(&self) -> &Value {
177        self.0.body()
178    }
179
180    pub(crate) fn base_star(&self) -> Option<String> {
181        if self.name().is_empty() {
182            // Cannot have a wildcard: no last item
183            return None;
184        }
185        Some(format!("{}/*", self.base()))
186    }
187}
188
189#[cfg(test)]
190mod test {
191    use super::*;
192    use serde::Deserialize;
193    use serde_json::json;
194
195    #[derive(Serialize, Deserialize)]
196    struct My {
197        val: usize,
198    }
199
200    #[test]
201    fn test_serde() {
202        let m = My { val: 1 };
203        let r = m.to_response().unwrap();
204        assert_eq!(r.0.body()["val"].clone(), 1);
205
206        let r = Request(http::Request::new(json!({"val":2})));
207        let m = My::from_request(r).unwrap();
208        assert_eq!(m.val, 2);
209    }
210
211    #[test]
212    fn test_paths() {
213        assert_eq!(requri("/").name(), "");
214        assert_eq!(requri("/a").name(), "a");
215        assert_eq!(requri("/a/b").name(), "b");
216
217        assert_eq!(requri("/").base(), "");
218        assert_eq!(requri("/a").base(), "");
219        assert_eq!(requri("/a/b").base(), "/a");
220
221        assert_eq!(requri("/").base_star(), None);
222        assert_eq!(requri("/a").base_star(), Some("/*".to_string()));
223        assert_eq!(requri("/a/b").base_star(), Some("/a/*".to_string()));
224
225        assert_eq!(requri("/?b=a").query_param("b"), Some("a"));
226    }
227
228    fn requri(uri: &str) -> Request {
229        Request(http::Request::builder().uri(uri).body(Value::Null).unwrap())
230    }
231}