mise_server/
mise.rs

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