scim_proto/
user.rs

1use crate::ScimEntryHeader;
2use base64urlsafedata::Base64UrlSafeData;
3use std::fmt;
4use url::Url;
5use uuid::Uuid;
6
7use serde::{Deserialize, Serialize};
8use serde_with::skip_serializing_none;
9
10#[skip_serializing_none]
11#[derive(Serialize, Deserialize, Debug, Clone)]
12#[serde(rename_all = "camelCase")]
13pub struct Name {
14    // The full name including all middle names and titles
15    formatted: Option<String>,
16    family_name: Option<String>,
17    given_name: Option<String>,
18    middle_name: Option<String>,
19    honorific_prefix: Option<String>,
20    honorific_suffix: Option<String>,
21}
22
23/*
24// https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.5
25//
26// https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
27// Same as locale?
28#[derive(Serialize, Deserialize, Debug, Clone)]
29enum Language {
30    en,
31}
32*/
33
34// https://datatracker.ietf.org/doc/html/rfc5646
35#[allow(non_camel_case_types)]
36#[derive(Serialize, Deserialize, Debug, Clone)]
37pub enum Locale {
38    en,
39    #[serde(rename = "en-AU")]
40    en_AU,
41    #[serde(rename = "en-US")]
42    en_US,
43    de,
44    #[serde(rename = "de-DE")]
45    de_DE,
46}
47
48impl fmt::Display for Locale {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        match self {
51            Locale::en => write!(f, "en"),
52            Locale::en_AU => write!(f, "en-AU"),
53            Locale::en_US => write!(f, "en-US"),
54            Locale::de => write!(f, "de"),
55            Locale::de_DE => write!(f, "de-DE"),
56        }
57    }
58}
59
60#[allow(non_camel_case_types)]
61#[derive(Serialize, Deserialize, Debug, Clone)]
62pub enum Timezone {
63    #[serde(rename = "Australia/Brisbane")]
64    australia_brisbane,
65    #[serde(rename = "America/Los_Angeles")]
66    america_los_angeles,
67}
68
69impl fmt::Display for Timezone {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        match self {
72            Timezone::australia_brisbane => write!(f, "Australia/Brisbane"),
73            Timezone::america_los_angeles => write!(f, "America/Los_Angeles"),
74        }
75    }
76}
77
78#[skip_serializing_none]
79#[derive(Serialize, Deserialize, Debug, Clone, Default)]
80#[serde(rename_all = "camelCase")]
81pub struct MultiValueAttr {
82    #[serde(rename = "type")]
83    pub type_: Option<String>,
84    pub primary: Option<bool>,
85    pub display: Option<String>,
86    #[serde(rename = "$ref")]
87    pub ref_: Option<Url>,
88    pub value: String,
89}
90
91#[skip_serializing_none]
92#[derive(Serialize, Deserialize, Debug, Clone)]
93#[serde(rename_all = "camelCase")]
94pub struct Photo {
95    #[serde(rename = "type")]
96    type_: Option<String>,
97    primary: Option<bool>,
98    display: Option<String>,
99    #[serde(rename = "$ref")]
100    ref_: Option<Url>,
101    value: Url,
102}
103
104#[skip_serializing_none]
105#[derive(Serialize, Deserialize, Debug, Clone)]
106pub struct Binary {
107    #[serde(rename = "type")]
108    type_: Option<String>,
109    primary: Option<bool>,
110    display: Option<String>,
111    #[serde(rename = "$ref")]
112    ref_: Option<Url>,
113    value: Base64UrlSafeData,
114}
115
116#[skip_serializing_none]
117#[derive(Serialize, Deserialize, Debug, Clone)]
118#[serde(rename_all = "camelCase")]
119pub struct Address {
120    #[serde(rename = "type")]
121    type_: Option<String>,
122    primary: Option<bool>,
123    formatted: Option<String>,
124    street_address: Option<String>,
125    locality: Option<String>,
126    region: Option<String>,
127    postal_code: Option<String>,
128    country: Option<String>,
129}
130
131/*
132#[derive(Serialize, Deserialize, Debug, Clone)]
133enum Membership {
134    Direct,
135    Indirect,
136}
137*/
138
139#[skip_serializing_none]
140#[derive(Serialize, Deserialize, Debug, Clone)]
141#[serde(rename_all = "camelCase")]
142pub struct Group {
143    #[serde(rename = "type")]
144    type_: Option<String>,
145    #[serde(rename = "$ref")]
146    ref_: Url,
147    value: Uuid,
148    display: String,
149}
150
151#[skip_serializing_none]
152#[derive(Serialize, Deserialize, Debug, Clone)]
153#[serde(rename_all = "camelCase")]
154pub struct User {
155    #[serde(flatten)]
156    entry: ScimEntryHeader,
157    // required, must be unique, string.
158    user_name: String,
159    // Components of the users name.
160    name: Option<Name>,
161    // required, must be unique, string.
162    display_name: Option<String>,
163    nick_name: Option<String>,
164    profile_url: Option<Url>,
165    title: Option<String>,
166    user_type: Option<String>,
167    preferred_language: Option<Locale>,
168    locale: Option<Locale>,
169    // https://datatracker.ietf.org/doc/html/rfc6557
170    // How can we validate this? https://docs.rs/iana-time-zone/0.1.51/iana_time_zone/fn.get_timezone.html
171    timezone: Option<Timezone>,
172    active: bool,
173    password: Option<String>,
174    #[serde(default, skip_serializing_if = "Vec::is_empty")]
175    emails: Vec<MultiValueAttr>,
176    #[serde(default, skip_serializing_if = "Vec::is_empty")]
177    phone_numbers: Vec<MultiValueAttr>,
178    #[serde(default, skip_serializing_if = "Vec::is_empty")]
179    ims: Vec<MultiValueAttr>,
180    #[serde(default, skip_serializing_if = "Vec::is_empty")]
181    photos: Vec<Photo>,
182    #[serde(default, skip_serializing_if = "Vec::is_empty")]
183    addresses: Vec<Address>,
184    #[serde(default, skip_serializing_if = "Vec::is_empty")]
185    groups: Vec<Group>,
186    #[serde(default, skip_serializing_if = "Vec::is_empty")]
187    entitlements: Vec<MultiValueAttr>,
188    #[serde(default, skip_serializing_if = "Vec::is_empty")]
189    roles: Vec<MultiValueAttr>,
190    #[serde(default, skip_serializing_if = "Vec::is_empty")]
191    x509certificates: Vec<Binary>,
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use crate::constants::RFC7643_USER;
198
199    #[test]
200    fn parse_user() {
201        let _ = tracing_subscriber::fmt::try_init();
202
203        let u: User = serde_json::from_str(RFC7643_USER).expect("Failed to parse RFC7643_USER");
204
205        tracing::trace!(?u);
206
207        let s = serde_json::to_string_pretty(&u).expect("Failed to serialise RFC7643_USER");
208        eprintln!("{}", s);
209    }
210}