1use from_response::FromResponse;
2use http::{request, Request, Response};
3use hyper::Body;
4use serde::Serialize;
5use tame_oauth::{
6 gcp::{TokenOrRequest, TokenProvider, TokenProviderWrapper},
7 Token,
8};
9use tower::{buffer::Buffer, util::BoxService, BoxError, Layer, Service, ServiceExt};
10use tower_http::map_response_body::MapResponseBodyLayer;
11use url::Url;
12
13pub mod api;
14mod body;
15use body::BodyStreamExt;
17mod error;
18mod from_response;
19
20pub type Result<T, E = error::DnsError> = std::result::Result<T, E>;
21
22#[derive(Clone)]
24pub struct DnsClient {
25 inner: Buffer<BoxService<Request<Body>, Response<Body>, BoxError>, Request<Body>>,
26 pub base_url: url::Url,
27 project_id: String,
28}
29
30impl DnsClient {
31 pub fn new<S, B>(service: S, project_id: &str) -> Self
32 where
33 S: Service<Request<Body>, Response = Response<B>> + Send + 'static,
34 S::Future: Send + 'static,
35 S::Error: Into<BoxError>,
36 B: http_body::Body<Data = bytes::Bytes> + Send + 'static,
37 B::Error: Into<BoxError>,
38 {
39 let service = MapResponseBodyLayer::new(|b: B| Body::wrap_stream(b.into_stream()))
40 .layer(service)
41 .map_err(|e| e.into());
42
43 Self {
44 inner: Buffer::new(BoxService::new(service), 1024),
45 project_id: project_id.to_string(),
46 base_url: Url::parse(&base_url(project_id)).unwrap(),
47 }
48 }
49
50 pub fn changes(&self) -> api::changes::ChangesHandler {
51 api::changes::ChangesHandler::new(self)
52 }
53
54 pub fn dns_keys(&self) -> api::dns_keys::DnsKeysHandler {
55 api::dns_keys::DnsKeysHandler::new(self)
56 }
57
58 pub fn managed_zone_operations(
59 &self,
60 ) -> api::managed_zone_operations::ManagedZoneOperationsHandler {
61 api::managed_zone_operations::ManagedZoneOperationsHandler::new(self)
62 }
63
64 pub fn managed_zones(&self) -> api::managed_zones::ManagedZonesHandler {
65 api::managed_zones::ManagedZonesHandler::new(self)
66 }
67
68 pub fn policies(&self) -> api::policies::PoliciesHandler {
69 api::policies::PoliciesHandler::new(self)
70 }
71
72 pub fn projects(&self) -> api::projects::ProjectsHandler {
73 api::projects::ProjectsHandler::new(self)
74 }
75
76 pub fn resource_record_sets(&self) -> api::resource_record_sets::ResourceRecordSetsHandler {
77 api::resource_record_sets::ResourceRecordSetsHandler::new(self)
78 }
79}
80
81impl DnsClient {
82 pub fn absolute_url(&self, path: impl AsRef<str>) -> Result<url::Url> {
83 self.base_url
84 .join(path.as_ref())
85 .map_err(error::DnsError::Url)
86 }
87
88 pub async fn post<P: Serialize + ?Sized, R: FromResponse>(
89 &self,
90 route: impl AsRef<str>,
91 body: Option<&P>,
92 ) -> Result<R> {
93 let builder = self
94 .request_builder(self.absolute_url(route)?, http::Method::POST)
95 .await?;
96
97 let request = match body {
98 Some(b) => builder.body(Body::from(
99 serde_json::to_string(b).map_err(error::DnsError::JsonWithoutPath)?,
100 ))?,
101 None => builder.body(Body::empty())?,
102 };
103
104 let response = self.execute(request).await?;
105 R::from_response(response).await
106 }
107
108 pub async fn get<R, A>(&self, route: A) -> Result<R>
109 where
110 A: AsRef<str>,
111 R: FromResponse,
112 {
113 let builder = self
114 .request_builder(self.absolute_url(route)?, http::Method::GET)
115 .await?;
116
117 let response = self.execute(builder.body(Body::empty())?).await?;
118 R::from_response(response).await
119 }
120
121 pub async fn patch<R, A, B>(&self, route: A, body: Option<&B>) -> Result<R>
122 where
123 A: AsRef<str>,
124 B: Serialize + ?Sized,
125 R: FromResponse,
126 {
127 let builder = self
128 .request_builder(self.absolute_url(route)?, http::Method::PATCH)
129 .await?;
130
131 let request = match body {
132 Some(b) => builder.body(Body::from(
133 serde_json::to_string(b).map_err(error::DnsError::JsonWithoutPath)?,
134 ))?,
135 None => builder.body(Body::empty())?,
136 };
137
138 let response = self.execute(request).await?;
139 R::from_response(response).await
140 }
141
142 pub async fn put<R, A, B>(&self, route: A, body: Option<&B>) -> Result<R>
143 where
144 A: AsRef<str>,
145 B: Serialize + ?Sized,
146 R: FromResponse,
147 {
148 let builder = self
149 .request_builder(self.absolute_url(route)?, http::Method::PUT)
150 .await?;
151
152 let request = match body {
153 Some(b) => builder.body(Body::from(
154 serde_json::to_string(b).map_err(error::DnsError::JsonWithoutPath)?,
155 ))?,
156 None => builder.body(Body::empty())?,
157 };
158
159 let response = self.execute(request).await?;
160 R::from_response(response).await
161 }
162
163 pub async fn delete<R, A>(&self, route: A) -> Result<R>
164 where
165 A: AsRef<str>,
166 R: FromResponse,
167 {
168 let builder = self
169 .request_builder(self.absolute_url(route)?, http::Method::DELETE)
170 .await?;
171
172 let response = self.execute(builder.body(Body::empty())?).await?;
173 R::from_response(response).await
174 }
175
176 pub async fn request_builder(
177 &self,
178 url: url::Url,
179 method: http::Method,
180 ) -> Result<http::request::Builder> {
181 match self.fetch_token().await {
182 Ok(token) => Ok(request::Builder::new()
183 .method(method)
184 .uri(url.to_string())
185 .header(
186 http::header::AUTHORIZATION,
187 format!("Bearer {}", token.access_token),
188 )),
189 Err(e) => Err(e),
190 }
191 }
192
193 pub async fn execute(&self, request: Request<Body>) -> Result<Response<Body>> {
194 let mut svc = self.inner.clone();
195 svc.ready()
196 .await
197 .map_err(error::DnsError::Service)?
198 .call(request)
199 .await
200 .map_err(error::DnsError::Service)
201 }
202}
203
204impl DnsClient {
205 async fn fetch_token(&self) -> Result<Token> {
206 let provider = TokenProviderWrapper::get_default_provider()
207 .expect("unable to read default token provider")
208 .expect("unable to find default token provider");
209
210 match provider.get_token(scopes()).unwrap() {
211 TokenOrRequest::Request {
212 request,
213 scope_hash,
214 ..
215 } => {
216 let (parts, body) = request.into_parts();
217
218 let mut request_builder = request::Builder::new();
219
220 for (key, value) in parts.headers.iter() {
221 request_builder
222 .headers_mut()
223 .unwrap()
224 .append(key, value.clone());
225 }
226
227 let request = request_builder
228 .method(parts.method)
229 .uri(parts.uri.to_string())
230 .body(Body::from(body))
231 .unwrap();
232
233 let response = self.execute(request).await.unwrap();
234
235 let mut response_builder = http::Response::builder()
236 .status(response.status())
237 .version(response.version());
238
239 let headers = response_builder.headers_mut().unwrap();
240 headers.extend(
241 response
242 .headers()
243 .into_iter()
244 .map(|(k, v)| (k.clone(), v.clone())),
245 );
246
247 provider
248 .parse_token_response(
249 scope_hash,
250 response_builder
251 .body(hyper::body::to_bytes(response.into_body()).await?)?,
252 )
253 .map_err(error::DnsError::Auth)
254 }
255 _ => unreachable!(),
256 }
257 }
258}
259
260fn base_url(project_id: &str) -> String {
261 format!("https://dns.googleapis.com/dns/v1/projects/{}/", project_id)
262}
263
264fn scopes() -> &'static [&'static str] {
265 &["https://www.googleapis.com/auth/ndev.clouddns.readwrite"]
266}