riffdb_client_rust/
export.rs1use std::fmt;
4
5use riffdb_proto::v1;
6use riffdb_types::{
7 ApplicationExportOperationId, ApplicationExportSelectionV1, ApplicationPortabilityManifest,
8 CapabilityApplicationExportScopeV1, RequestId,
9};
10
11#[derive(Clone, Eq, PartialEq)]
13pub struct StartApplicationExport {
14 operation_id: ApplicationExportOperationId,
15 selection: ApplicationExportSelectionV1,
16 portability_manifest: Option<ApplicationPortabilityManifest>,
17 lease_seconds: u32,
18}
19
20impl StartApplicationExport {
21 #[must_use]
23 pub const fn new(
24 operation_id: ApplicationExportOperationId,
25 selection: ApplicationExportSelectionV1,
26 lease_seconds: u32,
27 ) -> Self {
28 Self {
29 operation_id,
30 selection,
31 portability_manifest: None,
32 lease_seconds,
33 }
34 }
35
36 #[must_use]
38 pub fn new_portability(
39 operation_id: ApplicationExportOperationId,
40 selection: ApplicationExportSelectionV1,
41 portability_manifest: ApplicationPortabilityManifest,
42 lease_seconds: u32,
43 ) -> Self {
44 Self {
45 operation_id,
46 selection,
47 portability_manifest: Some(portability_manifest),
48 lease_seconds,
49 }
50 }
51
52 #[must_use]
54 pub const fn operation_id(&self) -> ApplicationExportOperationId {
55 self.operation_id
56 }
57
58 #[must_use]
60 pub const fn selection(&self) -> &ApplicationExportSelectionV1 {
61 &self.selection
62 }
63
64 #[must_use]
66 pub const fn lease_seconds(&self) -> u32 {
67 self.lease_seconds
68 }
69
70 #[must_use]
72 pub const fn portability_manifest(&self) -> Option<&ApplicationPortabilityManifest> {
73 self.portability_manifest.as_ref()
74 }
75
76 pub(crate) fn request(&self, request_id: RequestId) -> v1::StartApplicationExportRequest {
77 let scope = match self.selection.scope() {
78 CapabilityApplicationExportScopeV1::PrincipalFiltered => {
79 v1::CapabilityApplicationExportScope::PrincipalFiltered
80 }
81 CapabilityApplicationExportScopeV1::WholeApplication => {
82 v1::CapabilityApplicationExportScope::WholeApplication
83 }
84 };
85 v1::StartApplicationExportRequest {
86 request_id: request_id.into_bytes().to_vec(),
87 operation_id: self.operation_id.into_bytes().to_vec(),
88 selection: Some(v1::ApplicationExportSelection {
89 contract_lineage: self.selection.lineage().as_str().to_owned(),
90 scope: scope as i32,
91 entities: self.selection.entities(),
92 events: self.selection.events(),
93 provenance: self.selection.provenance(),
94 public_audit: self.selection.public_audit(),
95 }),
96 lease_seconds: self.lease_seconds,
97 canonical_portability_manifest_json: self
98 .portability_manifest
99 .as_ref()
100 .map_or_else(Vec::new, |manifest| manifest.canonical_bytes().to_vec()),
101 }
102 }
103}
104
105impl fmt::Debug for StartApplicationExport {
106 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
107 formatter
108 .debug_struct("StartApplicationExport")
109 .field("operation_id", &self.operation_id)
110 .field("selection", &self.selection)
111 .field(
112 "portability_manifest",
113 &self
114 .portability_manifest
115 .as_ref()
116 .map(|value| value.identity()),
117 )
118 .field("lease_seconds", &self.lease_seconds)
119 .finish()
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use riffdb_types::ContractLineage;
126
127 use super::*;
128
129 fn uuid(seed: u8) -> [u8; 16] {
130 let mut bytes = [seed; 16];
131 bytes[6] = 0x70 | (seed & 0x0f);
132 bytes[8] = 0x80 | (seed & 0x3f);
133 bytes
134 }
135
136 #[test]
137 fn retry_changes_only_outer_request_identity() {
138 let start = StartApplicationExport::new(
139 ApplicationExportOperationId::from_bytes(uuid(1)).expect("operation"),
140 ApplicationExportSelectionV1::new(
141 ContractLineage::new("TicketDesk").expect("lineage"),
142 CapabilityApplicationExportScopeV1::WholeApplication,
143 true,
144 true,
145 true,
146 false,
147 )
148 .expect("selection"),
149 900,
150 );
151 let first = start.request(RequestId::from_bytes(uuid(2)).expect("request"));
152 let second = start.request(RequestId::from_bytes(uuid(3)).expect("request"));
153 assert_ne!(first.request_id, second.request_id);
154 assert_eq!(first.operation_id, second.operation_id);
155 assert_eq!(first.selection, second.selection);
156 assert_eq!(first.lease_seconds, second.lease_seconds);
157 assert!(first.canonical_portability_manifest_json.is_empty());
158 }
159
160 #[test]
161 fn portability_manifest_bytes_are_part_of_every_retry_submission() {
162 let manifest = ApplicationPortabilityManifest::decode_canonical(include_bytes!(
163 "../../../fixtures/export/openfga/portability-manifest-v2.json"
164 ))
165 .expect("manifest");
166 let selection = ApplicationExportSelectionV1::new(
167 manifest.input().contract_lineage.clone(),
168 CapabilityApplicationExportScopeV1::WholeApplication,
169 true,
170 false,
171 false,
172 false,
173 )
174 .expect("selection");
175 let start = StartApplicationExport::new_portability(
176 ApplicationExportOperationId::from_bytes(uuid(1)).expect("operation"),
177 selection,
178 manifest.clone(),
179 900,
180 );
181 let first = start.request(RequestId::from_bytes(uuid(2)).expect("request"));
182 let second = start.request(RequestId::from_bytes(uuid(3)).expect("request"));
183 assert_eq!(
184 first.canonical_portability_manifest_json,
185 manifest.canonical_bytes()
186 );
187 assert_eq!(
188 first.canonical_portability_manifest_json,
189 second.canonical_portability_manifest_json
190 );
191 }
192}