auths_infra_http/
registry_client.rs1use auths_core::ports::network::{NetworkError, RateLimitInfo, RegistryClient, RegistryResponse};
2use std::future::Future;
3use std::time::Duration;
4
5use crate::error::map_reqwest_error;
6use crate::request::{
7 build_get_request, build_post_request, execute_request, parse_response_bytes,
8};
9use crate::{default_client_builder, default_http_client};
10
11pub struct HttpRegistryClient {
24 client: reqwest::Client,
25}
26
27impl HttpRegistryClient {
28 pub fn new() -> Self {
29 Self {
30 client: default_http_client(),
31 }
32 }
33
34 #[allow(clippy::expect_used)]
49 pub fn new_with_timeouts(connect_timeout: Duration, request_timeout: Duration) -> Self {
50 let client = default_client_builder()
51 .connect_timeout(connect_timeout)
52 .timeout(request_timeout)
53 .build()
54 .expect("failed to build HTTP client");
55 Self { client }
56 }
57}
58
59impl Default for HttpRegistryClient {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl RegistryClient for HttpRegistryClient {
66 fn fetch_registry_data(
67 &self,
68 registry_url: &str,
69 path: &str,
70 ) -> impl Future<Output = Result<Vec<u8>, NetworkError>> + Send {
71 let url = format!("{}/{}", registry_url.trim_end_matches('/'), path);
72 let request = build_get_request(&self.client, &url);
73
74 async move {
75 let response = execute_request(request, registry_url).await?;
76 parse_response_bytes(response, path).await
77 }
78 }
79
80 fn push_registry_data(
81 &self,
82 registry_url: &str,
83 path: &str,
84 data: &[u8],
85 ) -> impl Future<Output = Result<(), NetworkError>> + Send {
86 let url = format!("{}/{}", registry_url.trim_end_matches('/'), path);
87 let request = build_post_request(&self.client, &url, data.to_vec());
88
89 async move {
90 let response = execute_request(request, registry_url).await?;
91 let _ = parse_response_bytes(response, path).await?;
92 Ok(())
93 }
94 }
95
96 fn post_json(
97 &self,
98 registry_url: &str,
99 path: &str,
100 json_body: &[u8],
101 ) -> impl Future<Output = Result<RegistryResponse, NetworkError>> + Send {
102 let url = format!("{}/{}", registry_url.trim_end_matches('/'), path);
103 let request = self
104 .client
105 .post(&url)
106 .header("Content-Type", "application/json")
107 .body(json_body.to_vec());
108 let endpoint = registry_url.to_string();
109
110 async move {
111 let response = request
112 .send()
113 .await
114 .map_err(|e| map_reqwest_error(e, &endpoint))?;
115 let status = response.status().as_u16();
116 let rate_limit = extract_rate_limit_headers(&response);
117 let body = response.bytes().await.map(|b| b.to_vec()).map_err(|e| {
118 NetworkError::InvalidResponse {
119 detail: e.to_string(),
120 }
121 })?;
122 Ok(RegistryResponse {
123 status,
124 body,
125 rate_limit,
126 })
127 }
128 }
129}
130
131fn extract_rate_limit_headers(response: &reqwest::Response) -> Option<RateLimitInfo> {
132 let headers = response.headers();
133 let limit = headers
134 .get("x-ratelimit-limit")
135 .and_then(|v| v.to_str().ok())
136 .and_then(|s| s.parse::<i32>().ok());
137 let remaining = headers
138 .get("x-ratelimit-remaining")
139 .and_then(|v| v.to_str().ok())
140 .and_then(|s| s.parse::<i32>().ok());
141 let reset = headers
142 .get("x-ratelimit-reset")
143 .and_then(|v| v.to_str().ok())
144 .and_then(|s| s.parse::<i64>().ok());
145 let tier = headers
146 .get("x-ratelimit-tier")
147 .and_then(|v| v.to_str().ok())
148 .map(String::from);
149
150 if limit.is_some() || remaining.is_some() || reset.is_some() || tier.is_some() {
151 Some(RateLimitInfo {
152 limit,
153 remaining,
154 reset,
155 tier,
156 })
157 } else {
158 None
159 }
160}