1use crate::codecs::decode::enums::enum_proto_name;
4use crate::codecs::scalars::format_uint64_id;
5use crate::models::{
6 CreateSubaccountResult, GetSubaccountResult, SubAccount, SubAccountActivityEvent,
7 SubAccountActivityList, SubAccountInvite, SubAccountInvitesList, SubAccountMember,
8 SubAccountMembersList, SubAccountsList,
9};
10use crate::proto::auth::v1::{
11 CreateSubaccountResponse, GetSubaccountResponse, InviteSubaccountMemberResponse,
12 ListSubaccountEventsResponse, ListSubaccountInvitesResponse, ListSubaccountMembersResponse,
13 ListSubaccountsResponse, RespondSubaccountInviteResponse, Subaccount, SubaccountInvite,
14 SubaccountMemberView,
15};
16
17fn timestamp_ms(ts: Option<&buffa_types::google::protobuf::Timestamp>) -> i64 {
18 match ts {
19 Some(t) => t.seconds.saturating_mul(1000) + (t.nanos as i64) / 1_000_000,
20 None => 0,
21 }
22}
23
24fn activity_label<T: buffa::Enumeration>(value: &buffa::EnumValue<T>, prefix: &str) -> String {
25 let label = enum_proto_name(value);
26 match label.strip_prefix(prefix) {
27 Some(known) => known.to_ascii_lowercase(),
28 None => label,
29 }
30}
31
32pub fn subaccount_from_proto(msg: &Subaccount) -> SubAccount {
33 SubAccount {
34 subaccount_id: format_uint64_id(msg.id),
35 label: msg.label.clone(),
36 smart_account_address: msg.smart_account_address.clone(),
37 status: msg.status.clone(),
38 updated_at: msg.updated_at.as_option().cloned(),
39 revision: msg.revision,
40 }
41}
42
43pub fn subaccounts_list_from_proto(msg: &ListSubaccountsResponse) -> SubAccountsList {
44 SubAccountsList {
45 subaccounts: msg.subaccounts.iter().map(subaccount_from_proto).collect(),
46 }
47}
48
49pub fn get_subaccount_from_proto(msg: &GetSubaccountResponse) -> GetSubaccountResult {
50 GetSubaccountResult {
51 subaccount: msg.subaccount.as_option().map(subaccount_from_proto),
52 }
53}
54
55pub fn create_subaccount_from_proto(msg: &CreateSubaccountResponse) -> CreateSubaccountResult {
56 CreateSubaccountResult {
57 subaccount_id: format_uint64_id(msg.subaccount_id),
58 revision: msg.revision,
59 }
60}
61
62fn member_from_proto(msg: &SubaccountMemberView) -> SubAccountMember {
63 SubAccountMember {
64 grantee_account_id: format_uint64_id(msg.account_id),
65 role: enum_proto_name(&msg.role),
66 }
67}
68
69pub fn subaccount_members_list_from_proto(
70 msg: &ListSubaccountMembersResponse,
71) -> SubAccountMembersList {
72 SubAccountMembersList {
73 members: msg.members.iter().map(member_from_proto).collect(),
74 }
75}
76
77fn invite_from_proto(msg: &SubaccountInvite) -> SubAccountInvite {
78 SubAccountInvite {
79 invite_id: format_uint64_id(msg.id),
80 grantee_account_id: format_uint64_id(msg.grantee_account_id),
81 role: enum_proto_name(&msg.role),
82 status: enum_proto_name(&msg.status),
83 }
84}
85
86pub fn invite_subaccount_member_from_proto(
87 msg: &InviteSubaccountMemberResponse,
88) -> Option<SubAccountInvite> {
89 msg.invite.as_option().map(invite_from_proto)
90}
91
92pub fn respond_subaccount_invite_from_proto(
93 msg: &RespondSubaccountInviteResponse,
94) -> Option<SubAccountInvite> {
95 msg.invite.as_option().map(invite_from_proto)
96}
97
98pub fn subaccount_invites_list_from_proto(
99 msg: &ListSubaccountInvitesResponse,
100) -> SubAccountInvitesList {
101 SubAccountInvitesList {
102 invites: msg.invites.iter().map(invite_from_proto).collect(),
103 }
104}
105
106pub fn subaccount_activity_list_from_proto(
107 msg: &ListSubaccountEventsResponse,
108) -> SubAccountActivityList {
109 SubAccountActivityList {
110 events: msg
111 .events
112 .iter()
113 .map(|e| SubAccountActivityEvent {
114 event_type: format!(
115 "{}:{}",
116 activity_label(&e.entity_kind, "ACTIVITY_ENTITY_"),
117 activity_label(&e.event_action, "ACTIVITY_ACTION_")
118 ),
119 ts_ms: timestamp_ms(e.created_at.as_option()),
120 })
121 .collect(),
122 next_page_token: msg.next_page_token.clone(),
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use crate::proto::auth::v1::{
130 ActivityEntityKind, ActivityEvent, ActivityEventAction, SubaccountRole,
131 };
132 use buffa_types::google::protobuf::Timestamp;
133
134 #[test]
135 fn subaccounts_list_maps_fields() {
136 let msg = ListSubaccountsResponse {
137 subaccounts: vec![Subaccount {
138 id: 12,
139 label: "trading".into(),
140 smart_account_address: "0xabc".into(),
141 status: "active".into(),
142 updated_at: Timestamp {
143 seconds: 3,
144 nanos: 123_456_000,
145 ..Default::default()
146 }
147 .into(),
148 revision: 4,
149 ..Default::default()
150 }],
151 ..Default::default()
152 };
153 let result = subaccounts_list_from_proto(&msg);
154 assert_eq!(result.subaccounts.len(), 1);
155 assert_eq!(result.subaccounts[0].subaccount_id, format_uint64_id(12));
156 assert_eq!(result.subaccounts[0].smart_account_address, "0xabc");
157 assert_eq!(
158 result.subaccounts[0].updated_at.as_ref().unwrap().nanos,
159 123_456_000
160 );
161 assert_eq!(result.subaccounts[0].revision, 4);
162 }
163
164 #[test]
165 fn members_list_maps_grantee_and_role() {
166 let msg = ListSubaccountMembersResponse {
167 members: vec![SubaccountMemberView {
168 account_id: 99,
169 role: SubaccountRole::TRADER.into(),
170 ..Default::default()
171 }],
172 ..Default::default()
173 };
174 let result = subaccount_members_list_from_proto(&msg);
175 assert_eq!(result.members.len(), 1);
176 assert_eq!(result.members[0].grantee_account_id, format_uint64_id(99));
177 assert!(!result.members[0].role.is_empty());
178 }
179
180 #[test]
181 fn activity_list_joins_entity_and_action() {
182 let msg = ListSubaccountEventsResponse {
183 events: vec![ActivityEvent {
184 entity_kind: ActivityEntityKind::ACTIVITY_ENTITY_INVITE.into(),
185 event_action: ActivityEventAction::ACTIVITY_ACTION_CREATED.into(),
186 ..Default::default()
187 }],
188 next_page_token: "n".into(),
189 ..Default::default()
190 };
191 let result = subaccount_activity_list_from_proto(&msg);
192 assert_eq!(result.events[0].event_type, "invite:created");
193 assert_eq!(result.next_page_token, "n");
194 }
195
196 #[test]
197 fn create_subaccount_maps_revision() {
198 let result = create_subaccount_from_proto(&CreateSubaccountResponse {
199 subaccount_id: 42,
200 revision: 1,
201 ..Default::default()
202 });
203 assert_eq!(result.subaccount_id, format_uint64_id(42));
204 assert_eq!(result.revision, 1);
205 }
206}