1#![deny(warnings)]
2#![warn(unused_extern_crates)]
3#![deny(clippy::todo)]
4#![deny(clippy::unimplemented)]
5#![deny(clippy::unwrap_used)]
6#![deny(clippy::expect_used)]
7#![deny(clippy::panic)]
8#![deny(clippy::unreachable)]
9#![deny(clippy::await_holding_lock)]
10#![deny(clippy::needless_pass_by_value)]
11#![deny(clippy::trivially_copy_pass_by_ref)]
12
13use base64urlsafedata::Base64UrlSafeData;
14use serde::{Deserialize, Serialize};
15use std::collections::BTreeMap;
16use time::{format_description::well_known::Rfc3339, OffsetDateTime};
17use url::Url;
18use utoipa::ToSchema;
19use uuid::Uuid;
20
21pub mod constants;
22pub mod filter;
23pub mod group;
24pub mod user;
25
26pub mod prelude {
27 pub use crate::constants::*;
28 pub use crate::user::MultiValueAttr;
29 pub use crate::{ScimAttr, ScimComplexAttr, ScimEntry, ScimEntryHeader, ScimMeta, ScimValue};
30}
31
32#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
33#[serde(untagged)]
34pub enum ScimAttr {
35 Bool(bool),
36 Integer(i64),
37 Decimal(f64),
38 String(String),
39 #[serde(with = "time::serde::rfc3339")]
43 DateTime(OffsetDateTime),
44
45 Binary(Base64UrlSafeData),
46 Reference(Url),
47}
48
49impl ScimAttr {
50 pub fn parse_as_datetime(&self) -> Option<Self> {
51 let s = match self {
52 ScimAttr::String(s) => s,
53 _ => return None,
54 };
55
56 OffsetDateTime::parse(s, &Rfc3339)
57 .map(ScimAttr::DateTime)
58 .ok()
59 }
60}
61
62impl From<String> for ScimAttr {
63 fn from(s: String) -> Self {
64 ScimAttr::String(s)
65 }
66}
67
68impl From<bool> for ScimAttr {
69 fn from(b: bool) -> Self {
70 ScimAttr::Bool(b)
71 }
72}
73
74impl From<u32> for ScimAttr {
75 fn from(i: u32) -> Self {
76 ScimAttr::Integer(i as i64)
77 }
78}
79
80impl From<Vec<u8>> for ScimAttr {
81 fn from(data: Vec<u8>) -> Self {
82 ScimAttr::Binary(data.into())
83 }
84}
85
86impl From<OffsetDateTime> for ScimAttr {
87 fn from(odt: OffsetDateTime) -> Self {
88 ScimAttr::DateTime(odt)
89 }
90}
91
92impl From<ScimAttr> for ScimValue {
93 fn from(sa: ScimAttr) -> Self {
94 ScimValue::Simple(sa)
95 }
96}
97
98impl Eq for ScimAttr {}
99
100impl PartialEq for ScimAttr {
101 fn eq(&self, other: &Self) -> bool {
102 match (self, other) {
103 (ScimAttr::String(l), ScimAttr::String(r)) => l == r,
104 (ScimAttr::Bool(l), ScimAttr::Bool(r)) => l == r,
105 (ScimAttr::Decimal(l), ScimAttr::Decimal(r)) => l == r,
106 (ScimAttr::Integer(l), ScimAttr::Integer(r)) => l == r,
107 (ScimAttr::DateTime(l), ScimAttr::DateTime(r)) => l == r,
108 (ScimAttr::Binary(l), ScimAttr::Binary(r)) => l == r,
109 (ScimAttr::Reference(l), ScimAttr::Reference(r)) => l == r,
110 _ => false,
111 }
112 }
113}
114
115pub type ScimComplexAttr = BTreeMap<String, ScimAttr>;
116
117#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)]
118#[serde(untagged)]
119pub enum ScimValue {
120 Simple(ScimAttr),
121 Complex(ScimComplexAttr),
122 MultiSimple(Vec<ScimAttr>),
123 MultiComplex(Vec<ScimComplexAttr>),
124}
125
126impl ScimValue {
127 pub fn len(&self) -> usize {
128 match self {
129 ScimValue::Simple(_) | ScimValue::Complex(_) => 1,
130 ScimValue::MultiSimple(a) => a.len(),
131 ScimValue::MultiComplex(a) => a.len(),
132 }
133 }
134
135 pub fn is_empty(&self) -> bool {
136 self.len() == 0
137 }
138}
139
140#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)]
141#[serde(rename_all = "camelCase", deny_unknown_fields)]
142pub struct ScimMeta {
143 pub resource_type: String,
144 #[serde(with = "time::serde::rfc3339")]
145 pub created: OffsetDateTime,
146 #[serde(with = "time::serde::rfc3339")]
147 pub last_modified: OffsetDateTime,
148 pub location: Url,
149 pub version: String,
150}
151
152#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)]
153#[serde(rename_all = "camelCase")]
154pub struct ScimEntryHeader {
155 pub schemas: Vec<String>,
156 pub id: Uuid,
157 #[serde(skip_serializing_if = "Option::is_none")]
158 pub external_id: Option<String>,
159 #[serde(skip_serializing_if = "Option::is_none")]
160 pub meta: Option<ScimMeta>,
161}
162
163#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)]
164#[serde(rename_all = "camelCase")]
165pub struct ScimEntry {
166 pub schemas: Vec<String>,
167 pub id: Uuid,
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub external_id: Option<String>,
170 #[serde(skip_serializing_if = "Option::is_none")]
171 pub meta: Option<ScimMeta>,
172 #[serde(flatten)]
173 pub attrs: BTreeMap<String, ScimValue>,
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use crate::constants::RFC7643_USER;
180
181 #[test]
182 fn parse_scim_entry() {
183 let _ = tracing_subscriber::fmt::try_init();
184
185 let u: ScimEntry =
186 serde_json::from_str(RFC7643_USER).expect("Failed to parse RFC7643_USER");
187
188 tracing::trace!(?u);
189
190 let s = serde_json::to_string_pretty(&u).expect("Failed to serialise RFC7643_USER");
191 eprintln!("{}", s);
192 }
193
194 use serde::de::{self, Deserialize, Deserializer, Visitor};
198 use std::fmt;
199 use uuid::Uuid;
200
201 #[derive(Debug)]
208 #[allow(dead_code)]
209 enum TestB {
210 Integer(i64),
211 Decimal(f64),
212 MaybeUuid(Uuid, String),
213 String(String),
214 }
215
216 struct TestBVisitor;
217
218 impl Visitor<'_> for TestBVisitor {
219 type Value = TestB;
220
221 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
222 formatter.write_str("cheese")
223 }
224
225 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
226 where
227 E: de::Error,
228 {
229 Ok(TestB::Decimal(v))
230 }
231
232 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
233 where
234 E: de::Error,
235 {
236 Ok(TestB::Integer(v as i64))
237 }
238
239 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
240 where
241 E: de::Error,
242 {
243 Ok(TestB::Integer(v))
244 }
245
246 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
247 where
248 E: de::Error,
249 {
250 Ok(if let Ok(u) = Uuid::parse_str(v) {
251 TestB::MaybeUuid(u, v.to_string())
252 } else {
253 TestB::String(v.to_string())
254 })
255 }
256 }
257
258 impl<'de> Deserialize<'de> for TestB {
259 fn deserialize<D>(deserializer: D) -> Result<TestB, D::Error>
260 where
261 D: Deserializer<'de>,
262 {
263 deserializer.deserialize_any(TestBVisitor)
264 }
265 }
266
267 #[test]
268 fn parse_enum_b() {
269 let x: TestB = serde_json::from_str("10").unwrap();
270 eprintln!("{:?}", x);
271
272 let x: TestB = serde_json::from_str("10.5").unwrap();
273 eprintln!("{:?}", x);
274
275 let x: TestB = serde_json::from_str(r#""550e8400-e29b-41d4-a716-446655440000""#).unwrap();
276 eprintln!("{:?}", x);
277
278 let x: TestB = serde_json::from_str(r#""Value""#).unwrap();
279 eprintln!("{:?}", x);
280 }
281
282 #[derive(Serialize, Debug, Deserialize, Clone)]
287 #[serde(rename_all = "lowercase", from = "&str", into = "String")]
288 enum TestC {
289 A,
290 B,
291 Unknown(String),
292 }
293
294 impl From<TestC> for String {
295 fn from(v: TestC) -> String {
296 match v {
297 TestC::A => "A".to_string(),
298 TestC::B => "B".to_string(),
299 TestC::Unknown(v) => v,
300 }
301 }
302 }
303
304 impl From<&str> for TestC {
305 fn from(v: &str) -> TestC {
306 match v {
307 "A" => TestC::A,
308 "B" => TestC::B,
309 _ => TestC::Unknown(v.to_string()),
310 }
311 }
312 }
313
314 #[test]
315 fn parse_enum_c() {
316 let x = serde_json::to_string(&TestC::A).unwrap();
317 eprintln!("{:?}", x);
318
319 let x = serde_json::to_string(&TestC::B).unwrap();
320 eprintln!("{:?}", x);
321
322 let x = serde_json::to_string(&TestC::Unknown("X".to_string())).unwrap();
323 eprintln!("{:?}", x);
324 }
325}