use super::Method;
use percent_encoding::{AsciiSet, NON_ALPHANUMERIC};
const QUERY_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
.remove(b'-')
.remove(b'_')
.remove(b'.')
.remove(b'~');
#[derive(Debug, Clone)]
pub struct Endpoint {
base_url: String,
path: String,
method: Method,
query_params: Vec<(String, String)>,
}
impl Endpoint {
#[must_use]
pub fn new(base_url: impl Into<String>, path: impl Into<String>) -> Self {
Self {
base_url: base_url.into(),
path: path.into(),
method: Method::Get,
query_params: Vec::new(),
}
}
#[must_use]
pub fn with_method(mut self, method: Method) -> Self {
self.method = method;
self
}
#[must_use]
pub fn with_query(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.query_params.push((key.into(), value.into()));
self
}
#[must_use]
pub fn build_url(&self) -> String {
let capacity = self.base_url.len()
+ self.path.len()
+ usize::from(!self.query_params.is_empty())
+ self
.query_params
.iter()
.map(|(k, v)| k.len() + v.len() + 2)
.sum::<usize>();
let mut url = String::with_capacity(capacity);
url.push_str(&self.base_url);
url.push_str(&self.path);
if !self.query_params.is_empty() {
url.push('?');
for (i, (k, v)) in self.query_params.iter().enumerate() {
if i > 0 {
url.push('&');
}
url.extend(percent_encoding::utf8_percent_encode(k, QUERY_ENCODE_SET));
url.push('=');
url.extend(percent_encoding::utf8_percent_encode(v, QUERY_ENCODE_SET));
}
}
url
}
#[must_use]
pub fn method(&self) -> Method {
self.method
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_endpoint_construction() {
let endpoint = Endpoint::new("https://api.example.com", "/v1/users")
.with_method(Method::Post)
.with_query("limit", "10")
.with_query("offset", "0");
assert_eq!(endpoint.method(), Method::Post);
assert_eq!(
endpoint.build_url(),
"https://api.example.com/v1/users?limit=10&offset=0"
);
}
#[test]
fn test_endpoint_no_query() {
let endpoint = Endpoint::new("https://api.example.com", "/v1/users");
assert_eq!(endpoint.build_url(), "https://api.example.com/v1/users");
assert_eq!(endpoint.method(), Method::Get);
}
#[test]
fn test_endpoint_build_url_special_characters_in_query_value() {
let endpoint = Endpoint::new("https://api.example.com", "/v1alpha/sessions")
.with_query("pageToken", "abc&x=1 def");
let url = endpoint.build_url();
assert_eq!(
url,
"https://api.example.com/v1alpha/sessions?pageToken=abc%26x%3D1%20def"
);
assert_eq!(url.matches('&').count(), 0);
assert_eq!(url.matches('=').count(), 1);
}
}