Skip to main content

zammad_api/api/
users.rs

1use async_stream::try_stream;
2use futures_core::stream::BoxStream;
3use reqwest::Method;
4use serde::Serialize;
5use serde_json::{Map, Value};
6
7use crate::client::Client;
8use crate::error::{Error, Result};
9use crate::models::{NewUser, User};
10
11const PATH: &str = "api/v1/users";
12
13/// User endpoints.
14#[derive(Debug)]
15pub struct UsersApi<'a> {
16    client: &'a Client,
17}
18
19impl<'a> UsersApi<'a> {
20    pub(crate) fn new(client: &'a Client) -> Self {
21        Self { client }
22    }
23
24    /// `GET /api/v1/users/me` - the user the access token belongs to.
25    pub async fn me(&self) -> Result<User> {
26        let req = self.client.request(Method::GET, &format!("{PATH}/me"));
27        self.client.send_json(req).await
28    }
29
30    /// `GET /api/v1/users/:id`.
31    pub async fn get(&self, id: u64) -> Result<User> {
32        let req = self.client.request(Method::GET, &format!("{PATH}/{id}"));
33        self.client.send_json(req).await
34    }
35
36    /// `GET /api/v1/users` - paginated list.
37    pub fn list(&self) -> ListUsers<'a> {
38        ListUsers {
39            client: self.client,
40            per_page: 100,
41        }
42    }
43
44    /// `GET /api/v1/users/search?query=…`.
45    pub fn search(&self, query: impl Into<String>) -> SearchUsers<'a> {
46        SearchUsers {
47            client: self.client,
48            query: query.into(),
49            per_page: 100,
50        }
51    }
52
53    /// `POST /api/v1/users` - create a user from a [`NewUser`] payload.
54    pub async fn create(&self, user: &NewUser) -> Result<User> {
55        let req = self.client.request(Method::POST, PATH).json(user);
56        self.client.send_json(req).await
57    }
58
59    /// `PUT /api/v1/users/:id` - accepts any [`Serialize`] patch.
60    pub async fn update<P: Serialize + ?Sized>(&self, id: u64, patch: &P) -> Result<User> {
61        let req = self
62            .client
63            .request(Method::PUT, &format!("{PATH}/{id}"))
64            .json(patch);
65        self.client.send_json(req).await
66    }
67
68    /// Set a single field (typically a custom field) and verify the value was
69    /// persisted server-side. See [`crate::api::TicketsApi::set_field`] for
70    /// the rationale.
71    pub async fn set_field(
72        &self,
73        id: u64,
74        field: &str,
75        value: impl Into<Value>,
76    ) -> Result<User> {
77        let value: Value = value.into();
78        let mut body = Map::new();
79        body.insert(field.to_string(), value.clone());
80        let updated = self.update(id, &Value::Object(body)).await?;
81        let actual = updated.extra.get(field).cloned();
82        if actual.as_ref() != Some(&value) {
83            return Err(Error::WriteVerificationFailed {
84                field: field.into(),
85                actual,
86            });
87        }
88        Ok(updated)
89    }
90
91    /// `DELETE /api/v1/users/:id`.
92    pub async fn delete(&self, id: u64) -> Result<()> {
93        let req = self.client.request(Method::DELETE, &format!("{PATH}/{id}"));
94        self.client.send_unit(req).await
95    }
96}
97
98/// Builder + iterator for `GET /api/v1/users`.
99#[derive(Debug)]
100pub struct ListUsers<'a> {
101    client: &'a Client,
102    per_page: u32,
103}
104
105impl<'a> ListUsers<'a> {
106    /// Page size (Zammad caps at 200).
107    pub fn per_page(mut self, n: u32) -> Self {
108        self.per_page = n;
109        self
110    }
111
112    /// Fetch a single page.
113    pub async fn page(&self, page: u32) -> Result<Vec<User>> {
114        let req = self.client.request(Method::GET, PATH).query(&[
115            ("page", page.to_string()),
116            ("per_page", self.per_page.to_string()),
117        ]);
118        self.client.send_json(req).await
119    }
120
121    /// Yield every user, fetching pages lazily.
122    pub fn stream(self) -> BoxStream<'a, Result<User>> {
123        let client = self.client;
124        let per_page = self.per_page;
125        Box::pin(try_stream! {
126            let mut page = 1u32;
127            loop {
128                let req = client.request(Method::GET, PATH).query(&[
129                    ("page", page.to_string()),
130                    ("per_page", per_page.to_string()),
131                ]);
132                let chunk: Vec<User> = client.send_json(req).await?;
133                let len = chunk.len() as u32;
134                for u in chunk { yield u; }
135                if len < per_page { break; }
136                page += 1;
137            }
138        })
139    }
140}
141
142/// Builder + iterator for `GET /api/v1/users/search`.
143#[derive(Debug)]
144pub struct SearchUsers<'a> {
145    client: &'a Client,
146    query: String,
147    per_page: u32,
148}
149
150impl<'a> SearchUsers<'a> {
151    /// Page size (Zammad caps at 200).
152    pub fn per_page(mut self, n: u32) -> Self {
153        self.per_page = n;
154        self
155    }
156
157    /// Fetch a single page of search results.
158    pub async fn page(&self, page: u32) -> Result<Vec<User>> {
159        let req = self
160            .client
161            .request(Method::GET, &format!("{PATH}/search"))
162            .query(&[
163                ("query", self.query.clone()),
164                ("page", page.to_string()),
165                ("per_page", self.per_page.to_string()),
166            ]);
167        self.client.send_json(req).await
168    }
169
170    /// Yield every search hit, fetching pages lazily.
171    pub fn stream(self) -> BoxStream<'a, Result<User>> {
172        let client = self.client;
173        let per_page = self.per_page;
174        let query = self.query;
175        Box::pin(try_stream! {
176            let mut page = 1u32;
177            loop {
178                let req = client
179                    .request(Method::GET, &format!("{PATH}/search"))
180                    .query(&[
181                        ("query", query.clone()),
182                        ("page", page.to_string()),
183                        ("per_page", per_page.to_string()),
184                    ]);
185                let chunk: Vec<User> = client.send_json(req).await?;
186                let len = chunk.len() as u32;
187                for u in chunk { yield u; }
188                if len < per_page { break; }
189                page += 1;
190            }
191        })
192    }
193}