Skip to main content

polyester/codecs/decode/
admin.rs

1//! API key and resolve-account decoders.
2
3use crate::codecs::decode::enums::enum_proto_name;
4use crate::codecs::scalars::format_uint64_id;
5use crate::models::{ApiKeySummary, ApiKeysList, ResolvedAccount, ResolvedAccountsList};
6use crate::proto::auth::v1::{
7    ApiKey as ProtoApiKey, CreateApiKeyResponse, GetApiKeyResponse, ListApiKeysResponse,
8    ResolveAccountResponse, ResolvedAccount as ProtoResolvedAccount, UpdateApiKeyResponse,
9};
10
11pub fn api_key_from_proto(msg: &ProtoApiKey) -> ApiKeySummary {
12    let status = enum_proto_name(&msg.status);
13    ApiKeySummary {
14        key_id: msg.key_id.clone(),
15        label: msg.label.clone(),
16        status,
17        public_key_ed25519: hex::encode(&msg.public_key_ed25519),
18        created_at: msg.created_at.as_option().cloned(),
19        last_used_at: msg.last_used_at.as_option().cloned(),
20        updated_at: msg.updated_at.as_option().cloned(),
21        revision: msg.revision,
22    }
23}
24
25pub fn api_keys_list_from_proto(msg: &ListApiKeysResponse) -> ApiKeysList {
26    ApiKeysList {
27        keys: msg.api_keys.iter().map(api_key_from_proto).collect(),
28    }
29}
30
31pub fn api_key_from_get_proto(msg: &GetApiKeyResponse) -> Option<ApiKeySummary> {
32    msg.api_key.as_option().map(api_key_from_proto)
33}
34
35pub fn api_key_from_create_proto(msg: &CreateApiKeyResponse) -> Option<ApiKeySummary> {
36    msg.api_key.as_option().map(api_key_from_proto)
37}
38
39pub fn api_key_from_update_proto(msg: &UpdateApiKeyResponse) -> Option<ApiKeySummary> {
40    msg.api_key.as_option().map(api_key_from_proto)
41}
42
43pub fn resolved_account_from_proto(msg: &ProtoResolvedAccount) -> ResolvedAccount {
44    let username = if !msg.subaccount_label.is_empty() {
45        msg.subaccount_label.clone()
46    } else {
47        msg.root_username.clone()
48    };
49    ResolvedAccount {
50        account_id: format_uint64_id(msg.account_id),
51        username,
52        smart_account_address: msg.smart_account_address.clone(),
53    }
54}
55
56pub fn resolved_accounts_from_proto(msg: &ResolveAccountResponse) -> ResolvedAccountsList {
57    ResolvedAccountsList {
58        accounts: msg
59            .matches
60            .iter()
61            .map(resolved_account_from_proto)
62            .collect(),
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use crate::proto::auth::v1::ApiKeyStatus;
70    use buffa_types::google::protobuf::Timestamp;
71
72    #[test]
73    fn api_keys_list_maps_status() {
74        let msg = ListApiKeysResponse {
75            api_keys: vec![ProtoApiKey {
76                key_id: "key-1".into(),
77                label: "bot".into(),
78                status: ApiKeyStatus::Active.into(),
79                created_at: Timestamp {
80                    seconds: 1,
81                    ..Default::default()
82                }
83                .into(),
84                last_used_at: Timestamp {
85                    seconds: 2,
86                    ..Default::default()
87                }
88                .into(),
89                updated_at: Timestamp {
90                    seconds: 3,
91                    nanos: 123_456_000,
92                    ..Default::default()
93                }
94                .into(),
95                revision: 9,
96                ..Default::default()
97            }],
98            ..Default::default()
99        };
100        let result = api_keys_list_from_proto(&msg);
101        assert_eq!(result.keys.len(), 1);
102        assert_eq!(result.keys[0].key_id, "key-1");
103        assert_eq!(result.keys[0].status, "ACTIVE");
104        assert_eq!(result.keys[0].created_at.as_ref().unwrap().seconds, 1);
105        assert_eq!(result.keys[0].last_used_at.as_ref().unwrap().seconds, 2);
106        assert_eq!(
107            result.keys[0].updated_at.as_ref().unwrap().nanos,
108            123_456_000
109        );
110        assert_eq!(result.keys[0].revision, 9);
111    }
112
113    #[test]
114    fn resolved_accounts_formats_id() {
115        let msg = ResolveAccountResponse {
116            matches: vec![ProtoResolvedAccount {
117                smart_account_address: "0x123".into(),
118                kind: "subaccount".into(),
119                account_id: 99,
120                ..Default::default()
121            }],
122            ..Default::default()
123        };
124        let result = resolved_accounts_from_proto(&msg);
125        assert_eq!(result.accounts.len(), 1);
126        assert_eq!(result.accounts[0].account_id, format_uint64_id(99));
127        assert_eq!(result.accounts[0].smart_account_address, "0x123");
128    }
129}