1use http::uri;
2use ordered_float::OrderedFloat;
3
4pub trait RouteBuilder<'a> {
9 fn new(host: &'a str) -> Self;
11 fn build(self) -> Result<uri::Uri, http::Error>;
14 fn with_param<T: ToString>(self, name: String, value: T) -> Self;
16 fn with_path(self, path: String) -> Self;
19 fn with_path_weight(self, path: String, weight: f32) -> Self;
22 fn with_scheme(self, scheme: String) -> Self;
24}
25
26#[derive(Clone, Eq, Ord)]
27struct ApiRoutePath {
28 path: String,
29 weight: OrderedFloat<f32>,
30}
31
32impl ApiRoutePath {
33 pub fn new<'a>(path: String, weight: f32) -> Self {
34 Self{path: path.to_owned(), weight: OrderedFloat::from(weight)}
35 }
36}
37
38impl PartialEq for ApiRoutePath {
39 fn eq(&self, other: &Self) -> bool {
40 self.weight == other.weight && self.path == other.path
41 }
42}
43
44impl PartialEq<str> for ApiRoutePath {
45 fn eq(&self, other: &str) -> bool {
46 self.path == other
47 }
48}
49
50impl PartialOrd for ApiRoutePath {
51 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
52 self.weight.partial_cmp(&other.weight)
53 }
54}
55
56impl ToString for ApiRoutePath {
57 fn to_string(&self) -> String {
58 self.path.clone()
59 }
60}
61
62pub struct ApiRouteBuilder<'a> {
63 hostname: &'a str,
64 parameters: Vec<String>,
65 scheme: Option<String>,
66 sub_paths: Vec<ApiRoutePath>,
67}
68
69impl<'a> ApiRouteBuilder<'a> {
70 fn insert_param<T: ToString>(mut self, name: String, value: T) -> Self {
71 self.parameters.push(format!("{name}={}", value.to_string()));
72 self
73 }
74
75 fn insert_path(mut self, path: String, weight: Option<f32>) -> Self {
76 let weight = weight
77 .unwrap_or(f32::MAX)
78 .clamp(0.1, f32::MAX);
79 let path = ApiRoutePath::new(path, weight);
80 self.sub_paths.push(path);
81 self.sub_paths.sort();
82 self
83 }
84
85 fn insert_scheme(mut self, scheme: Option<String>) -> Self {
86 self.scheme = scheme;
87 self
88 }
89
90 fn parse_params(&self) -> String {
91 self.parameters.join("&")
92 }
93
94 fn parse_path(&self) -> String {
95 let mut paths = self.sub_paths.clone();
96 paths.retain(|p| p != "");
97
98 let paths: Vec<_> = paths
99 .iter()
100 .map(|p| p.to_string())
101 .collect();
102 paths.join("/").replace("//", "/")
103 }
104
105 fn parse_scheme(&self) -> String {
106 self.scheme.clone().unwrap_or(String::from("https"))
107 }
108}
109
110impl<'a> RouteBuilder<'a> for ApiRouteBuilder<'a> {
111 fn new(host: &'a str) -> Self {
112 Self{
113 hostname: host,
114 parameters: vec![],
115 scheme: None,
116 sub_paths: vec![ApiRoutePath::new(String::from("/"), 0.0)]
117 }
118 }
119
120 fn build(self) -> Result<uri::Uri, http::Error> {
128 let scheme = self.parse_scheme();
129 let hostname = self.hostname;
130 let path = self.parse_path();
131 let params = self.parse_params();
132
133 uri::Builder::new()
134 .scheme(scheme.as_str())
135 .authority(hostname)
136 .path_and_query(format!("{path}?{params}"))
137 .build()
138 }
139
140 fn with_param<T: ToString>(self, name: String, value: T) -> Self {
150 self.insert_param(name, value)
151 }
152
153 fn with_path(self, path: String) -> Self {
164 self.insert_path(path, None)
165 }
166
167 fn with_path_weight(self, path: String, weight: f32) -> Self {
179 self.insert_path(path, Some(weight))
180 }
181
182 fn with_scheme(self, scheme: String) -> Self {
193 self.insert_scheme(Some(scheme.to_owned()))
194 }
195}