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}