Skip to main content

dinio_sdk/api/
sinkhole_lists.rs

1//! `/api/v1/sinkhole-lists` (`../core/src/api/sinkhole_lists.ts`). A list is owned by the
2//! user who created it (`../core/src/api/ownership.ts`).
3
4use reqwest::Method;
5use uuid::Uuid;
6
7use crate::client::Client;
8use crate::error::{Error, Result};
9use crate::models::{CreatedSinkholeList, NewSinkholeList, SinkholeList, SinkholeListPatch};
10
11/// Sinkhole list routes, from [`Client::sinkhole_lists`].
12///
13/// Rules are in [`SinkholeRoles`](crate::api::SinkholeRoles), links to client identities
14/// in [`SinkholeLinks`](crate::api::SinkholeLinks). With a personal key you see and change
15/// only your own lists (all of them as an Admin). A zone key has no access: every route
16/// answers [`ErrorCode::Forbidden`](crate::ErrorCode::Forbidden).
17///
18/// Lists have an optional, non-unique name; to find one by name, see
19/// [`ops::find_sinkhole_list`](crate::ops::find_sinkhole_list).
20///
21/// # Examples
22///
23/// A blacklist for one client:
24///
25/// ```no_run
26/// use dinio_sdk::models::{NewSinkholeList, NewSinkholeRole, SinkholeListType, SinkholeRoleType};
27///
28/// # async fn example(client: &dinio_sdk::Client) -> dinio_sdk::Result<()> {
29/// let identity = client.client_identities().get("203.0.113.10").await?;
30/// let created = client
31///     .sinkhole_lists()
32///     .create(&NewSinkholeList {
33///         list_type: SinkholeListType::Blacklist,
34///         name: Some("ads".into()),
35///         is_shared: None,
36///         link_to: vec![identity.uuid],
37///     })
38///     .await?;
39/// client
40///     .sinkhole_roles()
41///     .create(&NewSinkholeRole {
42///         list_id: created.list.uuid,
43///         sinkhole_type: SinkholeRoleType::Wildcard,
44///         pattern: "*.ads.example".into(),
45///     })
46///     .await?;
47/// # Ok(())
48/// # }
49/// ```
50pub struct SinkholeLists<'a> {
51    client: &'a Client,
52}
53
54impl<'a> SinkholeLists<'a> {
55    pub(crate) fn new(client: &'a Client) -> Self {
56        Self { client }
57    }
58
59    /// The caller's lists, or with `shared` every shared list on the platform (any owner);
60    /// all at once (Core does not paginate them). Rules are not included.
61    ///
62    /// `GET /api/v1/sinkhole-lists[?shared=true]`
63    pub async fn list(&self, shared: bool) -> Result<Vec<SinkholeList>> {
64        let mut request = self.client.request(Method::GET, &["sinkhole-lists"])?;
65        if shared {
66            request = request.query(&[("shared", "true")]);
67        }
68        Ok(self.client.send(request).await?.0)
69    }
70
71    /// One list; with `with_roles`, its [`roles`](SinkholeList::roles) are filled in.
72    /// Owner or Admin only, also for a shared list.
73    ///
74    /// `GET /api/v1/sinkhole-lists/:uuid[?include=roles]`
75    ///
76    /// # Errors
77    ///
78    /// * [`ErrorCode::NotFound`](crate::ErrorCode::NotFound) if there is no such list.
79    /// * [`ErrorCode::Forbidden`](crate::ErrorCode::Forbidden) if it belongs to another
80    ///   user.
81    pub async fn get(&self, uuid: Uuid, with_roles: bool) -> Result<SinkholeList> {
82        let mut request = self
83            .client
84            .request(Method::GET, &["sinkhole-lists", &uuid.to_string()])?;
85        if with_roles {
86            request = request.query(&[("include", "roles")]);
87        }
88        Ok(self.client.send(request).await?.0)
89    }
90
91    /// Creates an empty list owned by the caller, linked to the identities in
92    /// [`link_to`](NewSinkholeList::link_to) in the same transaction.
93    ///
94    /// `POST /api/v1/sinkhole-lists`
95    ///
96    /// # Errors
97    ///
98    /// Nothing is created if:
99    ///
100    /// * [`ErrorCode::NotFound`](crate::ErrorCode::NotFound): an identity in `link_to`
101    ///   does not exist;
102    /// * [`ErrorCode::Forbidden`](crate::ErrorCode::Forbidden): one belongs to another
103    ///   user;
104    /// * [`ErrorCode::Validation`](crate::ErrorCode::Validation): the name is empty or
105    ///   longer than 100 characters.
106    pub async fn create(&self, list: &NewSinkholeList) -> Result<CreatedSinkholeList> {
107        let request = self
108            .client
109            .request(Method::POST, &["sinkhole-lists"])?
110            .json(list);
111        Ok(self.client.send(request).await?.0)
112    }
113
114    /// Changes whether a list is shared and/or its name. Owner or Admin only.
115    ///
116    /// `PATCH /api/v1/sinkhole-lists/:uuid`
117    ///
118    /// # Errors
119    ///
120    /// * [`Error::InvalidInput`] if `patch` changes nothing.
121    /// * [`ErrorCode::NotFound`](crate::ErrorCode::NotFound) if there is no such list.
122    /// * [`ErrorCode::Forbidden`](crate::ErrorCode::Forbidden) if it belongs to another
123    ///   user.
124    /// * [`ErrorCode::Validation`](crate::ErrorCode::Validation) for an empty or too long
125    ///   name.
126    pub async fn update(&self, uuid: Uuid, patch: &SinkholeListPatch) -> Result<SinkholeList> {
127        if patch.is_empty() {
128            return Err(Error::InvalidInput("nothing to change".into()));
129        }
130        let request = self
131            .client
132            .request(Method::PATCH, &["sinkhole-lists", &uuid.to_string()])?
133            .json(patch);
134        Ok(self.client.send(request).await?.0)
135    }
136
137    /// Shares or unshares a list; shorthand for [`update`](Self::update). Unsharing keeps
138    /// the links other users already have. Owner or Admin only.
139    ///
140    /// `PATCH /api/v1/sinkhole-lists/:uuid`
141    ///
142    /// # Errors
143    ///
144    /// As [`update`](Self::update).
145    pub async fn set_shared(&self, uuid: Uuid, is_shared: bool) -> Result<SinkholeList> {
146        let patch = SinkholeListPatch {
147            is_shared: Some(is_shared),
148            ..Default::default()
149        };
150        self.update(uuid, &patch).await
151    }
152
153    /// Deletes a list with its rules and every link to it, including other users' links
154    /// to a shared list. Owner or Admin only.
155    ///
156    /// `DELETE /api/v1/sinkhole-lists/:uuid`
157    ///
158    /// # Errors
159    ///
160    /// * [`ErrorCode::NotFound`](crate::ErrorCode::NotFound) if there is no such list.
161    /// * [`ErrorCode::Forbidden`](crate::ErrorCode::Forbidden) if it belongs to another
162    ///   user.
163    pub async fn delete(&self, uuid: Uuid) -> Result<()> {
164        let request = self
165            .client
166            .request(Method::DELETE, &["sinkhole-lists", &uuid.to_string()])?;
167        self.client.send_empty(request).await
168    }
169}