polyester/codecs/decode/
lifecycle.rs1use crate::codecs::decode::enums::enum_proto_name;
4use crate::codecs::scalars::format_uint64_id;
5use crate::errors::{Error, Result};
6use crate::models::{LifecycleFlowSummary, LifecycleFlowsList};
7use crate::proto::chain::lifecycle::v1::{
8 FlowSummaryView, FlowTxMatchView, GetFlowResponse, ListFlowsByTxResponse, ListFlowsResponse,
9};
10
11fn flow_summary_from_proto(msg: &FlowSummaryView) -> LifecycleFlowSummary {
12 LifecycleFlowSummary {
13 intent_id: msg.flow_id.clone(),
14 flow_kind: enum_proto_name(&msg.flow_kind),
15 latest_step: enum_proto_name(&msg.current_step),
16 is_open: msg.is_open,
17 is_terminal: msg.is_terminal,
18 owner_account_id: format_uint64_id(msg.owner_account_id),
19 smart_account_address: msg.source_address.clone(),
20 }
21}
22
23pub fn flow_summary_message_from_proto(msg: &FlowSummaryView) -> LifecycleFlowSummary {
24 flow_summary_from_proto(msg)
25}
26
27pub fn flows_list_from_proto(msg: &ListFlowsResponse) -> LifecycleFlowsList {
28 LifecycleFlowsList {
29 flows: msg.flows.iter().map(flow_summary_from_proto).collect(),
30 next_page_token: msg.next_page_token.clone(),
31 }
32}
33
34fn flow_tx_match_from_proto(msg: &FlowTxMatchView) -> LifecycleFlowSummary {
35 LifecycleFlowSummary {
36 intent_id: msg.flow_id.clone(),
37 flow_kind: enum_proto_name(&msg.flow_kind),
38 latest_step: enum_proto_name(&msg.current_step),
39 is_open: msg.is_open,
40 is_terminal: msg.is_terminal,
41 owner_account_id: String::new(),
42 smart_account_address: msg.source_address.clone(),
43 }
44}
45
46pub fn flows_by_tx_list_from_proto(msg: &ListFlowsByTxResponse) -> LifecycleFlowsList {
47 LifecycleFlowsList {
48 flows: msg.matches.iter().map(flow_tx_match_from_proto).collect(),
49 next_page_token: msg.next_page_token.clone(),
50 }
51}
52
53pub fn flow_from_get_response(msg: &GetFlowResponse) -> Result<LifecycleFlowSummary> {
54 let detail = msg
55 .flow
56 .as_option()
57 .ok_or_else(|| Error::transport("invalid GetFlow response: missing flow"))?;
58 detail
59 .summary
60 .as_option()
61 .map(flow_summary_from_proto)
62 .ok_or_else(|| Error::transport("invalid GetFlow response: missing flow summary"))
63}
64
65pub fn flow_from_get_by_tx_response(msg: &ListFlowsByTxResponse) -> Result<LifecycleFlowSummary> {
66 msg.matches
67 .first()
68 .map(flow_tx_match_from_proto)
69 .ok_or_else(|| Error::transport("invalid GetFlowByTx response: no matching flow"))
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::proto::chain::lifecycle::v1::{FlowKind as FlowKindEnum, FlowStep};
76
77 #[test]
78 fn flow_summary_maps_fields() {
79 let msg = FlowSummaryView {
80 flow_id: "flow-abc".into(),
81 flow_kind: FlowKindEnum::KindDeposit.into(),
82 current_step: FlowStep::Settlement.into(),
83 is_open: false,
84 is_terminal: true,
85 owner_account_id: 99,
86 source_address: "0xabc".into(),
87 ..Default::default()
88 };
89 let flow = flow_summary_message_from_proto(&msg);
90 assert_eq!(flow.intent_id, "flow-abc");
91 assert!(flow.is_terminal);
92 assert_eq!(flow.smart_account_address, "0xabc");
93 assert_eq!(flow.owner_account_id, format_uint64_id(99));
94 }
95
96 #[test]
97 fn flows_list_maps_pagination() {
98 let msg = ListFlowsResponse {
99 flows: vec![
100 FlowSummaryView {
101 flow_id: "a".into(),
102 ..Default::default()
103 },
104 FlowSummaryView {
105 flow_id: "b".into(),
106 ..Default::default()
107 },
108 ],
109 next_page_token: "next".into(),
110 ..Default::default()
111 };
112 let result = flows_list_from_proto(&msg);
113 assert_eq!(result.flows.len(), 2);
114 assert_eq!(result.next_page_token, "next");
115 }
116
117 #[test]
118 fn singular_flow_responses_reject_missing_required_entities() {
119 assert!(flow_from_get_response(&GetFlowResponse::default()).is_err());
120 assert!(flow_from_get_by_tx_response(&ListFlowsByTxResponse::default()).is_err());
121 }
122}