cloud_dns/
lib.rs

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;
15// Add `into_stream()` to `http::Body`
16use body::BodyStreamExt;
17mod error;
18mod from_response;
19
20pub type Result<T, E = error::DnsError> = std::result::Result<T, E>;
21
22/// The Cloud DNS API client.
23#[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}