1use crate::abac::AttrSet;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use serde_with::skip_serializing_none;
9use std::collections::HashMap;
10use std::time::SystemTime;
11
12#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
16pub struct TnId(pub u32);
17
18impl std::fmt::Display for TnId {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 write!(f, "{}", self.0)
21 }
22}
23
24impl Serialize for TnId {
25 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
26 where
27 S: serde::Serializer,
28 {
29 serializer.serialize_u32(self.0)
30 }
31}
32
33impl<'de> Deserialize<'de> for TnId {
34 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
35 where
36 D: serde::Deserializer<'de>,
37 {
38 Ok(TnId(u32::deserialize(deserializer)?))
39 }
40}
41
42#[derive(Clone, Copy, Debug, Default)]
46pub struct Timestamp(pub i64);
47
48impl Timestamp {
49 pub fn now() -> Timestamp {
50 let res = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default();
51 Timestamp(res.as_secs().cast_signed())
52 }
53
54 pub fn from_now(delta: i64) -> Timestamp {
55 let res = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default();
56 Timestamp(res.as_secs().cast_signed() + delta)
57 }
58
59 pub fn add_seconds(&self, seconds: i64) -> Timestamp {
61 Timestamp(self.0 + seconds)
62 }
63
64 pub fn to_iso_string(&self) -> String {
66 use chrono::{DateTime, SecondsFormat};
67 DateTime::from_timestamp(self.0, 0)
68 .unwrap_or_default()
69 .to_rfc3339_opts(SecondsFormat::Secs, true)
70 }
71}
72
73impl std::fmt::Display for Timestamp {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 write!(f, "{}", self.0)
76 }
77}
78
79impl std::cmp::PartialEq for Timestamp {
80 fn eq(&self, other: &Self) -> bool {
81 self.0 == other.0
82 }
83}
84
85impl std::cmp::PartialOrd for Timestamp {
86 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
87 Some(self.cmp(other))
88 }
89}
90
91impl std::cmp::Eq for Timestamp {}
92
93impl std::cmp::Ord for Timestamp {
94 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
95 self.0.cmp(&other.0)
96 }
97}
98
99impl Serialize for Timestamp {
100 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
101 where
102 S: serde::Serializer,
103 {
104 serializer.serialize_i64(self.0)
105 }
106}
107
108impl<'de> Deserialize<'de> for Timestamp {
109 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
110 where
111 D: serde::Deserializer<'de>,
112 {
113 use serde::de::{Error, Visitor};
114
115 struct TimestampVisitor;
116
117 impl Visitor<'_> for TimestampVisitor {
118 type Value = Timestamp;
119
120 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
121 write!(f, "an integer timestamp or ISO 8601 string")
122 }
123
124 fn visit_i64<E: Error>(self, v: i64) -> Result<Self::Value, E> {
125 Ok(Timestamp(v))
126 }
127
128 fn visit_u64<E: Error>(self, v: u64) -> Result<Self::Value, E> {
129 Ok(Timestamp(v.cast_signed()))
130 }
131
132 fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
133 use chrono::DateTime;
134 DateTime::parse_from_rfc3339(v)
135 .map(|dt| Timestamp(dt.timestamp()))
136 .map_err(|_| E::custom("invalid ISO 8601 timestamp"))
137 }
138 }
139
140 deserializer.deserialize_any(TimestampVisitor)
141 }
142}
143
144pub fn serialize_timestamp_iso<S>(ts: &Timestamp, serializer: S) -> Result<S::Ok, S::Error>
146where
147 S: Serializer,
148{
149 use chrono::{DateTime, SecondsFormat};
150 let dt = DateTime::from_timestamp(ts.0, 0).unwrap_or_default();
151 serializer.serialize_str(&dt.to_rfc3339_opts(SecondsFormat::Secs, true))
152}
153
154pub fn serialize_timestamp_iso_opt<S>(
156 ts: &Option<Timestamp>,
157 serializer: S,
158) -> Result<S::Ok, S::Error>
159where
160 S: Serializer,
161{
162 match ts {
163 Some(ts) => serialize_timestamp_iso(ts, serializer),
164 None => serializer.serialize_none(),
165 }
166}
167
168#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
175pub enum Patch<T> {
176 #[default]
178 Undefined,
179 Null,
181 Value(T),
183}
184
185impl<T> Patch<T> {
186 pub fn is_undefined(&self) -> bool {
188 matches!(self, Patch::Undefined)
189 }
190
191 pub fn is_null(&self) -> bool {
193 matches!(self, Patch::Null)
194 }
195
196 pub fn is_value(&self) -> bool {
198 matches!(self, Patch::Value(_))
199 }
200
201 pub fn value(&self) -> Option<&T> {
203 match self {
204 Patch::Value(v) => Some(v),
205 _ => None,
206 }
207 }
208
209 pub fn as_option(&self) -> Option<Option<&T>> {
211 match self {
212 Patch::Undefined => None,
213 Patch::Null => Some(None),
214 Patch::Value(v) => Some(Some(v)),
215 }
216 }
217
218 pub fn map<U, F>(self, f: F) -> Patch<U>
220 where
221 F: FnOnce(T) -> U,
222 {
223 match self {
224 Patch::Undefined => Patch::Undefined,
225 Patch::Null => Patch::Null,
226 Patch::Value(v) => Patch::Value(f(v)),
227 }
228 }
229}
230
231impl<T> Serialize for Patch<T>
232where
233 T: Serialize,
234{
235 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
236 where
237 S: Serializer,
238 {
239 match self {
240 Patch::Undefined | Patch::Null => serializer.serialize_none(),
241 Patch::Value(v) => v.serialize(serializer),
242 }
243 }
244}
245
246impl<'de, T> Deserialize<'de> for Patch<T>
247where
248 T: Deserialize<'de>,
249{
250 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
251 where
252 D: Deserializer<'de>,
253 {
254 Option::<T>::deserialize(deserializer).map(|opt| match opt {
255 None => Patch::Null,
256 Some(v) => Patch::Value(v),
257 })
258 }
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
266#[serde(rename_all = "camelCase")]
267pub struct RegisterVerifyCheckRequest {
268 #[serde(rename = "type")]
269 pub typ: String, pub id_tag: String,
271 pub app_domain: Option<String>,
272 pub token: Option<String>, }
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
277#[serde(rename_all = "camelCase")]
278pub struct RegisterRequest {
279 #[serde(rename = "type")]
280 pub typ: String, pub id_tag: String,
282 pub app_domain: Option<String>,
283 pub email: String,
284 pub token: String,
285 pub lang: Option<String>,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
290#[serde(rename_all = "camelCase")]
291pub struct RegisterVerifyRequest {
292 pub id_tag: String,
293 pub token: String,
294}
295
296#[serde_with::skip_serializing_none]
298#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
299#[serde(rename_all = "camelCase")]
300pub struct Profile {
301 pub id_tag: String,
302 pub name: String,
303 #[serde(rename = "type")]
304 pub r#type: String,
305 pub profile_pic: Option<String>,
306 pub cover_pic: Option<String>,
307 pub keys: Vec<crate::auth_adapter::AuthKey>,
308 pub x: Option<HashMap<String, String>>,
310}
311
312#[serde_with::skip_serializing_none]
318#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
319#[serde(rename_all = "camelCase")]
320pub struct ProfileBase {
321 pub id_tag: String,
322 pub name: String,
323 #[serde(rename = "type")]
324 pub r#type: String,
325 pub profile_pic: Option<String>,
326 pub keys: Vec<crate::auth_adapter::AuthKey>,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
332#[serde(rename_all = "camelCase")]
333pub struct AppDomainRes {
334 pub app_domain: String,
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
339#[serde(rename_all = "camelCase")]
340pub struct ProfilePatch {
341 #[serde(default)]
342 pub name: Patch<String>,
343 #[serde(default)]
345 pub x: Option<HashMap<String, Option<String>>>,
346}
347
348#[derive(Debug, Clone, Serialize, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct AdminProfilePatch {
352 #[serde(default)]
354 pub name: Patch<String>,
355
356 #[serde(default)]
358 pub roles: Patch<Option<Vec<String>>>,
359 #[serde(default)]
360 pub status: Patch<crate::meta_adapter::ProfileStatus>,
361}
362
363#[skip_serializing_none]
365#[derive(Debug, Clone, Serialize, Deserialize)]
366#[serde(rename_all = "camelCase")]
367pub struct ProfileInfo {
368 pub id_tag: String,
369 pub name: String,
370 #[serde(rename = "type")]
371 pub r#type: Option<String>,
372 pub profile_pic: Option<String>, pub status: Option<crate::meta_adapter::ProfileStatus>,
374 pub connected: Option<bool>,
375 pub following: Option<bool>,
376 pub follower: Option<bool>,
377 pub trust: Option<crate::meta_adapter::ProfileTrust>,
380 pub roles: Option<Vec<String>>,
381 #[serde(
382 serialize_with = "serialize_timestamp_iso_opt",
383 skip_serializing_if = "Option::is_none"
384 )]
385 pub created_at: Option<Timestamp>,
386 pub x: Option<HashMap<String, String>>,
388}
389
390#[derive(Debug, Clone, Deserialize)]
392#[serde(rename_all = "camelCase")]
393pub struct CreateCommunityRequest {
394 #[serde(rename = "type")]
395 pub typ: String, pub name: Option<String>,
397 pub profile_pic: Option<String>,
398 pub app_domain: Option<String>, pub invite_ref: Option<String>, }
401
402#[skip_serializing_none]
404#[derive(Debug, Clone, Serialize)]
405#[serde(rename_all = "camelCase")]
406pub struct CommunityProfileResponse {
407 pub id_tag: String,
408 pub name: String,
409 #[serde(rename = "type")]
410 pub r#type: String,
411 pub profile_pic: Option<String>,
412 #[serde(serialize_with = "serialize_timestamp_iso")]
413 pub created_at: Timestamp,
414 pub onboarding: Option<String>,
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize)]
427#[serde(rename_all = "camelCase")]
428pub struct CreateActionRequest {
429 #[serde(rename = "type")]
430 pub r#type: String, pub sub_type: Option<String>, pub parent_id: Option<String>,
433 pub content: String,
435 pub attachments: Option<Vec<String>>, pub audience: Option<Vec<String>>,
437}
438
439#[skip_serializing_none]
441#[derive(Debug, Clone, Serialize, Deserialize)]
442#[serde(rename_all = "camelCase")]
443pub struct ActionResponse {
444 pub action_id: String,
445 pub action_token: String,
446 #[serde(rename = "type")]
447 pub r#type: String,
448 pub sub_type: Option<String>,
449 pub parent_id: Option<String>,
450 pub root_id: Option<String>,
451 pub content: String,
452 pub attachments: Vec<String>,
453 pub issuer_tag: String,
454 #[serde(serialize_with = "serialize_timestamp_iso")]
455 pub created_at: Timestamp,
456}
457
458#[derive(Debug, Clone, Default, Deserialize)]
460#[serde(rename_all = "camelCase")]
461pub struct ListActionsQuery {
462 #[serde(rename = "type")]
463 pub r#type: Option<String>,
464 pub parent_id: Option<String>,
465 pub offset: Option<usize>,
466 pub limit: Option<usize>,
467}
468
469#[derive(Debug, Clone, Serialize, Deserialize)]
471#[serde(rename_all = "camelCase")]
472pub struct FileUploadResponse {
473 pub file_id: String,
474 pub descriptor: String,
475 pub variants: Vec<FileVariantInfo>,
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize)]
480#[serde(rename_all = "camelCase")]
481pub struct FileVariantInfo {
482 pub variant_id: String,
483 pub format: String,
484 pub size: u64,
485 pub resolution: Option<(u32, u32)>,
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize)]
490pub struct TagInfo {
491 pub tag: String,
492 #[serde(skip_serializing_if = "Option::is_none")]
493 pub count: Option<u32>,
494}
495
496#[derive(Debug, Clone, Serialize, Deserialize)]
501#[serde(rename_all = "camelCase")]
502pub struct PaginationInfo {
503 pub offset: usize,
504 pub limit: usize,
505 pub total: usize,
506}
507
508#[derive(Debug, Clone, Serialize, Deserialize)]
513#[serde(rename_all = "camelCase")]
514pub struct CursorPaginationInfo {
515 pub next_cursor: Option<String>,
517 pub has_more: bool,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize)]
523pub struct CursorData {
524 pub s: String,
526 pub v: serde_json::Value,
528 pub id: String,
530}
531
532impl CursorData {
533 pub fn new(sort_field: &str, sort_value: serde_json::Value, item_id: &str) -> Self {
535 Self { s: sort_field.to_string(), v: sort_value, id: item_id.to_string() }
536 }
537
538 pub fn encode(&self) -> String {
540 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
541 let json = serde_json::to_string(self).unwrap_or_default();
542 URL_SAFE_NO_PAD.encode(json.as_bytes())
543 }
544
545 pub fn decode(cursor: &str) -> Option<Self> {
547 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
548 let bytes = URL_SAFE_NO_PAD.decode(cursor).ok()?;
549 let json = String::from_utf8(bytes).ok()?;
550 serde_json::from_str(&json).ok()
551 }
552
553 pub fn timestamp(&self) -> Option<i64> {
555 self.v.as_i64()
556 }
557
558 pub fn string_value(&self) -> Option<&str> {
560 self.v.as_str()
561 }
562}
563
564#[derive(Debug, Serialize, Deserialize)]
566#[serde(rename_all = "camelCase")]
567pub struct ApiResponse<T> {
568 pub data: T,
569 #[serde(skip_serializing_if = "Option::is_none")]
570 pub pagination: Option<PaginationInfo>,
571 #[serde(skip_serializing_if = "Option::is_none")]
572 pub cursor_pagination: Option<CursorPaginationInfo>,
573 #[serde(serialize_with = "serialize_timestamp_iso")]
574 pub time: Timestamp,
575 #[serde(skip_serializing_if = "Option::is_none")]
576 pub req_id: Option<String>,
577}
578
579impl<T> ApiResponse<T> {
580 pub fn new(data: T) -> Self {
582 Self {
583 data,
584 pagination: None,
585 cursor_pagination: None,
586 time: Timestamp::now(),
587 req_id: None,
588 }
589 }
590
591 pub fn with_pagination(data: T, offset: usize, limit: usize, total: usize) -> Self {
593 Self {
594 data,
595 pagination: Some(PaginationInfo { offset, limit, total }),
596 cursor_pagination: None,
597 time: Timestamp::now(),
598 req_id: None,
599 }
600 }
601
602 pub fn with_cursor_pagination(data: T, next_cursor: Option<String>, has_more: bool) -> Self {
604 Self {
605 data,
606 pagination: None,
607 cursor_pagination: Some(CursorPaginationInfo { next_cursor, has_more }),
608 time: Timestamp::now(),
609 req_id: None,
610 }
611 }
612
613 pub fn with_req_id(mut self, req_id: String) -> Self {
615 self.req_id = Some(req_id);
616 self
617 }
618}
619
620#[derive(Debug, Serialize, Deserialize)]
622#[serde(rename_all = "camelCase")]
623pub struct ErrorResponse {
624 pub error: ErrorDetails,
625}
626
627#[derive(Debug, Serialize, Deserialize)]
629#[serde(rename_all = "camelCase")]
630pub struct ErrorDetails {
631 pub code: String,
632 pub message: String,
633 #[serde(skip_serializing_if = "Option::is_none")]
634 pub details: Option<serde_json::Value>,
635}
636
637impl ErrorResponse {
638 pub fn new(code: String, message: String) -> Self {
640 Self { error: ErrorDetails { code, message, details: None } }
641 }
642
643 pub fn with_details(mut self, details: serde_json::Value) -> Self {
645 self.error.details = Some(details);
646 self
647 }
648}
649
650#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
655#[serde(rename_all = "lowercase")]
656pub enum AccessLevel {
657 None,
658 Read,
659 Comment,
660 Write,
661 Admin,
662}
663
664impl AccessLevel {
665 pub fn as_str(&self) -> &'static str {
666 match self {
667 Self::None => "none",
668 Self::Read => "read",
669 Self::Comment => "comment",
670 Self::Write => "write",
671 Self::Admin => "admin",
672 }
673 }
674
675 pub fn min(self, other: Self) -> Self {
678 match (self, other) {
679 (Self::None, _) | (_, Self::None) => Self::None,
680 (Self::Read, _) | (_, Self::Read) => Self::Read,
681 (Self::Comment, _) | (_, Self::Comment) => Self::Comment,
682 (Self::Write, _) | (_, Self::Write) => Self::Write,
683 (Self::Admin, Self::Admin) => Self::Admin,
684 }
685 }
686
687 pub fn max(self, other: Self) -> Self {
690 match (self, other) {
691 (Self::Admin, _) | (_, Self::Admin) => Self::Admin,
692 (Self::Write, _) | (_, Self::Write) => Self::Write,
693 (Self::Comment, _) | (_, Self::Comment) => Self::Comment,
694 (Self::Read, _) | (_, Self::Read) => Self::Read,
695 (Self::None, Self::None) => Self::None,
696 }
697 }
698
699 pub fn from_perm_char(c: char) -> Self {
704 match c {
705 'W' | 'A' => Self::Write,
706 'C' => Self::Comment,
707 _ => Self::Read,
708 }
709 }
710}
711
712#[derive(Debug, Clone, PartialEq, Eq)]
717pub enum TokenScope {
718 File { file_id: String, access: AccessLevel },
720 ApkgPublish,
722}
723
724impl TokenScope {
725 pub fn parse(s: &str) -> Option<Self> {
732 if s == "apkg:publish" {
733 return Some(Self::ApkgPublish);
734 }
735 let parts: Vec<&str> = s.split(':').collect();
736 if parts.len() == 3 && parts[0] == "file" {
737 let access = match parts[2] {
738 "W" => AccessLevel::Write,
739 "C" => AccessLevel::Comment,
740 _ => AccessLevel::Read, };
742 return Some(Self::File { file_id: parts[1].to_string(), access });
743 }
744 None
745 }
746
747 pub fn file_id(&self) -> Option<&str> {
749 match self {
750 Self::File { file_id, .. } => Some(file_id),
751 Self::ApkgPublish => None,
752 }
753 }
754
755 pub fn file_access(&self) -> Option<AccessLevel> {
757 match self {
758 Self::File { access, .. } => Some(*access),
759 Self::ApkgPublish => None,
760 }
761 }
762
763 pub fn matches_file(&self, target_file_id: &str) -> bool {
765 match self {
766 Self::File { file_id, .. } => file_id == target_file_id,
767 Self::ApkgPublish => false,
768 }
769 }
770}
771
772#[derive(Debug, Clone)]
774pub struct ProfileAttrs {
775 pub id_tag: Box<str>,
776 pub profile_type: Box<str>,
777 pub tenant_tag: Box<str>,
778 pub roles: Vec<Box<str>>,
779 pub status: Box<str>,
780 pub following: bool,
781 pub connected: bool,
782 pub visibility: Box<str>,
783}
784
785impl AttrSet for ProfileAttrs {
786 fn get(&self, key: &str) -> Option<&str> {
787 match key {
788 "id_tag" => Some(&self.id_tag),
789 "profile_type" => Some(&self.profile_type),
790 "tenant_tag" | "owner_id_tag" => Some(&self.tenant_tag),
791 "status" => Some(&self.status),
792 "following" => Some(if self.following { "true" } else { "false" }),
793 "connected" => Some(if self.connected { "true" } else { "false" }),
794 "visibility" => Some(&self.visibility),
795 _ => None,
796 }
797 }
798
799 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
800 match key {
801 "roles" => Some(self.roles.iter().map(AsRef::as_ref).collect()),
802 _ => None,
803 }
804 }
805}
806
807#[derive(Debug, Clone)]
809pub struct ActionAttrs {
810 pub typ: Box<str>,
811 pub sub_typ: Option<Box<str>>,
812 pub tenant_id_tag: Box<str>,
814 pub issuer_id_tag: Box<str>,
816 pub parent_id: Option<Box<str>>,
817 pub root_id: Option<Box<str>>,
818 pub audience_tag: Vec<Box<str>>,
819 pub tags: Vec<Box<str>>,
820 pub visibility: Box<str>,
821 pub following: bool,
823 pub connected: bool,
825}
826
827impl AttrSet for ActionAttrs {
828 fn get(&self, key: &str) -> Option<&str> {
829 match key {
830 "type" => Some(&self.typ),
831 "sub_type" => self.sub_typ.as_deref(),
832 "tenant_id_tag" | "owner_id_tag" => Some(&self.tenant_id_tag),
834 "issuer_id_tag" => Some(&self.issuer_id_tag),
835 "parent_id" => self.parent_id.as_deref(),
836 "root_id" => self.root_id.as_deref(),
837 "visibility" => Some(&self.visibility),
838 "following" => Some(if self.following { "true" } else { "false" }),
839 "connected" => Some(if self.connected { "true" } else { "false" }),
840 _ => None,
841 }
842 }
843
844 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
845 match key {
846 "audience_tag" => Some(self.audience_tag.iter().map(AsRef::as_ref).collect()),
847 "tags" => Some(self.tags.iter().map(AsRef::as_ref).collect()),
848 _ => None,
849 }
850 }
851}
852
853#[derive(Debug, Clone)]
855pub struct FileAttrs {
856 pub file_id: Box<str>,
857 pub owner_id_tag: Box<str>,
858 pub mime_type: Box<str>,
859 pub tags: Vec<Box<str>>,
860 pub visibility: Box<str>,
861 pub access_level: AccessLevel,
862 pub following: bool,
864 pub connected: bool,
866}
867
868impl AttrSet for FileAttrs {
869 fn get(&self, key: &str) -> Option<&str> {
870 match key {
871 "file_id" => Some(&self.file_id),
872 "owner_id_tag" => Some(&self.owner_id_tag),
873 "mime_type" => Some(&self.mime_type),
874 "visibility" => Some(&self.visibility),
875 "access_level" => Some(self.access_level.as_str()),
876 "following" => Some(if self.following { "true" } else { "false" }),
877 "connected" => Some(if self.connected { "true" } else { "false" }),
878 _ => None,
879 }
880 }
881
882 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
883 match key {
884 "tags" => Some(self.tags.iter().map(AsRef::as_ref).collect()),
885 _ => None,
886 }
887 }
888}
889
890#[derive(Debug, Clone)]
895pub struct SubjectAttrs {
896 pub id_tag: Box<str>,
897 pub roles: Vec<Box<str>>,
898 pub tier: Box<str>, pub quota_remaining_bytes: Box<str>, pub rate_limit_remaining: Box<str>, pub banned: bool,
902 pub email_verified: bool,
903}
904
905impl AttrSet for SubjectAttrs {
906 fn get(&self, key: &str) -> Option<&str> {
907 match key {
908 "id_tag" => Some(&self.id_tag),
909 "tier" => Some(&self.tier),
910 "quota_remaining" | "quota_remaining_bytes" => Some(&self.quota_remaining_bytes),
911 "rate_limit_remaining" => Some(&self.rate_limit_remaining),
912 "banned" => Some(if self.banned { "true" } else { "false" }),
913 "email_verified" => Some(if self.email_verified { "true" } else { "false" }),
914 _ => None,
915 }
916 }
917
918 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
919 match key {
920 "roles" => Some(self.roles.iter().map(AsRef::as_ref).collect()),
921 _ => None,
922 }
923 }
924}
925
926