Skip to main content

jules_api/http/
endpoint.rs

1//! Endpoint module defining construction of API endpoints.
2
3use super::Method;
4
5/// Represents an API endpoint builder.
6#[derive(Debug, Clone)]
7pub struct Endpoint {
8    base_url: String,
9    path: String,
10    method: Method,
11    query_params: Vec<(String, String)>,
12}
13
14impl Endpoint {
15    /// Creates a new `Endpoint` with the given base URL and path.
16    #[must_use]
17    pub fn new(base_url: impl Into<String>, path: impl Into<String>) -> Self {
18        Self {
19            base_url: base_url.into(),
20            path: path.into(),
21            method: Method::Get,
22            query_params: Vec::new(),
23        }
24    }
25
26    /// Sets the HTTP method for the endpoint.
27    #[must_use]
28    pub fn with_method(mut self, method: Method) -> Self {
29        self.method = method;
30        self
31    }
32
33    /// Adds a query parameter to the endpoint.
34    #[must_use]
35    pub fn with_query(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
36        self.query_params.push((key.into(), value.into()));
37        self
38    }
39
40    /// Builds the full URL string for the endpoint.
41    #[must_use]
42    pub fn build_url(&self) -> String {
43        let capacity = self.base_url.len()
44            + self.path.len()
45            + usize::from(!self.query_params.is_empty())
46            + self
47                .query_params
48                .iter()
49                .map(|(k, v)| k.len() + v.len() + 2)
50                .sum::<usize>();
51
52        let mut url = String::with_capacity(capacity);
53        url.push_str(&self.base_url);
54        url.push_str(&self.path);
55
56        if !self.query_params.is_empty() {
57            url.push('?');
58            for (i, (k, v)) in self.query_params.iter().enumerate() {
59                if i > 0 {
60                    url.push('&');
61                }
62                url.push_str(k);
63                url.push('=');
64                url.push_str(v);
65            }
66        }
67
68        url
69    }
70
71    /// Returns the HTTP method for the endpoint.
72    #[must_use]
73    pub fn method(&self) -> Method {
74        self.method
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn test_endpoint_construction() {
84        let endpoint = Endpoint::new("https://api.example.com", "/v1/users")
85            .with_method(Method::Post)
86            .with_query("limit", "10")
87            .with_query("offset", "0");
88
89        assert_eq!(endpoint.method(), Method::Post);
90        assert_eq!(
91            endpoint.build_url(),
92            "https://api.example.com/v1/users?limit=10&offset=0"
93        );
94    }
95
96    #[test]
97    fn test_endpoint_no_query() {
98        let endpoint = Endpoint::new("https://api.example.com", "/v1/users");
99        assert_eq!(endpoint.build_url(), "https://api.example.com/v1/users");
100        assert_eq!(endpoint.method(), Method::Get);
101    }
102}