1use std::str::FromStr;
2
3use crate::api::features::FeaturedEndpoint;
4use crate::client::Client;
5use crate::error::ClientError;
6use derive_builder::Builder;
7use rustify::endpoint::{Endpoint, EndpointResult, MiddleWare};
8use rustify::errors::ClientError as RestClientError;
9use serde::de::DeserializeOwned;
10
11pub use crate::api::features::Features;
12
13pub mod catalog;
14pub mod check;
15pub mod connect;
16pub mod features;
17pub mod kv;
18pub mod service;
19pub mod session;
20
21#[derive(Builder, Debug)]
22#[builder(pattern = "owned")]
23pub struct ApiResponse<T> {
24 #[builder(setter(into, strip_option), default)]
25 pub cache: Option<String>,
26 #[builder(setter(into, strip_option), default)]
27 pub content_hash: Option<String>,
28 #[builder(setter(into, strip_option), default)]
29 pub default_acl_policy: Option<String>,
30 #[builder(setter(into, strip_option), default)]
31 pub index: Option<String>,
32 #[builder(setter(into, strip_option), default)]
33 pub known_leader: Option<String>,
34 #[builder(setter(into, strip_option), default)]
35 pub last_contact: Option<String>,
36 #[builder(setter(into, strip_option), default)]
37 pub query_backend: Option<String>,
38 pub response: T,
39}
40
41impl<T> ApiResponse<T> {
42 pub fn builder() -> ApiResponseBuilder<T> {
43 ApiResponseBuilder::default()
44 }
45}
46
47#[derive(Debug, Clone)]
55pub struct EndpointMiddleware {
56 pub features: Option<Features>,
57 pub token: Option<String>,
58 pub version: String,
59}
60impl MiddleWare for EndpointMiddleware {
61 #[instrument(skip(self, req), err)]
62 fn request<E: Endpoint>(
63 &self,
64 _: &E,
65 req: &mut http::Request<Vec<u8>>,
66 ) -> Result<(), rustify::errors::ClientError> {
67 debug!(
69 "Middleware: prepending {} version to URL",
70 self.version.as_str()
71 );
72 let url = url::Url::parse(req.uri().to_string().as_str()).unwrap();
73 let mut url_c = url.clone();
74 let mut segs: Vec<&str> = url.path_segments().unwrap().collect();
75 segs.insert(0, self.version.as_str());
76 url_c.path_segments_mut().unwrap().clear().extend(segs);
77 *req.uri_mut() = http::Uri::from_str(url_c.as_str()).unwrap();
78 debug!("Middleware: final URL is {}", url_c.as_str());
79
80 if let Some(token) = &self.token {
82 debug!("Middleware: adding ACL token to header");
83 req.headers_mut().append(
84 "X-Consul-Token",
85 http::HeaderValue::from_str(token).unwrap(),
86 );
87 }
88
89 if let Some(f) = &self.features {
91 f.process(req);
92 }
93
94 Ok(())
95 }
96
97 fn response<E: Endpoint>(
98 &self,
99 _: &E,
100 _: &mut http::Response<Vec<u8>>,
101 ) -> Result<(), rustify::errors::ClientError> {
102 Ok(())
103 }
104}
105
106pub async fn exec_with_empty<E>(
111 client: &impl Client,
112 endpoint: E,
113) -> Result<ApiResponse<()>, ClientError>
114where
115 E: Endpoint<Response = ()> + FeaturedEndpoint,
116{
117 info!("Executing {} and expecting no response", endpoint.path());
118 let features = endpoint.features();
119 endpoint
120 .with_middleware(&client.middle(features))
121 .exec(client.http())
122 .await
123 .map_err(parse_err)
124 .map(parse_empty)?
125}
126
127pub async fn exec_with_raw<E>(
132 client: &impl Client,
133 endpoint: E,
134) -> Result<ApiResponse<Vec<u8>>, ClientError>
135where
136 E: Endpoint + FeaturedEndpoint,
137{
138 info!("Executing {} and expecting a response", endpoint.path());
139 let features = endpoint.features();
140 endpoint
141 .with_middleware(&client.middle(features))
142 .exec(client.http())
143 .await
144 .map_err(parse_err)
145 .map(parse_raw)?
146}
147
148pub async fn exec_with_result<E>(
157 client: &impl Client,
158 endpoint: E,
159) -> Result<ApiResponse<E::Response>, ClientError>
160where
161 E: Endpoint + FeaturedEndpoint,
162{
163 info!("Executing {} and expecting a response", endpoint.path());
164 let features = endpoint.features();
165 endpoint
166 .with_middleware(&client.middle(features))
167 .exec(client.http())
168 .await
169 .map_err(parse_err)
170 .map(parse)?
171}
172
173fn parse<T>(result: EndpointResult<T>) -> Result<ApiResponse<T>, ClientError>
175where
176 T: DeserializeOwned + Send + Sync,
177{
178 let mut builder = parse_headers(result.response.headers());
179
180 let response = result.parse().map_err(ClientError::from)?;
181 builder = builder.response(response);
182 Ok(builder.build().unwrap())
183}
184
185fn parse_empty(result: EndpointResult<()>) -> Result<ApiResponse<()>, ClientError> {
187 let mut builder = parse_headers(result.response.headers());
188
189 builder = builder.response(());
190 Ok(builder.build().unwrap())
191}
192
193fn parse_raw<T>(result: EndpointResult<T>) -> Result<ApiResponse<Vec<u8>>, ClientError>
195where
196 T: DeserializeOwned + Send + Sync,
197{
198 let mut builder = parse_headers(result.response.headers());
199
200 let response = result.raw();
201 builder = builder.response(response);
202 Ok(builder.build().unwrap())
203}
204
205fn parse_headers<T>(headers: &http::HeaderMap) -> ApiResponseBuilder<T>
207where
208 T: DeserializeOwned + Send + Sync,
209{
210 let mut builder = ApiResponse::builder();
211
212 if headers.contains_key("X-Cache") {
213 builder = builder.cache(headers["X-Cache"].to_str().unwrap());
214 }
215 if headers.contains_key("X-Consul-ContentHash") {
216 builder = builder.content_hash(headers["X-Consul-ContentHash"].to_str().unwrap())
217 }
218 if headers.contains_key("X-Consul-Default-ACL-Policy") {
219 builder =
220 builder.default_acl_policy(headers["X-Consul-Default-ACL-Policy"].to_str().unwrap())
221 }
222 if headers.contains_key("X-Consul-Index") {
223 builder = builder.index(headers["X-Consul-Index"].to_str().unwrap())
224 }
225 if headers.contains_key("X-Consul-KnownLeader") {
226 builder = builder.known_leader(headers["X-Consul-KnownLeader"].to_str().unwrap())
227 }
228 if headers.contains_key("X-Consul-LastContact") {
229 builder = builder.last_contact(headers["X-Consul-LastContact"].to_str().unwrap())
230 }
231 if headers.contains_key("X-Consul-Query-Backend") {
232 builder = builder.query_backend(headers["X-Consul-Query-Backend"].to_str().unwrap())
233 }
234
235 builder
236}
237
238fn parse_err(e: RestClientError) -> ClientError {
240 if let RestClientError::ServerResponseError { code, content } = &e {
241 ClientError::APIError {
242 code: *code,
243 message: content.clone(),
244 }
245 } else {
246 ClientError::from(e)
247 }
248}