Skip to main content

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}