1use crate::{AuthApiKey, AuthInvitation, AuthOrganization, AuthPasskey, InvitationStatus};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize, Serializer};
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
6pub struct OrganizationView {
7 #[serde(default, flatten)]
8 pub additional_fields: std::collections::BTreeMap<String, serde_json::Value>,
9 pub id: String,
10 pub name: String,
11 pub slug: String,
12 pub logo: Option<String>,
13 #[serde(
14 serialize_with = "serialize_json_option_as_string",
15 skip_serializing_if = "Option::is_none"
16 )]
17 #[serde(
18 default,
19 deserialize_with = "crate::utils::json::deserialize_optional_value"
20 )]
21 pub metadata: Option<serde_json::Value>,
22 #[serde(rename = "createdAt")]
23 #[serde(serialize_with = "crate::utils::datetime::serialize")]
24 pub created_at: DateTime<Utc>,
25 #[serde(rename = "updatedAt")]
26 #[serde(serialize_with = "crate::utils::datetime::serialize")]
27 pub updated_at: DateTime<Utc>,
28}
29
30impl<T: AuthOrganization> From<&T> for OrganizationView {
31 fn from(org: &T) -> Self {
32 Self {
33 additional_fields: org.additional_fields(),
34 id: org.id().into_owned(),
35 name: org.name().to_owned(),
36 slug: org.slug().to_owned(),
37 logo: org.logo().map(str::to_owned),
38 metadata: org.metadata().cloned(),
39 created_at: org.created_at(),
40 updated_at: org.updated_at(),
41 }
42 }
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
47pub struct InvitationView {
48 pub id: String,
49 #[serde(rename = "organizationId")]
50 pub organization_id: String,
51 pub email: String,
52 pub role: Option<String>,
53 pub status: InvitationStatus,
54 #[serde(rename = "inviterId")]
55 pub inviter_id: String,
56 #[serde(rename = "expiresAt")]
57 #[serde(serialize_with = "crate::utils::datetime::serialize")]
58 pub expires_at: DateTime<Utc>,
59 #[serde(rename = "createdAt")]
60 #[serde(serialize_with = "crate::utils::datetime::serialize")]
61 pub created_at: DateTime<Utc>,
62 #[serde(rename = "teamId", default, skip_serializing_if = "Option::is_none")]
63 pub team_id: Option<String>,
64 #[serde(
65 flatten,
66 default,
67 deserialize_with = "crate::utils::json::deserialize_btree_map"
68 )]
69 pub extension_fields: std::collections::BTreeMap<String, serde_json::Value>,
70}
71
72impl<T: AuthInvitation> From<&T> for InvitationView {
73 fn from(inv: &T) -> Self {
74 Self {
75 id: inv.id().into_owned(),
76 organization_id: inv.organization_id().into_owned(),
77 email: inv.email().to_owned(),
78 role: inv.optional_role().map(str::to_owned),
79 status: inv.status().clone(),
80 inviter_id: inv.inviter_id().into_owned(),
81 expires_at: inv.expires_at(),
82 created_at: inv.created_at(),
83 team_id: inv.team_id().map(str::to_owned),
84 extension_fields: std::collections::BTreeMap::default(),
85 }
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
91pub struct PasskeyView {
92 pub id: String,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub name: Option<String>,
95 #[serde(rename = "credentialID")]
96 pub credential_id: String,
97 #[serde(rename = "userId")]
98 pub user_id: String,
99 #[serde(rename = "publicKey")]
100 pub public_key: String,
101 pub counter: u64,
102 #[serde(rename = "deviceType")]
103 pub device_type: String,
104 #[serde(rename = "backedUp")]
105 pub backed_up: bool,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub transports: Option<String>,
108 #[serde(rename = "createdAt")]
109 pub created_at: String,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub aaguid: Option<String>,
112}
113
114impl<T: AuthPasskey> From<&T> for PasskeyView {
115 fn from(pk: &T) -> Self {
116 Self {
117 id: pk.id().into_owned(),
118 name: pk.name().map(str::to_owned),
119 credential_id: pk.credential_id().to_owned(),
120 user_id: pk.user_id().into_owned(),
121 public_key: pk.public_key().to_owned(),
122 counter: pk.counter(),
123 device_type: pk.device_type().to_owned(),
124 backed_up: pk.backed_up(),
125 transports: pk.transports().map(str::to_owned),
126 created_at: pk
127 .created_at()
128 .to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
129 aaguid: pk.aaguid().map(str::to_owned),
130 }
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
139pub struct ApiKeyView {
140 pub id: String,
141 pub name: Option<String>,
142 pub start: Option<String>,
143 pub prefix: Option<String>,
144 #[serde(rename = "referenceId")]
145 pub reference_id: String,
146 #[serde(rename = "configId")]
147 pub config_id: String,
148 #[serde(rename = "refillInterval")]
149 #[serde(serialize_with = "serialize_api_key_number")]
150 pub refill_interval: Option<f64>,
151 #[serde(rename = "refillAmount")]
152 #[serde(serialize_with = "serialize_api_key_number")]
153 pub refill_amount: Option<f64>,
154 #[serde(rename = "lastRefillAt")]
155 pub last_refill_at: Option<String>,
156 pub enabled: bool,
157 #[serde(rename = "rateLimitEnabled")]
158 pub rate_limit_enabled: bool,
159 #[serde(rename = "rateLimitTimeWindow")]
160 #[serde(serialize_with = "serialize_api_key_number")]
161 pub rate_limit_time_window: Option<f64>,
162 #[serde(rename = "rateLimitMax")]
163 #[serde(serialize_with = "serialize_api_key_number")]
164 pub rate_limit_max: Option<f64>,
165 #[serde(rename = "requestCount")]
166 #[serde(serialize_with = "serialize_api_key_number")]
167 pub request_count: Option<f64>,
168 #[serde(serialize_with = "serialize_api_key_number")]
169 pub remaining: Option<f64>,
170 #[serde(rename = "lastRequest")]
171 pub last_request: Option<String>,
172 #[serde(rename = "expiresAt")]
173 pub expires_at: Option<String>,
174 #[serde(rename = "createdAt")]
175 pub created_at: String,
176 #[serde(rename = "updatedAt")]
177 pub updated_at: String,
178 #[serde(
179 default,
180 deserialize_with = "crate::utils::json::deserialize_optional_value"
181 )]
182 pub permissions: Option<serde_json::Value>,
183 #[serde(
184 default,
185 deserialize_with = "crate::utils::json::deserialize_optional_value"
186 )]
187 pub metadata: Option<serde_json::Value>,
188}
189
190impl<T: AuthApiKey> From<&T> for ApiKeyView {
191 fn from(ak: &T) -> Self {
192 Self {
193 id: ak.id().into_owned(),
194 name: ak.name().map(str::to_owned),
195 start: ak.start().map(str::to_owned),
196 prefix: ak.prefix().map(str::to_owned),
197 reference_id: ak.reference_id().into_owned(),
198 config_id: ak.config_id().into_owned(),
199 refill_interval: ak.refill_interval(),
200 refill_amount: ak.refill_amount(),
201 last_refill_at: ak.last_refill_at().map(api_key_wire_date),
202 enabled: ak.enabled(),
203 rate_limit_enabled: ak.rate_limit_enabled(),
204 rate_limit_time_window: ak.rate_limit_time_window(),
205 rate_limit_max: ak.rate_limit_max(),
206 request_count: ak.request_count(),
207 remaining: ak.remaining(),
208 last_request: ak.last_request().map(api_key_wire_date),
209 expires_at: ak.expires_at().map(api_key_wire_date),
210 created_at: api_key_wire_date(ak.created_at()),
211 updated_at: api_key_wire_date(ak.updated_at()),
212 permissions: ak
213 .permissions()
214 .and_then(|s| crate::utils::json::from_slice(s.as_bytes()).ok())
215 .map(|mut value| {
216 normalize_api_key_permission_dates(&mut value);
217 value
218 }),
219 metadata: ak.metadata().and_then(|s| {
220 let value = crate::utils::json::parse_value(s).ok()?;
221 if let crate::utils::json::JsValue::String(encoded) = value {
222 let mut value = crate::utils::json::parse_value(&encoded)
223 .ok()?
224 .to_json_value()
225 .ok()?;
226 normalize_api_key_permission_dates(&mut value);
227 Some(value)
228 } else {
229 value.to_json_value().ok()
230 }
231 }),
232 }
233 }
234}
235
236pub(in crate::wire) fn normalize_api_key_permission_dates(value: &mut serde_json::Value) {
237 match value {
238 serde_json::Value::String(text) => {
239 if let Some(date) = crate::utils::datetime::normalize_json_date(text) {
240 *text = date;
241 }
242 }
243 serde_json::Value::Array(values) => {
244 for value in values {
245 normalize_api_key_permission_dates(value);
246 }
247 }
248 serde_json::Value::Object(values) => {
249 for value in values.values_mut() {
250 normalize_api_key_permission_dates(value);
251 }
252 }
253 _ => {}
254 }
255}
256
257#[expect(
260 clippy::ref_option,
261 reason = "Serde field serializers receive a reference to the declared Option field type"
262)]
263pub(in crate::wire) fn serialize_json_option_as_string<S>(
264 value: &Option<serde_json::Value>,
265 serializer: S,
266) -> Result<S::Ok, S::Error>
267where
268 S: Serializer,
269{
270 match value {
271 Some(inner) => serializer
272 .serialize_some(&serde_json::to_string(inner).map_err(serde::ser::Error::custom)?),
273 None => serializer.serialize_none(),
274 }
275}
276
277#[expect(
279 clippy::as_conversions,
280 clippy::cast_possible_truncation,
281 reason = "JavaScript-compatible numbers deliberately retain IEEE754 rounding and guarded integer coercion at the wire or adapter boundary"
282)]
283#[expect(
284 clippy::ref_option,
285 reason = "Serde field serializers receive a reference to the declared Option field type"
286)]
287pub(in crate::wire) fn serialize_api_key_number<S: Serializer>(
288 value: &Option<f64>,
289 serializer: S,
290) -> Result<S::Ok, S::Error> {
291 match value {
292 Some(value) if value.fract() == 0.0 && value.abs() <= 9_007_199_254_740_991.0 => {
293 serializer.serialize_i64(*value as i64)
294 }
295 value => value.serialize(serializer),
296 }
297}
298
299pub(in crate::wire) fn api_key_wire_date(value: &str) -> String {
302 DateTime::parse_from_rfc3339(value).map_or_else(
303 |_| value.to_owned(),
304 |date| {
305 date.with_timezone(&Utc)
306 .to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
307 },
308 )
309}