Skip to main content

polyester/codecs/decode/
auth.rs

1//! Auth response decoders.
2
3use crate::codecs::scalars::{format_id, format_uint64_id};
4use crate::models::{
5    AccountIdentity, MeResult, UserProfile, UsernameHistoryEntry, UsernameHistoryList,
6};
7use crate::proto::auth::v1::{
8    AccountIdentity as ProtoAccountIdentity, GetUsernameHistoryResponse, MeResponse,
9    UserProfile as ProtoUserProfile,
10};
11
12fn timestamp_ms(ts: Option<&buffa_types::google::protobuf::Timestamp>) -> Option<i64> {
13    ts.map(|t| t.seconds.saturating_mul(1000) + (t.nanos as i64) / 1_000_000)
14}
15
16pub fn me_from_proto(msg: &MeResponse) -> MeResult {
17    MeResult {
18        account_id: format_uint64_id(msg.account_id),
19        api_key_id: msg.api_key_id.map(format_uint64_id),
20        username: if msg.username.is_empty() {
21            None
22        } else {
23            Some(msg.username.clone())
24        },
25        root_smart_account_address: if msg.root_smart_account_address.is_empty() {
26            None
27        } else {
28            Some(msg.root_smart_account_address.clone())
29        },
30    }
31}
32
33pub fn profile_from_proto(msg: &ProtoUserProfile) -> UserProfile {
34    UserProfile {
35        username: msg.username.clone(),
36        bio: msg.bio.clone(),
37        website: msg.website.clone(),
38        twitter: msg.twitter.clone(),
39        twitter_verified: msg.twitter_verified,
40        discord: msg.discord.clone(),
41        discord_verified: msg.discord_verified,
42        avatar_url: msg.avatar_url.clone(),
43        created_at_ms: timestamp_ms(msg.created_at.as_option()),
44        next_username_change_at_ms: timestamp_ms(msg.next_username_change_at.as_option()),
45        vip_tier: msg.vip_tier,
46        username_unlocked: msg.username_unlocked,
47    }
48}
49
50pub fn username_history_from_proto(msg: &GetUsernameHistoryResponse) -> UsernameHistoryList {
51    UsernameHistoryList {
52        entries: msg
53            .history
54            .iter()
55            .map(|e| UsernameHistoryEntry {
56                username: e.username.clone(),
57                changed_at_ms: timestamp_ms(e.set_at.as_option()),
58            })
59            .collect(),
60    }
61}
62
63pub fn account_identity_from_proto(msg: &ProtoAccountIdentity) -> AccountIdentity {
64    AccountIdentity {
65        account_id: format_id(msg.account_id),
66        username: msg.username.clone(),
67        avatar_url: msg.avatar_url.clone(),
68        root_smart_account_address: msg.root_smart_account_address.clone(),
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use crate::codecs::scalars::format_uint64_id;
76    use crate::proto::auth::v1::MeResponse;
77
78    #[test]
79    fn me_from_proto_formats_ids() {
80        let msg = MeResponse {
81            account_id: 42,
82            api_key_id: Some(99),
83            username: "alice".into(),
84            root_smart_account_address: "0xabc".into(),
85            ..Default::default()
86        };
87        let me = me_from_proto(&msg);
88        assert_eq!(me.account_id, format_uint64_id(42));
89        assert_eq!(
90            me.api_key_id.as_deref(),
91            Some(format_uint64_id(99).as_str())
92        );
93        assert_eq!(me.username.as_deref(), Some("alice"));
94        assert_eq!(me.root_smart_account_address.as_deref(), Some("0xabc"));
95    }
96
97    #[test]
98    fn profile_from_proto_maps_fields() {
99        let msg = ProtoUserProfile {
100            username: "alice".into(),
101            bio: "hi".into(),
102            twitter_verified: true,
103            vip_tier: 2,
104            username_unlocked: true,
105            ..Default::default()
106        };
107        let profile = profile_from_proto(&msg);
108        assert_eq!(profile.username, "alice");
109        assert_eq!(profile.bio, "hi");
110        assert!(profile.twitter_verified);
111        assert_eq!(profile.vip_tier, 2);
112        assert!(profile.username_unlocked);
113    }
114}