Skip to main content

polyester/codecs/decode/
lifecycle.rs

1//! Lifecycle flow decoders.
2
3use 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}