1use crate::client::ConcurClient;
6use crate::error::Result;
7use crate::models::{Address, Email, Meta, UserName};
8use reqwest::Method;
9use serde::{Deserialize, Serialize};
10
11pub struct IdentityApi<'a> {
13 client: &'a ConcurClient,
14}
15
16impl<'a> IdentityApi<'a> {
17 pub fn new(client: &'a ConcurClient) -> Self {
18 Self { client }
19 }
20
21 pub async fn list_users(&self, count: Option<u32>, cursor: Option<&str>) -> Result<UserList> {
23 let mut path = "/profile/identity/v4.1/Users".to_string();
24 let mut sep = '?';
25 if let Some(c) = count {
26 path.push_str(&format!("{}count={}", sep, c));
27 sep = '&';
28 }
29 if let Some(c) = cursor {
30 path.push_str(&format!("{}cursor={}", sep, urlencoding::encode(c)));
31 }
32 self.client
33 .request(Method::GET, &path)
34 .await?
35 .send()
36 .await?
37 .json()
38 .await
39 .map_err(Into::into)
40 }
41
42 pub async fn search_users(
44 &self,
45 filter: &str,
46 attributes: Option<Vec<String>>,
47 ) -> Result<UserList> {
48 let body = SearchRequest {
49 schemas: vec!["urn:ietf:params:scim:api:messages:concur:2.0:SearchRequest".to_string()],
50 filter: filter.to_string(),
51 attributes,
52 };
53 let req = self
54 .client
55 .request(Method::POST, "/profile/identity/v4.1/Users/.search")
56 .await?;
57 let req = ConcurClient::with_json(req, &body)?;
58 self.client.execute(req).await
59 }
60
61 pub async fn get_user(&self, id: &str) -> Result<User> {
63 self.client
64 .request(Method::GET, &format!("/profile/identity/v4.1/Users/{}", id))
65 .await?
66 .send()
67 .await?
68 .json()
69 .await
70 .map_err(Into::into)
71 }
72
73 pub async fn create_user(&self, user: &User) -> Result<User> {
75 let req = self
76 .client
77 .request(Method::POST, "/profile/identity/v4.1/Users")
78 .await?;
79 let req = ConcurClient::with_json(req, user)?;
80 self.client.execute(req).await
81 }
82
83 pub async fn patch_user(&self, id: &str, ops: &[PatchOp]) -> Result<User> {
85 let body = PatchRequest {
86 schemas: vec!["urn:ietf:params:scim:api:messages:2.0:PatchOp".to_string()],
87 operations: ops.to_vec(),
88 };
89 let req = self
90 .client
91 .request(
92 Method::PATCH,
93 &format!("/profile/identity/v4.1/Users/{}", id),
94 )
95 .await?;
96 let req = ConcurClient::with_json(req, &body)?;
97 self.client.execute(req).await
98 }
99
100 pub async fn delete_user(&self, id: &str) -> Result<()> {
102 self.client
103 .execute_unit(
104 self.client
105 .request(
106 Method::DELETE,
107 &format!("/profile/identity/v4.1/Users/{}", id),
108 )
109 .await?,
110 )
111 .await
112 }
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct SearchRequest {
117 pub schemas: Vec<String>,
118 pub filter: String,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub attributes: Option<Vec<String>>,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct PatchRequest {
125 pub schemas: Vec<String>,
126 pub operations: Vec<PatchOp>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct PatchOp {
131 pub op: String,
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub path: Option<String>,
134 #[serde(skip_serializing_if = "Option::is_none")]
135 pub value: Option<serde_json::Value>,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct UserList {
140 pub schemas: Vec<String>,
141 #[serde(rename = "totalResults")]
142 pub total_results: u64,
143 #[serde(rename = "itemsPerPage")]
144 pub items_per_page: u32,
145 pub resources: Vec<User>,
146}
147
148#[derive(Debug, Clone, Default, Serialize, Deserialize)]
149pub struct User {
150 pub schemas: Vec<String>,
151 pub id: Option<String>,
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub user_name: Option<String>,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub active: Option<bool>,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub display_name: Option<String>,
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub name: Option<UserName>,
160 #[serde(skip_serializing_if = "Option::is_none")]
161 pub emails: Option<Vec<Email>>,
162 #[serde(skip_serializing_if = "Option::is_none")]
163 pub addresses: Option<Vec<Address>>,
164 #[serde(skip_serializing_if = "Option::is_none")]
165 pub meta: Option<Meta>,
166 #[serde(skip_serializing_if = "Option::is_none")]
167 pub external_id: Option<String>,
168 #[serde(
169 rename = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
170 skip_serializing_if = "Option::is_none"
171 )]
172 pub enterprise_extension: Option<EnterpriseExtension>,
173 #[serde(
174 rename = "urn:ietf:params:scim:schemas:extension:sap:2.0:User",
175 skip_serializing_if = "Option::is_none"
176 )]
177 pub sap_extension: Option<serde_json::Value>,
178}
179
180#[derive(Debug, Clone, Default, Serialize, Deserialize)]
181pub struct EnterpriseExtension {
182 #[serde(skip_serializing_if = "Option::is_none")]
183 pub company_id: Option<String>,
184 #[serde(skip_serializing_if = "Option::is_none")]
185 pub employee_number: Option<String>,
186 #[serde(skip_serializing_if = "Option::is_none")]
187 pub cost_center: Option<String>,
188 #[serde(skip_serializing_if = "Option::is_none")]
189 pub manager: Option<serde_json::Value>,
190 #[serde(skip_serializing_if = "Option::is_none")]
191 pub start_date: Option<String>,
192 #[serde(skip_serializing_if = "Option::is_none")]
193 pub termination_date: Option<String>,
194}