1use super::http::{AuthStrategy, HttpError, RequestFactory, ResponseBytes};
2use super::models::{
3 CloudflareDnsRecord, CloudflareDnsRecordBatch, CloudflareDnsRecordWrite, CloudflareDnsSettings,
4 CloudflareDnssec, CloudflareDnssecEdit, CloudflareDurableObjectNamespace, CloudflareEnvelope,
5 CloudflareWorkerBuild, CloudflareWorkerScript, CloudflareWorkerSecret,
6 CloudflareWorkerSettings, CloudflareZone, CloudflareZoneFilters, DomainAvailability,
7 PaginationInfo, RegisteredDomain, SecretRecord, SecretsQuota, SecretsStore,
8};
9use reqwest::header::{HeaderName, HeaderValue};
10use reqwest::StatusCode;
11use serde::{Deserialize, Serialize};
12use serde_json::{json, Value};
13
14const CLOUDFLARE_API_BASE: &str = "https://api.cloudflare.com/client/v4";
15
16#[derive(Debug, Clone)]
17pub struct CloudflareClient {
18 account_id: String,
19 http: RequestFactory,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct CloudflareStoreCreateRequest {
24 pub name: String,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct CloudflareSecretCreateRequest {
29 pub name: String,
30 pub value: String,
31 #[serde(default)]
32 pub scopes: Vec<String>,
33 #[serde(default)]
34 pub comment: Option<String>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize, Default)]
38pub struct CloudflareSecretEditRequest {
39 #[serde(default)]
40 pub name: Option<String>,
41 #[serde(default)]
42 pub value: Option<String>,
43 #[serde(default)]
44 pub scopes: Option<Vec<String>>,
45 #[serde(default)]
46 pub comment: Option<String>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct CloudflareSecretDuplicateRequest {
51 pub name: String,
52 #[serde(default)]
53 pub scopes: Vec<String>,
54 #[serde(default)]
55 pub comment: Option<String>,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct CloudflareBulkDeleteRequest {
60 pub ids: Vec<String>,
61}
62
63#[derive(Debug, Clone, Serialize, Default)]
64pub struct CloudflareZoneCreateRequest {
65 pub name: String,
66 #[serde(default)]
67 pub account: Option<CloudflareZoneAccountWrite>,
68 #[serde(default)]
69 pub jump_start: Option<bool>,
70 #[serde(rename = "type", default)]
71 pub zone_type: Option<String>,
72}
73
74#[derive(Debug, Clone, Serialize, Default)]
75pub struct CloudflareZoneEditRequest {
76 #[serde(default)]
77 pub paused: Option<bool>,
78 #[serde(rename = "type", default)]
79 pub zone_type: Option<String>,
80 #[serde(default)]
81 pub vanity_name_servers: Option<Vec<String>>,
82}
83
84#[derive(Debug, Clone, Serialize, Default)]
85pub struct CloudflareZoneAccountWrite {
86 #[serde(default)]
87 pub id: Option<String>,
88}
89
90#[derive(Debug, Clone, Serialize, Default)]
91pub struct CloudflareDnsRecordListFilters {
92 #[serde(default)]
93 pub name: Option<String>,
94 #[serde(rename = "type", default)]
95 pub record_type: Option<String>,
96 #[serde(default)]
97 pub page: Option<u64>,
98 #[serde(default)]
99 pub per_page: Option<u64>,
100}
101
102impl CloudflareClient {
103 pub fn new(
104 api_token: impl Into<String>,
105 account_id: impl Into<String>,
106 ) -> Result<Self, String> {
107 let token = api_token.into();
108 let http = RequestFactory::new(CLOUDFLARE_API_BASE)
109 .map_err(|error| error.to_string())?
110 .with_auth(AuthStrategy::Bearer(token))
111 .with_default_header(
112 HeaderName::from_static("content-type"),
113 HeaderValue::from_static("application/json"),
114 );
115 Ok(Self {
116 account_id: account_id.into(),
117 http,
118 })
119 }
120
121 pub async fn search_domains(
122 &self,
123 query: &str,
124 extension: &[String],
125 ) -> Result<Vec<DomainAvailability>, String> {
126 #[derive(Serialize)]
127 struct SearchQuery<'a> {
128 query: &'a str,
129 #[serde(skip_serializing_if = "<[String]>::is_empty")]
130 tlds: &'a [String],
131 }
132
133 let envelope: CloudflareEnvelope<Vec<DomainAvailability>> = self
134 .http
135 .get_json(
136 &format!("/accounts/{}/registrar/domain-search", self.account_id),
137 Some(&SearchQuery {
138 query,
139 tlds: extension,
140 }),
141 )
142 .await
143 .map_err(render_http_error)?;
144 Ok(envelope.result)
145 }
146
147 pub async fn check_domains(
148 &self,
149 domains: &[String],
150 ) -> Result<Vec<DomainAvailability>, String> {
151 let envelope: CloudflareEnvelope<Vec<DomainAvailability>> = self
152 .http
153 .post_json(
154 &format!("/accounts/{}/registrar/domain-check", self.account_id),
155 &json!({ "domains": domains }),
156 )
157 .await
158 .map_err(render_http_error)?;
159 Ok(envelope.result)
160 }
161
162 pub async fn list_registered_domains(
163 &self,
164 ) -> Result<(Vec<RegisteredDomain>, Option<PaginationInfo>), String> {
165 let envelope: CloudflareEnvelope<Vec<RegisteredDomain>> = self
166 .http
167 .get_json(
168 &format!("/accounts/{}/registrar/domains", self.account_id),
169 Option::<&Value>::None,
170 )
171 .await
172 .map_err(render_http_error)?;
173 Ok((envelope.result, envelope.result_info))
174 }
175
176 pub async fn list_stores(&self) -> Result<(Vec<SecretsStore>, Option<PaginationInfo>), String> {
177 let envelope: CloudflareEnvelope<Vec<SecretsStore>> = self
178 .http
179 .get_json(
180 &format!("/accounts/{}/secrets_store/stores", self.account_id),
181 Option::<&Value>::None,
182 )
183 .await
184 .map_err(render_http_error)?;
185 Ok((envelope.result, envelope.result_info))
186 }
187
188 pub async fn get_store(&self, store_id: &str) -> Result<SecretsStore, String> {
189 let envelope: CloudflareEnvelope<SecretsStore> = self
190 .http
191 .get_json(
192 &format!(
193 "/accounts/{}/secrets_store/stores/{}",
194 self.account_id, store_id
195 ),
196 Option::<&Value>::None,
197 )
198 .await
199 .map_err(render_http_error)?;
200 Ok(envelope.result)
201 }
202
203 pub async fn create_store(
204 &self,
205 request: &CloudflareStoreCreateRequest,
206 ) -> Result<SecretsStore, String> {
207 let envelope: CloudflareEnvelope<SecretsStore> = self
208 .http
209 .post_json(
210 &format!("/accounts/{}/secrets_store/stores", self.account_id),
211 request,
212 )
213 .await
214 .map_err(render_http_error)?;
215 Ok(envelope.result)
216 }
217
218 pub async fn delete_store(&self, store_id: &str) -> Result<(), String> {
219 let _: CloudflareEnvelope<Option<Value>> = self
220 .http
221 .delete_json(
222 &format!(
223 "/accounts/{}/secrets_store/stores/{}",
224 self.account_id, store_id
225 ),
226 Option::<&Value>::None,
227 )
228 .await
229 .map_err(render_http_error)?;
230 Ok(())
231 }
232
233 pub async fn list_secrets(
234 &self,
235 store_id: &str,
236 ) -> Result<(Vec<SecretRecord>, Option<PaginationInfo>), String> {
237 let envelope: CloudflareEnvelope<Vec<SecretRecord>> = self
238 .http
239 .get_json(
240 &format!(
241 "/accounts/{}/secrets_store/stores/{}/secrets",
242 self.account_id, store_id
243 ),
244 Option::<&Value>::None,
245 )
246 .await
247 .map_err(render_http_error)?;
248 Ok((envelope.result, envelope.result_info))
249 }
250
251 pub async fn get_secret(
252 &self,
253 store_id: &str,
254 secret_id: &str,
255 ) -> Result<SecretRecord, String> {
256 let envelope: CloudflareEnvelope<SecretRecord> = self
257 .http
258 .get_json(
259 &format!(
260 "/accounts/{}/secrets_store/stores/{}/secrets/{}",
261 self.account_id, store_id, secret_id
262 ),
263 Option::<&Value>::None,
264 )
265 .await
266 .map_err(render_http_error)?;
267 Ok(envelope.result)
268 }
269
270 pub async fn create_secret(
271 &self,
272 store_id: &str,
273 request: &CloudflareSecretCreateRequest,
274 ) -> Result<SecretRecord, String> {
275 let envelope: CloudflareEnvelope<Vec<SecretRecord>> = self
276 .http
277 .post_json(
278 &format!(
279 "/accounts/{}/secrets_store/stores/{}/secrets",
280 self.account_id, store_id
281 ),
282 &vec![request],
283 )
284 .await
285 .map_err(render_http_error)?;
286 envelope
287 .result
288 .into_iter()
289 .next()
290 .ok_or_else(|| "Cloudflare did not return a created secret.".to_string())
291 }
292
293 pub async fn edit_secret(
294 &self,
295 store_id: &str,
296 secret_id: &str,
297 request: &CloudflareSecretEditRequest,
298 ) -> Result<SecretRecord, String> {
299 let envelope: CloudflareEnvelope<SecretRecord> = self
300 .http
301 .patch_json(
302 &format!(
303 "/accounts/{}/secrets_store/stores/{}/secrets/{}",
304 self.account_id, store_id, secret_id
305 ),
306 request,
307 )
308 .await
309 .map_err(render_http_error)?;
310 Ok(envelope.result)
311 }
312
313 pub async fn delete_secret(&self, store_id: &str, secret_id: &str) -> Result<(), String> {
314 let _: CloudflareEnvelope<Option<Value>> = self
315 .http
316 .delete_json(
317 &format!(
318 "/accounts/{}/secrets_store/stores/{}/secrets/{}",
319 self.account_id, store_id, secret_id
320 ),
321 Option::<&Value>::None,
322 )
323 .await
324 .map_err(render_http_error)?;
325 Ok(())
326 }
327
328 pub async fn bulk_delete_secrets(
329 &self,
330 store_id: &str,
331 request: &CloudflareBulkDeleteRequest,
332 ) -> Result<(), String> {
333 let _: CloudflareEnvelope<Option<Value>> = self
334 .http
335 .delete_json_with_body(
336 &format!(
337 "/accounts/{}/secrets_store/stores/{}/secrets",
338 self.account_id, store_id
339 ),
340 Option::<&Value>::None,
341 request,
342 )
343 .await
344 .map_err(render_http_error)?;
345 Ok(())
346 }
347
348 pub async fn duplicate_secret(
349 &self,
350 store_id: &str,
351 secret_id: &str,
352 request: &CloudflareSecretDuplicateRequest,
353 ) -> Result<SecretRecord, String> {
354 let envelope: CloudflareEnvelope<SecretRecord> = self
355 .http
356 .post_json(
357 &format!(
358 "/accounts/{}/secrets_store/stores/{}/secrets/{}/duplicate",
359 self.account_id, store_id, secret_id
360 ),
361 request,
362 )
363 .await
364 .map_err(render_http_error)?;
365 Ok(envelope.result)
366 }
367
368 pub async fn get_quota(&self) -> Result<SecretsQuota, String> {
369 let envelope: CloudflareEnvelope<SecretsQuota> = self
370 .http
371 .get_json(
372 &format!("/accounts/{}/secrets_store/quota", self.account_id),
373 Option::<&Value>::None,
374 )
375 .await
376 .map_err(render_http_error)?;
377 Ok(envelope.result)
378 }
379
380 pub async fn list_worker_secrets(
381 &self,
382 script_name: &str,
383 ) -> Result<Vec<CloudflareWorkerSecret>, String> {
384 let envelope: CloudflareEnvelope<Vec<CloudflareWorkerSecret>> = self
385 .http
386 .get_json(
387 &format!(
388 "/accounts/{}/workers/scripts/{}/secrets",
389 self.account_id, script_name
390 ),
391 Option::<&Value>::None,
392 )
393 .await
394 .map_err(render_http_error)?;
395 Ok(envelope.result)
396 }
397
398 pub async fn get_worker_secret(
399 &self,
400 script_name: &str,
401 secret_name: &str,
402 ) -> Result<CloudflareWorkerSecret, String> {
403 let envelope: CloudflareEnvelope<CloudflareWorkerSecret> = self
404 .http
405 .get_json(
406 &format!(
407 "/accounts/{}/workers/scripts/{}/secrets/{}",
408 self.account_id, script_name, secret_name
409 ),
410 Option::<&Value>::None,
411 )
412 .await
413 .map_err(render_http_error)?;
414 Ok(envelope.result)
415 }
416
417 pub async fn put_worker_secret(
418 &self,
419 script_name: &str,
420 secret_name: &str,
421 secret_value: &str,
422 ) -> Result<CloudflareWorkerSecret, String> {
423 let envelope: CloudflareEnvelope<CloudflareWorkerSecret> = self
424 .http
425 .put_json(
426 &format!(
427 "/accounts/{}/workers/scripts/{}/secrets",
428 self.account_id, script_name
429 ),
430 &json!({
431 "name": secret_name,
432 "text": secret_value,
433 "type": "secret_text"
434 }),
435 )
436 .await
437 .map_err(render_http_error)?;
438 Ok(envelope.result)
439 }
440
441 pub async fn delete_worker_secret(
442 &self,
443 script_name: &str,
444 secret_name: &str,
445 ) -> Result<(), String> {
446 let _: CloudflareEnvelope<Option<Value>> = self
447 .http
448 .delete_json(
449 &format!(
450 "/accounts/{}/workers/scripts/{}/secrets/{}",
451 self.account_id, script_name, secret_name
452 ),
453 Option::<&Value>::None,
454 )
455 .await
456 .map_err(render_http_error)?;
457 Ok(())
458 }
459
460 pub async fn patch_worker_secrets_bulk(
461 &self,
462 script_name: &str,
463 patch: &Value,
464 ) -> Result<Value, String> {
465 let envelope: CloudflareEnvelope<Value> = self
466 .http
467 .patch_json(
468 &format!(
469 "/accounts/{}/workers/scripts/{}/secrets-bulk",
470 self.account_id, script_name
471 ),
472 patch,
473 )
474 .await
475 .map_err(render_http_error)?;
476 Ok(envelope.result)
477 }
478
479 pub async fn list_worker_scripts(&self) -> Result<Vec<CloudflareWorkerScript>, String> {
480 let envelope: CloudflareEnvelope<Vec<CloudflareWorkerScript>> = self
481 .http
482 .get_json(
483 &format!("/accounts/{}/workers/scripts", self.account_id),
484 Option::<&Value>::None,
485 )
486 .await
487 .map_err(render_http_error)?;
488 Ok(envelope.result)
489 }
490
491 pub async fn list_worker_builds(
492 &self,
493 worker_tag: &str,
494 ) -> Result<Vec<CloudflareWorkerBuild>, String> {
495 let envelope: CloudflareEnvelope<Vec<CloudflareWorkerBuild>> = self
496 .http
497 .get_json(
498 &format!(
499 "/accounts/{}/builds/workers/{}/builds",
500 self.account_id, worker_tag
501 ),
502 Option::<&Value>::None,
503 )
504 .await
505 .map_err(render_http_error)?;
506 Ok(envelope.result)
507 }
508
509 pub async fn get_worker_build_logs(&self, build_uuid: &str) -> Result<Value, String> {
510 let envelope: CloudflareEnvelope<Value> = self
511 .http
512 .get_json(
513 &format!(
514 "/accounts/{}/builds/builds/{}/logs",
515 self.account_id, build_uuid
516 ),
517 Option::<&Value>::None,
518 )
519 .await
520 .map_err(render_http_error)?;
521 Ok(envelope.result)
522 }
523
524 pub async fn get_worker_settings(
525 &self,
526 script_name: &str,
527 ) -> Result<Option<CloudflareWorkerSettings>, String> {
528 let result: Result<CloudflareEnvelope<CloudflareWorkerSettings>, HttpError> = self
529 .http
530 .get_json(
531 &format!(
532 "/accounts/{}/workers/scripts/{}/settings",
533 self.account_id, script_name
534 ),
535 Option::<&Value>::None,
536 )
537 .await;
538
539 match result {
540 Ok(envelope) => Ok(Some(envelope.result)),
541 Err(HttpError::Request {
542 status: Some(StatusCode::NOT_FOUND),
543 ..
544 }) => Ok(None),
545 Err(error) => Err(render_http_error(error)),
546 }
547 }
548
549 pub async fn list_durable_object_namespaces(
552 &self,
553 ) -> Result<Vec<CloudflareDurableObjectNamespace>, String> {
554 let envelope: CloudflareEnvelope<Vec<CloudflareDurableObjectNamespace>> = self
555 .http
556 .get_json(
557 &format!(
558 "/accounts/{}/workers/durable_objects/namespaces",
559 self.account_id
560 ),
561 Option::<&Value>::None,
562 )
563 .await
564 .map_err(render_http_error)?;
565 Ok(envelope.result)
566 }
567
568 pub async fn list_zones(
569 &self,
570 filters: &CloudflareZoneFilters,
571 ) -> Result<(Vec<CloudflareZone>, Option<PaginationInfo>), String> {
572 let envelope: CloudflareEnvelope<Vec<CloudflareZone>> = self
573 .http
574 .get_json("/zones", Some(filters))
575 .await
576 .map_err(render_http_error)?;
577 Ok((envelope.result, envelope.result_info))
578 }
579
580 pub async fn get_zone(&self, zone_id: &str) -> Result<CloudflareZone, String> {
581 let envelope: CloudflareEnvelope<CloudflareZone> = self
582 .http
583 .get_json(&format!("/zones/{}", zone_id), Option::<&Value>::None)
584 .await
585 .map_err(render_http_error)?;
586 Ok(envelope.result)
587 }
588
589 pub async fn create_zone(
590 &self,
591 request: &CloudflareZoneCreateRequest,
592 ) -> Result<CloudflareZone, String> {
593 let envelope: CloudflareEnvelope<CloudflareZone> = self
594 .http
595 .post_json("/zones", request)
596 .await
597 .map_err(render_http_error)?;
598 Ok(envelope.result)
599 }
600
601 pub async fn edit_zone(
602 &self,
603 zone_id: &str,
604 request: &CloudflareZoneEditRequest,
605 ) -> Result<CloudflareZone, String> {
606 let envelope: CloudflareEnvelope<CloudflareZone> = self
607 .http
608 .patch_json(&format!("/zones/{}", zone_id), request)
609 .await
610 .map_err(render_http_error)?;
611 Ok(envelope.result)
612 }
613
614 pub async fn delete_zone(&self, zone_id: &str) -> Result<(), String> {
615 let _: CloudflareEnvelope<Option<Value>> = self
616 .http
617 .delete_json(&format!("/zones/{}", zone_id), Option::<&Value>::None)
618 .await
619 .map_err(render_http_error)?;
620 Ok(())
621 }
622
623 pub async fn list_records(
624 &self,
625 zone_id: &str,
626 filters: &CloudflareDnsRecordListFilters,
627 ) -> Result<(Vec<CloudflareDnsRecord>, Option<PaginationInfo>), String> {
628 let envelope: CloudflareEnvelope<Vec<CloudflareDnsRecord>> = self
629 .http
630 .get_json(&format!("/zones/{}/dns_records", zone_id), Some(filters))
631 .await
632 .map_err(render_http_error)?;
633 Ok((envelope.result, envelope.result_info))
634 }
635
636 pub async fn get_record(
637 &self,
638 zone_id: &str,
639 record_id: &str,
640 ) -> Result<CloudflareDnsRecord, String> {
641 let envelope: CloudflareEnvelope<CloudflareDnsRecord> = self
642 .http
643 .get_json(
644 &format!("/zones/{}/dns_records/{}", zone_id, record_id),
645 Option::<&Value>::None,
646 )
647 .await
648 .map_err(render_http_error)?;
649 Ok(envelope.result)
650 }
651
652 pub async fn create_record(
653 &self,
654 zone_id: &str,
655 request: &CloudflareDnsRecordWrite,
656 ) -> Result<CloudflareDnsRecord, String> {
657 let envelope: CloudflareEnvelope<CloudflareDnsRecord> = self
658 .http
659 .post_json(&format!("/zones/{}/dns_records", zone_id), request)
660 .await
661 .map_err(render_http_error)?;
662 Ok(envelope.result)
663 }
664
665 pub async fn replace_record(
666 &self,
667 zone_id: &str,
668 record_id: &str,
669 request: &CloudflareDnsRecordWrite,
670 ) -> Result<CloudflareDnsRecord, String> {
671 let envelope: CloudflareEnvelope<CloudflareDnsRecord> = self
672 .http
673 .put_json(
674 &format!("/zones/{}/dns_records/{}", zone_id, record_id),
675 request,
676 )
677 .await
678 .map_err(render_http_error)?;
679 Ok(envelope.result)
680 }
681
682 pub async fn edit_record(
683 &self,
684 zone_id: &str,
685 record_id: &str,
686 request: &CloudflareDnsRecordWrite,
687 ) -> Result<CloudflareDnsRecord, String> {
688 let envelope: CloudflareEnvelope<CloudflareDnsRecord> = self
689 .http
690 .patch_json(
691 &format!("/zones/{}/dns_records/{}", zone_id, record_id),
692 request,
693 )
694 .await
695 .map_err(render_http_error)?;
696 Ok(envelope.result)
697 }
698
699 pub async fn delete_record(&self, zone_id: &str, record_id: &str) -> Result<(), String> {
700 let _: CloudflareEnvelope<Option<Value>> = self
701 .http
702 .delete_json(
703 &format!("/zones/{}/dns_records/{}", zone_id, record_id),
704 Option::<&Value>::None,
705 )
706 .await
707 .map_err(render_http_error)?;
708 Ok(())
709 }
710
711 pub async fn batch_records(
712 &self,
713 zone_id: &str,
714 request: &CloudflareDnsRecordBatch,
715 ) -> Result<Value, String> {
716 let envelope: CloudflareEnvelope<Value> = self
717 .http
718 .post_json(&format!("/zones/{}/dns_records/batch", zone_id), request)
719 .await
720 .map_err(render_http_error)?;
721 Ok(envelope.result)
722 }
723
724 pub async fn export_records(&self, zone_id: &str) -> Result<ResponseBytes, String> {
725 self.http
726 .get_bytes(
727 &format!("/zones/{}/dns_records/export", zone_id),
728 Option::<&Value>::None,
729 )
730 .await
731 .map_err(render_http_error)
732 }
733
734 pub async fn import_records(&self, zone_id: &str, bytes: Vec<u8>) -> Result<Value, String> {
735 let response = self
736 .http
737 .post_bytes(
738 &format!("/zones/{}/dns_records/import", zone_id),
739 bytes,
740 "text/plain",
741 )
742 .await
743 .map_err(render_http_error)?;
744 let envelope: CloudflareEnvelope<Value> =
745 serde_json::from_slice(&response.body).map_err(|error| error.to_string())?;
746 Ok(envelope.result)
747 }
748
749 pub async fn get_dnssec(&self, zone_id: &str) -> Result<CloudflareDnssec, String> {
750 let envelope: CloudflareEnvelope<CloudflareDnssec> = self
751 .http
752 .get_json(
753 &format!("/zones/{}/dnssec", zone_id),
754 Option::<&Value>::None,
755 )
756 .await
757 .map_err(render_http_error)?;
758 Ok(envelope.result)
759 }
760
761 pub async fn edit_dnssec(
762 &self,
763 zone_id: &str,
764 request: &CloudflareDnssecEdit,
765 ) -> Result<CloudflareDnssec, String> {
766 let envelope: CloudflareEnvelope<CloudflareDnssec> = self
767 .http
768 .patch_json(&format!("/zones/{}/dnssec", zone_id), request)
769 .await
770 .map_err(render_http_error)?;
771 Ok(envelope.result)
772 }
773
774 pub async fn get_dns_settings(&self, zone_id: &str) -> Result<CloudflareDnsSettings, String> {
775 let envelope: CloudflareEnvelope<CloudflareDnsSettings> = self
776 .http
777 .get_json(
778 &format!("/zones/{}/dns_settings", zone_id),
779 Option::<&Value>::None,
780 )
781 .await
782 .map_err(render_http_error)?;
783 Ok(envelope.result)
784 }
785
786 pub async fn edit_dns_settings(
787 &self,
788 zone_id: &str,
789 request: &CloudflareDnsSettings,
790 ) -> Result<CloudflareDnsSettings, String> {
791 let envelope: CloudflareEnvelope<CloudflareDnsSettings> = self
792 .http
793 .patch_json(&format!("/zones/{}/dns_settings", zone_id), request)
794 .await
795 .map_err(render_http_error)?;
796 Ok(envelope.result)
797 }
798}
799
800fn render_http_error(error: HttpError) -> String {
801 error.to_string()
802}