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, Default, 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 #[serde(
389 serialize_with = "serialize_timestamp_iso_opt",
390 skip_serializing_if = "Option::is_none"
391 )]
392 pub feed_read_at: Option<Timestamp>,
393 #[serde(
396 serialize_with = "serialize_timestamp_iso_opt",
397 skip_serializing_if = "Option::is_none"
398 )]
399 pub msg_read_at: Option<Timestamp>,
400 pub hidden_in_home: Option<bool>,
403 pub x: Option<HashMap<String, String>>,
405}
406
407#[derive(Debug, Clone, Deserialize)]
409#[serde(rename_all = "camelCase")]
410pub struct CreateCommunityRequest {
411 #[serde(rename = "type")]
412 pub typ: String, pub name: Option<String>,
414 pub profile_pic: Option<String>,
415 pub app_domain: Option<String>, pub invite_ref: Option<String>, }
418
419#[skip_serializing_none]
421#[derive(Debug, Clone, Serialize)]
422#[serde(rename_all = "camelCase")]
423pub struct CommunityProfileResponse {
424 pub id_tag: String,
425 pub name: String,
426 #[serde(rename = "type")]
427 pub r#type: String,
428 pub profile_pic: Option<String>,
429 #[serde(serialize_with = "serialize_timestamp_iso")]
430 pub created_at: Timestamp,
431 pub onboarding: Option<String>,
437}
438
439#[derive(Debug, Clone, Serialize, Deserialize)]
444#[serde(rename_all = "camelCase")]
445pub struct CreateActionRequest {
446 #[serde(rename = "type")]
447 pub r#type: String, pub sub_type: Option<String>, pub parent_id: Option<String>,
450 pub content: String,
452 pub attachments: Option<Vec<String>>, pub audience: Option<Vec<String>>,
454}
455
456#[skip_serializing_none]
458#[derive(Debug, Clone, Serialize, Deserialize)]
459#[serde(rename_all = "camelCase")]
460pub struct ActionResponse {
461 pub action_id: String,
462 pub action_token: String,
463 #[serde(rename = "type")]
464 pub r#type: String,
465 pub sub_type: Option<String>,
466 pub parent_id: Option<String>,
467 pub root_id: Option<String>,
468 pub content: String,
469 pub attachments: Vec<String>,
470 pub issuer_tag: String,
471 #[serde(serialize_with = "serialize_timestamp_iso")]
472 pub created_at: Timestamp,
473}
474
475#[derive(Debug, Clone, Default, Deserialize)]
477#[serde(rename_all = "camelCase")]
478pub struct ListActionsQuery {
479 #[serde(rename = "type")]
480 pub r#type: Option<String>,
481 pub parent_id: Option<String>,
482 pub offset: Option<usize>,
483 pub limit: Option<usize>,
484}
485
486#[derive(Debug, Clone, Serialize, Deserialize)]
488#[serde(rename_all = "camelCase")]
489pub struct FileUploadResponse {
490 pub file_id: String,
491 pub descriptor: String,
492 pub variants: Vec<FileVariantInfo>,
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize)]
497#[serde(rename_all = "camelCase")]
498pub struct FileVariantInfo {
499 pub variant_id: String,
500 pub format: String,
501 pub size: u64,
502 pub resolution: Option<(u32, u32)>,
503}
504
505#[derive(Debug, Clone, Serialize, Deserialize)]
507pub struct TagInfo {
508 pub tag: String,
509 #[serde(skip_serializing_if = "Option::is_none")]
510 pub count: Option<u32>,
511}
512
513#[derive(Debug, Clone, Serialize, Deserialize)]
518#[serde(rename_all = "camelCase")]
519pub struct PaginationInfo {
520 pub offset: usize,
521 pub limit: usize,
522 pub total: usize,
523}
524
525#[derive(Debug, Clone, Serialize, Deserialize)]
530#[serde(rename_all = "camelCase")]
531pub struct CursorPaginationInfo {
532 pub next_cursor: Option<String>,
534 pub has_more: bool,
536 #[serde(skip_serializing_if = "Option::is_none")]
539 pub count: Option<i64>,
540}
541
542#[derive(Debug, Clone, Serialize, Deserialize)]
544pub struct CursorData {
545 pub s: String,
547 pub v: serde_json::Value,
549 pub id: String,
551}
552
553impl CursorData {
554 pub fn new(sort_field: &str, sort_value: serde_json::Value, item_id: &str) -> Self {
556 Self { s: sort_field.to_string(), v: sort_value, id: item_id.to_string() }
557 }
558
559 pub fn encode(&self) -> String {
561 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
562 let json = serde_json::to_string(self).unwrap_or_default();
563 URL_SAFE_NO_PAD.encode(json.as_bytes())
564 }
565
566 pub fn decode(cursor: &str) -> Option<Self> {
568 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
569 let bytes = URL_SAFE_NO_PAD.decode(cursor).ok()?;
570 let json = String::from_utf8(bytes).ok()?;
571 serde_json::from_str(&json).ok()
572 }
573
574 pub fn timestamp(&self) -> Option<i64> {
576 self.v.as_i64()
577 }
578
579 pub fn string_value(&self) -> Option<&str> {
581 self.v.as_str()
582 }
583}
584
585#[derive(Debug, Serialize, Deserialize)]
587#[serde(rename_all = "camelCase")]
588pub struct ApiResponse<T> {
589 pub data: T,
590 #[serde(skip_serializing_if = "Option::is_none")]
591 pub pagination: Option<PaginationInfo>,
592 #[serde(skip_serializing_if = "Option::is_none")]
593 pub cursor_pagination: Option<CursorPaginationInfo>,
594 #[serde(serialize_with = "serialize_timestamp_iso")]
595 pub time: Timestamp,
596 #[serde(skip_serializing_if = "Option::is_none")]
597 pub req_id: Option<String>,
598}
599
600impl<T> ApiResponse<T> {
601 pub fn new(data: T) -> Self {
603 Self {
604 data,
605 pagination: None,
606 cursor_pagination: None,
607 time: Timestamp::now(),
608 req_id: None,
609 }
610 }
611
612 pub fn with_pagination(data: T, offset: usize, limit: usize, total: usize) -> Self {
614 Self {
615 data,
616 pagination: Some(PaginationInfo { offset, limit, total }),
617 cursor_pagination: None,
618 time: Timestamp::now(),
619 req_id: None,
620 }
621 }
622
623 pub fn with_cursor_pagination(data: T, next_cursor: Option<String>, has_more: bool) -> Self {
625 Self {
626 data,
627 pagination: None,
628 cursor_pagination: Some(CursorPaginationInfo { next_cursor, has_more, count: None }),
629 time: Timestamp::now(),
630 req_id: None,
631 }
632 }
633
634 pub fn with_count(count: i64) -> Self
638 where
639 T: Default,
640 {
641 Self {
642 data: T::default(),
643 pagination: None,
644 cursor_pagination: Some(CursorPaginationInfo {
645 next_cursor: None,
646 has_more: false,
647 count: Some(count),
648 }),
649 time: Timestamp::now(),
650 req_id: None,
651 }
652 }
653
654 pub fn with_req_id(mut self, req_id: String) -> Self {
656 self.req_id = Some(req_id);
657 self
658 }
659}
660
661#[derive(Debug, Serialize, Deserialize)]
663#[serde(rename_all = "camelCase")]
664pub struct ErrorResponse {
665 pub error: ErrorDetails,
666}
667
668#[derive(Debug, Serialize, Deserialize)]
670#[serde(rename_all = "camelCase")]
671pub struct ErrorDetails {
672 pub code: String,
673 pub message: String,
674 #[serde(skip_serializing_if = "Option::is_none")]
675 pub details: Option<serde_json::Value>,
676}
677
678impl ErrorResponse {
679 pub fn new(code: String, message: String) -> Self {
681 Self { error: ErrorDetails { code, message, details: None } }
682 }
683
684 pub fn with_details(mut self, details: serde_json::Value) -> Self {
686 self.error.details = Some(details);
687 self
688 }
689}
690
691#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
696#[serde(rename_all = "lowercase")]
697pub enum AccessLevel {
698 None,
699 Read,
700 Comment,
701 Write,
702 Admin,
703}
704
705impl AccessLevel {
706 pub fn as_str(&self) -> &'static str {
707 match self {
708 Self::None => "none",
709 Self::Read => "read",
710 Self::Comment => "comment",
711 Self::Write => "write",
712 Self::Admin => "admin",
713 }
714 }
715
716 pub fn min(self, other: Self) -> Self {
719 match (self, other) {
720 (Self::None, _) | (_, Self::None) => Self::None,
721 (Self::Read, _) | (_, Self::Read) => Self::Read,
722 (Self::Comment, _) | (_, Self::Comment) => Self::Comment,
723 (Self::Write, _) | (_, Self::Write) => Self::Write,
724 (Self::Admin, Self::Admin) => Self::Admin,
725 }
726 }
727
728 pub fn max(self, other: Self) -> Self {
731 match (self, other) {
732 (Self::Admin, _) | (_, Self::Admin) => Self::Admin,
733 (Self::Write, _) | (_, Self::Write) => Self::Write,
734 (Self::Comment, _) | (_, Self::Comment) => Self::Comment,
735 (Self::Read, _) | (_, Self::Read) => Self::Read,
736 (Self::None, Self::None) => Self::None,
737 }
738 }
739
740 pub fn from_perm_char(c: char) -> Self {
745 match c {
746 'W' | 'A' => Self::Write,
747 'C' => Self::Comment,
748 _ => Self::Read,
749 }
750 }
751}
752
753#[derive(Debug, Clone, PartialEq, Eq)]
758pub enum TokenScope {
759 File { file_id: String, access: AccessLevel },
761 ApkgPublish,
763}
764
765impl TokenScope {
766 pub fn parse(s: &str) -> Option<Self> {
773 if s == "apkg:publish" {
774 return Some(Self::ApkgPublish);
775 }
776 let parts: Vec<&str> = s.split(':').collect();
777 if parts.len() == 3 && parts[0] == "file" {
778 let access = match parts[2] {
779 "W" => AccessLevel::Write,
780 "C" => AccessLevel::Comment,
781 _ => AccessLevel::Read, };
783 return Some(Self::File { file_id: parts[1].to_string(), access });
784 }
785 None
786 }
787
788 pub fn file_id(&self) -> Option<&str> {
790 match self {
791 Self::File { file_id, .. } => Some(file_id),
792 Self::ApkgPublish => None,
793 }
794 }
795
796 pub fn file_access(&self) -> Option<AccessLevel> {
798 match self {
799 Self::File { access, .. } => Some(*access),
800 Self::ApkgPublish => None,
801 }
802 }
803
804 pub fn matches_file(&self, target_file_id: &str) -> bool {
806 match self {
807 Self::File { file_id, .. } => file_id == target_file_id,
808 Self::ApkgPublish => false,
809 }
810 }
811}
812
813#[derive(Debug, Clone)]
815pub struct ProfileAttrs {
816 pub id_tag: Box<str>,
817 pub profile_type: Box<str>,
818 pub tenant_tag: Box<str>,
819 pub roles: Vec<Box<str>>,
820 pub status: Box<str>,
821 pub following: bool,
822 pub connected: bool,
823 pub visibility: Box<str>,
824}
825
826impl AttrSet for ProfileAttrs {
827 fn get(&self, key: &str) -> Option<&str> {
828 match key {
829 "id_tag" => Some(&self.id_tag),
830 "profile_type" => Some(&self.profile_type),
831 "tenant_tag" | "owner_id_tag" => Some(&self.tenant_tag),
832 "status" => Some(&self.status),
833 "following" => Some(if self.following { "true" } else { "false" }),
834 "connected" => Some(if self.connected { "true" } else { "false" }),
835 "visibility" => Some(&self.visibility),
836 _ => None,
837 }
838 }
839
840 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
841 match key {
842 "roles" => Some(self.roles.iter().map(AsRef::as_ref).collect()),
843 _ => None,
844 }
845 }
846}
847
848#[derive(Debug, Clone)]
850pub struct ActionAttrs {
851 pub typ: Box<str>,
852 pub sub_typ: Option<Box<str>>,
853 pub tenant_id_tag: Box<str>,
855 pub issuer_id_tag: Box<str>,
857 pub parent_id: Option<Box<str>>,
858 pub root_id: Option<Box<str>>,
859 pub audience_tag: Vec<Box<str>>,
860 pub tags: Vec<Box<str>>,
861 pub visibility: Box<str>,
862 pub following: bool,
864 pub connected: bool,
866}
867
868impl AttrSet for ActionAttrs {
869 fn get(&self, key: &str) -> Option<&str> {
870 match key {
871 "type" => Some(&self.typ),
872 "sub_type" => self.sub_typ.as_deref(),
873 "tenant_id_tag" | "owner_id_tag" => Some(&self.tenant_id_tag),
875 "issuer_id_tag" => Some(&self.issuer_id_tag),
876 "parent_id" => self.parent_id.as_deref(),
877 "root_id" => self.root_id.as_deref(),
878 "visibility" => Some(&self.visibility),
879 "following" => Some(if self.following { "true" } else { "false" }),
880 "connected" => Some(if self.connected { "true" } else { "false" }),
881 _ => None,
882 }
883 }
884
885 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
886 match key {
887 "audience_tag" => Some(self.audience_tag.iter().map(AsRef::as_ref).collect()),
888 "tags" => Some(self.tags.iter().map(AsRef::as_ref).collect()),
889 _ => None,
890 }
891 }
892}
893
894#[derive(Debug, Clone)]
896pub struct FileAttrs {
897 pub file_id: Box<str>,
898 pub owner_id_tag: Box<str>,
899 pub mime_type: Box<str>,
900 pub tags: Vec<Box<str>>,
901 pub visibility: Box<str>,
902 pub access_level: AccessLevel,
903 pub following: bool,
905 pub connected: bool,
907}
908
909impl AttrSet for FileAttrs {
910 fn get(&self, key: &str) -> Option<&str> {
911 match key {
912 "file_id" => Some(&self.file_id),
913 "owner_id_tag" => Some(&self.owner_id_tag),
914 "mime_type" => Some(&self.mime_type),
915 "visibility" => Some(&self.visibility),
916 "access_level" => Some(self.access_level.as_str()),
917 "following" => Some(if self.following { "true" } else { "false" }),
918 "connected" => Some(if self.connected { "true" } else { "false" }),
919 _ => None,
920 }
921 }
922
923 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
924 match key {
925 "tags" => Some(self.tags.iter().map(AsRef::as_ref).collect()),
926 _ => None,
927 }
928 }
929}
930
931#[derive(Debug, Clone)]
936pub struct SubjectAttrs {
937 pub id_tag: Box<str>,
938 pub roles: Vec<Box<str>>,
939 pub tier: Box<str>, pub quota_remaining_bytes: Box<str>, pub rate_limit_remaining: Box<str>, pub banned: bool,
943 pub email_verified: bool,
944}
945
946impl AttrSet for SubjectAttrs {
947 fn get(&self, key: &str) -> Option<&str> {
948 match key {
949 "id_tag" => Some(&self.id_tag),
950 "tier" => Some(&self.tier),
951 "quota_remaining" | "quota_remaining_bytes" => Some(&self.quota_remaining_bytes),
952 "rate_limit_remaining" => Some(&self.rate_limit_remaining),
953 "banned" => Some(if self.banned { "true" } else { "false" }),
954 "email_verified" => Some(if self.email_verified { "true" } else { "false" }),
955 _ => None,
956 }
957 }
958
959 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
960 match key {
961 "roles" => Some(self.roles.iter().map(AsRef::as_ref).collect()),
962 _ => None,
963 }
964 }
965}
966
967