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