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)]
331#[serde(rename_all = "camelCase")]
332pub struct ProfilePatch {
333 #[serde(default)]
334 pub name: Patch<String>,
335 #[serde(default)]
337 pub x: Option<HashMap<String, Option<String>>>,
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize)]
342#[serde(rename_all = "camelCase")]
343pub struct AdminProfilePatch {
344 #[serde(default)]
346 pub name: Patch<String>,
347
348 #[serde(default)]
350 pub roles: Patch<Option<Vec<String>>>,
351 #[serde(default)]
352 pub status: Patch<crate::meta_adapter::ProfileStatus>,
353}
354
355#[skip_serializing_none]
357#[derive(Debug, Clone, Serialize, Deserialize)]
358#[serde(rename_all = "camelCase")]
359pub struct ProfileInfo {
360 pub id_tag: String,
361 pub name: String,
362 #[serde(rename = "type")]
363 pub r#type: Option<String>,
364 pub profile_pic: Option<String>, pub status: Option<crate::meta_adapter::ProfileStatus>,
366 pub connected: Option<bool>,
367 pub following: Option<bool>,
368 pub trust: Option<crate::meta_adapter::ProfileTrust>,
371 pub roles: Option<Vec<String>>,
372 #[serde(
373 serialize_with = "serialize_timestamp_iso_opt",
374 skip_serializing_if = "Option::is_none"
375 )]
376 pub created_at: Option<Timestamp>,
377 pub x: Option<HashMap<String, String>>,
379}
380
381#[derive(Debug, Clone, Deserialize)]
383#[serde(rename_all = "camelCase")]
384pub struct CreateCommunityRequest {
385 #[serde(rename = "type")]
386 pub typ: String, pub name: Option<String>,
388 pub profile_pic: Option<String>,
389 pub app_domain: Option<String>, pub invite_ref: Option<String>, }
392
393#[skip_serializing_none]
395#[derive(Debug, Clone, Serialize)]
396#[serde(rename_all = "camelCase")]
397pub struct CommunityProfileResponse {
398 pub id_tag: String,
399 pub name: String,
400 #[serde(rename = "type")]
401 pub r#type: String,
402 pub profile_pic: Option<String>,
403 #[serde(serialize_with = "serialize_timestamp_iso")]
404 pub created_at: Timestamp,
405 pub onboarding: Option<String>,
411}
412
413#[derive(Debug, Clone, Serialize, Deserialize)]
418#[serde(rename_all = "camelCase")]
419pub struct CreateActionRequest {
420 #[serde(rename = "type")]
421 pub r#type: String, pub sub_type: Option<String>, pub parent_id: Option<String>,
424 pub content: String,
426 pub attachments: Option<Vec<String>>, pub audience: Option<Vec<String>>,
428}
429
430#[skip_serializing_none]
432#[derive(Debug, Clone, Serialize, Deserialize)]
433#[serde(rename_all = "camelCase")]
434pub struct ActionResponse {
435 pub action_id: String,
436 pub action_token: String,
437 #[serde(rename = "type")]
438 pub r#type: String,
439 pub sub_type: Option<String>,
440 pub parent_id: Option<String>,
441 pub root_id: Option<String>,
442 pub content: String,
443 pub attachments: Vec<String>,
444 pub issuer_tag: String,
445 #[serde(serialize_with = "serialize_timestamp_iso")]
446 pub created_at: Timestamp,
447}
448
449#[derive(Debug, Clone, Default, Deserialize)]
451#[serde(rename_all = "camelCase")]
452pub struct ListActionsQuery {
453 #[serde(rename = "type")]
454 pub r#type: Option<String>,
455 pub parent_id: Option<String>,
456 pub offset: Option<usize>,
457 pub limit: Option<usize>,
458}
459
460#[derive(Debug, Clone, Serialize, Deserialize)]
462#[serde(rename_all = "camelCase")]
463pub struct FileUploadResponse {
464 pub file_id: String,
465 pub descriptor: String,
466 pub variants: Vec<FileVariantInfo>,
467}
468
469#[derive(Debug, Clone, Serialize, Deserialize)]
471#[serde(rename_all = "camelCase")]
472pub struct FileVariantInfo {
473 pub variant_id: String,
474 pub format: String,
475 pub size: u64,
476 pub resolution: Option<(u32, u32)>,
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct TagInfo {
482 pub tag: String,
483 #[serde(skip_serializing_if = "Option::is_none")]
484 pub count: Option<u32>,
485}
486
487#[derive(Debug, Clone, Serialize, Deserialize)]
492#[serde(rename_all = "camelCase")]
493pub struct PaginationInfo {
494 pub offset: usize,
495 pub limit: usize,
496 pub total: usize,
497}
498
499#[derive(Debug, Clone, Serialize, Deserialize)]
504#[serde(rename_all = "camelCase")]
505pub struct CursorPaginationInfo {
506 pub next_cursor: Option<String>,
508 pub has_more: bool,
510}
511
512#[derive(Debug, Clone, Serialize, Deserialize)]
514pub struct CursorData {
515 pub s: String,
517 pub v: serde_json::Value,
519 pub id: String,
521}
522
523impl CursorData {
524 pub fn new(sort_field: &str, sort_value: serde_json::Value, item_id: &str) -> Self {
526 Self { s: sort_field.to_string(), v: sort_value, id: item_id.to_string() }
527 }
528
529 pub fn encode(&self) -> String {
531 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
532 let json = serde_json::to_string(self).unwrap_or_default();
533 URL_SAFE_NO_PAD.encode(json.as_bytes())
534 }
535
536 pub fn decode(cursor: &str) -> Option<Self> {
538 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
539 let bytes = URL_SAFE_NO_PAD.decode(cursor).ok()?;
540 let json = String::from_utf8(bytes).ok()?;
541 serde_json::from_str(&json).ok()
542 }
543
544 pub fn timestamp(&self) -> Option<i64> {
546 self.v.as_i64()
547 }
548
549 pub fn string_value(&self) -> Option<&str> {
551 self.v.as_str()
552 }
553}
554
555#[derive(Debug, Serialize, Deserialize)]
557#[serde(rename_all = "camelCase")]
558pub struct ApiResponse<T> {
559 pub data: T,
560 #[serde(skip_serializing_if = "Option::is_none")]
561 pub pagination: Option<PaginationInfo>,
562 #[serde(skip_serializing_if = "Option::is_none")]
563 pub cursor_pagination: Option<CursorPaginationInfo>,
564 #[serde(serialize_with = "serialize_timestamp_iso")]
565 pub time: Timestamp,
566 #[serde(skip_serializing_if = "Option::is_none")]
567 pub req_id: Option<String>,
568}
569
570impl<T> ApiResponse<T> {
571 pub fn new(data: T) -> Self {
573 Self {
574 data,
575 pagination: None,
576 cursor_pagination: None,
577 time: Timestamp::now(),
578 req_id: None,
579 }
580 }
581
582 pub fn with_pagination(data: T, offset: usize, limit: usize, total: usize) -> Self {
584 Self {
585 data,
586 pagination: Some(PaginationInfo { offset, limit, total }),
587 cursor_pagination: None,
588 time: Timestamp::now(),
589 req_id: None,
590 }
591 }
592
593 pub fn with_cursor_pagination(data: T, next_cursor: Option<String>, has_more: bool) -> Self {
595 Self {
596 data,
597 pagination: None,
598 cursor_pagination: Some(CursorPaginationInfo { next_cursor, has_more }),
599 time: Timestamp::now(),
600 req_id: None,
601 }
602 }
603
604 pub fn with_req_id(mut self, req_id: String) -> Self {
606 self.req_id = Some(req_id);
607 self
608 }
609}
610
611#[derive(Debug, Serialize, Deserialize)]
613#[serde(rename_all = "camelCase")]
614pub struct ErrorResponse {
615 pub error: ErrorDetails,
616}
617
618#[derive(Debug, Serialize, Deserialize)]
620#[serde(rename_all = "camelCase")]
621pub struct ErrorDetails {
622 pub code: String,
623 pub message: String,
624 #[serde(skip_serializing_if = "Option::is_none")]
625 pub details: Option<serde_json::Value>,
626}
627
628impl ErrorResponse {
629 pub fn new(code: String, message: String) -> Self {
631 Self { error: ErrorDetails { code, message, details: None } }
632 }
633
634 pub fn with_details(mut self, details: serde_json::Value) -> Self {
636 self.error.details = Some(details);
637 self
638 }
639}
640
641#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
646#[serde(rename_all = "lowercase")]
647pub enum AccessLevel {
648 None,
649 Read,
650 Comment,
651 Write,
652 Admin,
653}
654
655impl AccessLevel {
656 pub fn as_str(&self) -> &'static str {
657 match self {
658 Self::None => "none",
659 Self::Read => "read",
660 Self::Comment => "comment",
661 Self::Write => "write",
662 Self::Admin => "admin",
663 }
664 }
665
666 pub fn min(self, other: Self) -> Self {
669 match (self, other) {
670 (Self::None, _) | (_, Self::None) => Self::None,
671 (Self::Read, _) | (_, Self::Read) => Self::Read,
672 (Self::Comment, _) | (_, Self::Comment) => Self::Comment,
673 (Self::Write, _) | (_, Self::Write) => Self::Write,
674 (Self::Admin, Self::Admin) => Self::Admin,
675 }
676 }
677
678 pub fn max(self, other: Self) -> Self {
681 match (self, other) {
682 (Self::Admin, _) | (_, Self::Admin) => Self::Admin,
683 (Self::Write, _) | (_, Self::Write) => Self::Write,
684 (Self::Comment, _) | (_, Self::Comment) => Self::Comment,
685 (Self::Read, _) | (_, Self::Read) => Self::Read,
686 (Self::None, Self::None) => Self::None,
687 }
688 }
689
690 pub fn from_perm_char(c: char) -> Self {
695 match c {
696 'W' | 'A' => Self::Write,
697 'C' => Self::Comment,
698 _ => Self::Read,
699 }
700 }
701}
702
703#[derive(Debug, Clone, PartialEq, Eq)]
708pub enum TokenScope {
709 File { file_id: String, access: AccessLevel },
711 ApkgPublish,
713}
714
715impl TokenScope {
716 pub fn parse(s: &str) -> Option<Self> {
723 if s == "apkg:publish" {
724 return Some(Self::ApkgPublish);
725 }
726 let parts: Vec<&str> = s.split(':').collect();
727 if parts.len() == 3 && parts[0] == "file" {
728 let access = match parts[2] {
729 "W" => AccessLevel::Write,
730 "C" => AccessLevel::Comment,
731 _ => AccessLevel::Read, };
733 return Some(Self::File { file_id: parts[1].to_string(), access });
734 }
735 None
736 }
737
738 pub fn file_id(&self) -> Option<&str> {
740 match self {
741 Self::File { file_id, .. } => Some(file_id),
742 Self::ApkgPublish => None,
743 }
744 }
745
746 pub fn file_access(&self) -> Option<AccessLevel> {
748 match self {
749 Self::File { access, .. } => Some(*access),
750 Self::ApkgPublish => None,
751 }
752 }
753
754 pub fn matches_file(&self, target_file_id: &str) -> bool {
756 match self {
757 Self::File { file_id, .. } => file_id == target_file_id,
758 Self::ApkgPublish => false,
759 }
760 }
761}
762
763#[derive(Debug, Clone)]
765pub struct ProfileAttrs {
766 pub id_tag: Box<str>,
767 pub profile_type: Box<str>,
768 pub tenant_tag: Box<str>,
769 pub roles: Vec<Box<str>>,
770 pub status: Box<str>,
771 pub following: bool,
772 pub connected: bool,
773 pub visibility: Box<str>,
774}
775
776impl AttrSet for ProfileAttrs {
777 fn get(&self, key: &str) -> Option<&str> {
778 match key {
779 "id_tag" => Some(&self.id_tag),
780 "profile_type" => Some(&self.profile_type),
781 "tenant_tag" | "owner_id_tag" => Some(&self.tenant_tag),
782 "status" => Some(&self.status),
783 "following" => Some(if self.following { "true" } else { "false" }),
784 "connected" => Some(if self.connected { "true" } else { "false" }),
785 "visibility" => Some(&self.visibility),
786 _ => None,
787 }
788 }
789
790 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
791 match key {
792 "roles" => Some(self.roles.iter().map(AsRef::as_ref).collect()),
793 _ => None,
794 }
795 }
796}
797
798#[derive(Debug, Clone)]
800pub struct ActionAttrs {
801 pub typ: Box<str>,
802 pub sub_typ: Option<Box<str>>,
803 pub tenant_id_tag: Box<str>,
805 pub issuer_id_tag: Box<str>,
807 pub parent_id: Option<Box<str>>,
808 pub root_id: Option<Box<str>>,
809 pub audience_tag: Vec<Box<str>>,
810 pub tags: Vec<Box<str>>,
811 pub visibility: Box<str>,
812 pub following: bool,
814 pub connected: bool,
816}
817
818impl AttrSet for ActionAttrs {
819 fn get(&self, key: &str) -> Option<&str> {
820 match key {
821 "type" => Some(&self.typ),
822 "sub_type" => self.sub_typ.as_deref(),
823 "tenant_id_tag" | "owner_id_tag" => Some(&self.tenant_id_tag),
825 "issuer_id_tag" => Some(&self.issuer_id_tag),
826 "parent_id" => self.parent_id.as_deref(),
827 "root_id" => self.root_id.as_deref(),
828 "visibility" => Some(&self.visibility),
829 "following" => Some(if self.following { "true" } else { "false" }),
830 "connected" => Some(if self.connected { "true" } else { "false" }),
831 _ => None,
832 }
833 }
834
835 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
836 match key {
837 "audience_tag" => Some(self.audience_tag.iter().map(AsRef::as_ref).collect()),
838 "tags" => Some(self.tags.iter().map(AsRef::as_ref).collect()),
839 _ => None,
840 }
841 }
842}
843
844#[derive(Debug, Clone)]
846pub struct FileAttrs {
847 pub file_id: Box<str>,
848 pub owner_id_tag: Box<str>,
849 pub mime_type: Box<str>,
850 pub tags: Vec<Box<str>>,
851 pub visibility: Box<str>,
852 pub access_level: AccessLevel,
853 pub following: bool,
855 pub connected: bool,
857}
858
859impl AttrSet for FileAttrs {
860 fn get(&self, key: &str) -> Option<&str> {
861 match key {
862 "file_id" => Some(&self.file_id),
863 "owner_id_tag" => Some(&self.owner_id_tag),
864 "mime_type" => Some(&self.mime_type),
865 "visibility" => Some(&self.visibility),
866 "access_level" => Some(self.access_level.as_str()),
867 "following" => Some(if self.following { "true" } else { "false" }),
868 "connected" => Some(if self.connected { "true" } else { "false" }),
869 _ => None,
870 }
871 }
872
873 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
874 match key {
875 "tags" => Some(self.tags.iter().map(AsRef::as_ref).collect()),
876 _ => None,
877 }
878 }
879}
880
881#[derive(Debug, Clone)]
886pub struct SubjectAttrs {
887 pub id_tag: Box<str>,
888 pub roles: Vec<Box<str>>,
889 pub tier: Box<str>, pub quota_remaining_bytes: Box<str>, pub rate_limit_remaining: Box<str>, pub banned: bool,
893 pub email_verified: bool,
894}
895
896impl AttrSet for SubjectAttrs {
897 fn get(&self, key: &str) -> Option<&str> {
898 match key {
899 "id_tag" => Some(&self.id_tag),
900 "tier" => Some(&self.tier),
901 "quota_remaining" | "quota_remaining_bytes" => Some(&self.quota_remaining_bytes),
902 "rate_limit_remaining" => Some(&self.rate_limit_remaining),
903 "banned" => Some(if self.banned { "true" } else { "false" }),
904 "email_verified" => Some(if self.email_verified { "true" } else { "false" }),
905 _ => None,
906 }
907 }
908
909 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
910 match key {
911 "roles" => Some(self.roles.iter().map(AsRef::as_ref).collect()),
912 _ => None,
913 }
914 }
915}
916
917