riffdb_client_rust/
reimport.rs1use std::fmt;
4
5use riffdb_proto::{PublicWireError, v1, validate_public_message};
6use riffdb_types::{
7 ApplicationInstallationCampaignId, ApplicationPortabilityManifest,
8 CapabilityApplicationReimportScopeV1, ContractLineage, RequestId,
9};
10
11#[derive(Clone, Eq, PartialEq)]
13pub struct StartApplicationReimport {
14 campaign_id: ApplicationInstallationCampaignId,
15 lineage: ContractLineage,
16 scope: CapabilityApplicationReimportScopeV1,
17 portability_manifest: ApplicationPortabilityManifest,
18 canonical_export_manifest_json: Vec<u8>,
19 canonical_export_receipt_json: Vec<u8>,
20}
21
22impl StartApplicationReimport {
23 pub fn new(
29 campaign_id: ApplicationInstallationCampaignId,
30 lineage: ContractLineage,
31 scope: CapabilityApplicationReimportScopeV1,
32 portability_manifest: ApplicationPortabilityManifest,
33 canonical_export_manifest_json: Vec<u8>,
34 canonical_export_receipt_json: Vec<u8>,
35 ) -> Result<Self, PublicWireError> {
36 let submission = Self {
37 campaign_id,
38 lineage,
39 scope,
40 portability_manifest,
41 canonical_export_manifest_json,
42 canonical_export_receipt_json,
43 };
44 validate_public_message(
45 &submission.request(
46 RequestId::from_bytes(campaign_id.into_bytes())
47 .map_err(|_| PublicWireError::InvalidUuidV7)?,
48 ),
49 )?;
50 Ok(submission)
51 }
52
53 #[must_use]
55 pub const fn campaign_id(&self) -> ApplicationInstallationCampaignId {
56 self.campaign_id
57 }
58
59 #[must_use]
61 pub const fn lineage(&self) -> &ContractLineage {
62 &self.lineage
63 }
64
65 #[must_use]
67 pub const fn scope(&self) -> CapabilityApplicationReimportScopeV1 {
68 self.scope
69 }
70
71 #[must_use]
73 pub const fn portability_manifest(&self) -> &ApplicationPortabilityManifest {
74 &self.portability_manifest
75 }
76
77 pub(crate) fn request(&self, request_id: RequestId) -> v1::StartApplicationReimportRequest {
78 let scope = match self.scope {
79 CapabilityApplicationReimportScopeV1::PrincipalFiltered => {
80 v1::CapabilityApplicationReimportScope::PrincipalFiltered
81 }
82 CapabilityApplicationReimportScopeV1::WholeApplication => {
83 v1::CapabilityApplicationReimportScope::WholeApplication
84 }
85 };
86 v1::StartApplicationReimportRequest {
87 request_id: request_id.into_bytes().to_vec(),
88 campaign_id: self.campaign_id.into_bytes().to_vec(),
89 contract_lineage: self.lineage.as_str().to_owned(),
90 scope: scope as i32,
91 canonical_portability_manifest_json: self
92 .portability_manifest
93 .canonical_bytes()
94 .to_vec(),
95 canonical_export_manifest_json: self.canonical_export_manifest_json.clone(),
96 canonical_export_receipt_json: self.canonical_export_receipt_json.clone(),
97 }
98 }
99}
100
101impl fmt::Debug for StartApplicationReimport {
102 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
103 formatter
104 .debug_struct("StartApplicationReimport")
105 .field("campaign_id", &self.campaign_id)
106 .field("lineage", &self.lineage)
107 .field("scope", &self.scope)
108 .field(
109 "portability_manifest_hash",
110 &self.portability_manifest.identity(),
111 )
112 .finish_non_exhaustive()
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 fn uuid(seed: u8) -> [u8; 16] {
121 let mut bytes = [seed; 16];
122 bytes[6] = 0x70 | (seed & 0x0f);
123 bytes[8] = 0x80 | (seed & 0x3f);
124 bytes
125 }
126
127 #[test]
128 fn retries_change_only_outer_request_identity() {
129 let portability = ApplicationPortabilityManifest::decode_canonical(include_bytes!(
130 "../../../fixtures/export/openfga/portability-manifest-v2.json"
131 ))
132 .expect("portability manifest");
133 let start = StartApplicationReimport::new(
134 ApplicationInstallationCampaignId::from_bytes(uuid(1)).expect("campaign"),
135 portability.input().contract_lineage.clone(),
136 CapabilityApplicationReimportScopeV1::WholeApplication,
137 portability,
138 b"{\"manifest\":\"exact\"}".to_vec(),
139 b"{\"receipt\":\"exact\"}".to_vec(),
140 )
141 .expect("structural submission");
142 let first = start.request(RequestId::from_bytes(uuid(2)).expect("request"));
143 let second = start.request(RequestId::from_bytes(uuid(3)).expect("request"));
144 assert_ne!(first.request_id, second.request_id);
145 assert_eq!(first.campaign_id, second.campaign_id);
146 assert_eq!(first.contract_lineage, second.contract_lineage);
147 assert_eq!(first.scope, second.scope);
148 assert_eq!(
149 first.canonical_portability_manifest_json,
150 second.canonical_portability_manifest_json
151 );
152 assert_eq!(
153 first.canonical_export_manifest_json,
154 second.canonical_export_manifest_json
155 );
156 assert_eq!(
157 first.canonical_export_receipt_json,
158 second.canonical_export_receipt_json
159 );
160 }
161
162 #[test]
163 fn malformed_source_documents_fail_before_transport() {
164 let portability = ApplicationPortabilityManifest::decode_canonical(include_bytes!(
165 "../../../fixtures/export/openfga/portability-manifest-v2.json"
166 ))
167 .expect("portability manifest");
168 assert_eq!(
169 StartApplicationReimport::new(
170 ApplicationInstallationCampaignId::from_bytes(uuid(1)).expect("campaign"),
171 portability.input().contract_lineage.clone(),
172 CapabilityApplicationReimportScopeV1::WholeApplication,
173 portability,
174 b"not-json".to_vec(),
175 b"{}".to_vec(),
176 )
177 .expect_err("malformed source must fail"),
178 PublicWireError::InvalidBytes
179 );
180 }
181}