dinio_sdk/api/redirects.rs
1//! `/api/v1/redirects` (`../core/src/api/redirects.ts`). A redirect is addressed by its
2//! host name and belongs to a zone; access is the zone's.
3
4use reqwest::Method;
5use serde::Serialize;
6use uuid::Uuid;
7
8use crate::api::{MAX_PER_PAGE, PageRequest};
9use crate::client::Client;
10use crate::envelope::Page;
11use crate::error::Result;
12use crate::models::{NewRedirect, Redirect, RedirectPatch};
13
14#[derive(Serialize)]
15struct ListQuery {
16 #[serde(skip_serializing_if = "Option::is_none")]
17 zone_id: Option<Uuid>,
18 #[serde(flatten)]
19 page: PageRequest,
20}
21
22/// HTTP(S) redirects by host name, from [`Client::redirects`].
23///
24/// A redirect lives on a host name inside a zone, and access to it is access to that zone:
25/// the zone's key, a member of the zone, or an Admin. Creating one makes Core add the DNS
26/// records that point the name at the platform; deleting it removes them.
27pub struct Redirects<'a> {
28 client: &'a Client,
29}
30
31impl<'a> Redirects<'a> {
32 pub(crate) fn new(client: &'a Client) -> Self {
33 Self { client }
34 }
35
36 /// One page of the redirects of `zone_id`, or of every zone the caller can access when
37 /// `None` (a zone key: its zone; an Admin: all). Without `certificate` status; read one
38 /// redirect with [`get`](Self::get) for that.
39 ///
40 /// `GET /api/v1/redirects[?zone_id=...]`
41 ///
42 /// # Errors
43 ///
44 /// [`ErrorCode::Forbidden`](crate::ErrorCode::Forbidden) if the caller has no access to
45 /// `zone_id`.
46 pub async fn list(&self, zone_id: Option<Uuid>, page: PageRequest) -> Result<Page<Redirect>> {
47 let query = ListQuery { zone_id, page };
48 let request = self
49 .client
50 .request(Method::GET, &["redirects"])?
51 .query(&query);
52 let (items, meta) = self.client.send(request).await?;
53 Ok(Page { items, meta })
54 }
55
56 /// All redirects [`list`](Self::list) would return, following pagination.
57 ///
58 /// `GET /api/v1/redirects`, as many times as there are pages.
59 ///
60 /// # Errors
61 ///
62 /// As [`list`](Self::list).
63 pub async fn list_all(&self, zone_id: Option<Uuid>) -> Result<Vec<Redirect>> {
64 let mut all = Vec::new();
65 let mut page = 1;
66 loop {
67 let request = PageRequest {
68 page: Some(page),
69 per_page: Some(MAX_PER_PAGE),
70 };
71 let Page { items, meta } = self.list(zone_id, request).await?;
72 all.extend(items);
73 match meta {
74 Some(meta) if page < meta.total_pages => page += 1,
75 _ => return Ok(all),
76 }
77 }
78 }
79
80 /// One redirect by host name, with its `certificate` status for `tls` = `Auto`.
81 ///
82 /// `GET /api/v1/redirects/:name`
83 ///
84 /// # Errors
85 ///
86 /// [`ErrorCode::NotFound`](crate::ErrorCode::NotFound) if there is no such redirect.
87 pub async fn get(&self, name: &str) -> Result<Redirect> {
88 let request = self.client.request(Method::GET, &["redirects", name])?;
89 Ok(self.client.send(request).await?.0)
90 }
91
92 /// Creates a redirect and the DNS records that point its name at the platform; Relay
93 /// starts answering HTTP(S) requests for the name. With `tls` = `Auto`
94 /// Core starts issuing a certificate right away. Returns it with the name normalized.
95 ///
96 /// `POST /api/v1/redirects`
97 ///
98 /// # Errors
99 ///
100 /// * [`ErrorCode::Conflict`](crate::ErrorCode::Conflict) if the name already has a
101 /// redirect, is not inside the zone, belongs to a more specific hosted zone or is
102 /// delegated away, already has A/AAAA/CNAME records of the user's own, or (`Auto`) is
103 /// covered by another certificate. [`ApiError::details`](crate::ApiError::details)
104 /// lists the reasons.
105 /// * [`ErrorCode::Validation`](crate::ErrorCode::Validation) for a bad name, code or
106 /// URL.
107 ///
108 /// # Examples
109 ///
110 /// ```no_run
111 /// use dinio_sdk::models::{NewRedirect, RedirectTls};
112 /// use dinio_sdk::ops;
113 ///
114 /// # async fn example(client: &dinio_sdk::Client) -> dinio_sdk::Result<()> {
115 /// let zone = ops::find_zone_for_host(client, "go.example.com").await?;
116 /// let redirect = NewRedirect {
117 /// zone_id: zone.uuid,
118 /// name: "go.example.com".into(),
119 /// code: 302,
120 /// location: "https://example.com/landing".into(),
121 /// tls: Some(RedirectTls::Auto),
122 /// };
123 /// client.redirects().create(&redirect).await?;
124 /// # Ok(())
125 /// # }
126 /// ```
127 pub async fn create(&self, redirect: &NewRedirect) -> Result<Redirect> {
128 let request = self
129 .client
130 .request(Method::POST, &["redirects"])?
131 .json(redirect);
132 Ok(self.client.send(request).await?.0)
133 }
134
135 /// Changes a redirect's code, target and/or certificate source; fields left `None` stay
136 /// as they are. Its DNS records do not change.
137 ///
138 /// `PATCH /api/v1/redirects/:name`
139 ///
140 /// # Errors
141 ///
142 /// * [`ErrorCode::NotFound`](crate::ErrorCode::NotFound) if there is no such redirect.
143 /// * [`ErrorCode::Validation`](crate::ErrorCode::Validation) for a bad code or URL.
144 /// * [`ErrorCode::Conflict`](crate::ErrorCode::Conflict) when switching to `Auto` while
145 /// another certificate covers the name.
146 pub async fn update(&self, name: &str, patch: &RedirectPatch) -> Result<Redirect> {
147 let request = self
148 .client
149 .request(Method::PATCH, &["redirects", name])?
150 .json(patch);
151 Ok(self.client.send(request).await?.0)
152 }
153
154 /// Deletes a redirect with its DNS records and its automatic certificate.
155 ///
156 /// `DELETE /api/v1/redirects/:name`
157 ///
158 /// # Errors
159 ///
160 /// [`ErrorCode::NotFound`](crate::ErrorCode::NotFound) if there is no such redirect.
161 pub async fn delete(&self, name: &str) -> Result<()> {
162 let request = self.client.request(Method::DELETE, &["redirects", name])?;
163 self.client.send_empty(request).await
164 }
165}