junobuild_cdn/proposals/workflows/
commit.rs

1use crate::proposals::errors::{
2    JUNO_CDN_PROPOSALS_ERROR_CANNOT_COMMIT, JUNO_CDN_PROPOSALS_ERROR_CANNOT_COMMIT_INVALID_STATUS,
3    JUNO_CDN_PROPOSALS_ERROR_EMPTY_ASSETS, JUNO_CDN_PROPOSALS_ERROR_EMPTY_CONTENT_CHUNKS,
4    JUNO_CDN_PROPOSALS_ERROR_INVALID_HASH, JUNO_CDN_PROPOSALS_ERROR_NOT_CONTENT_CHUNKS_AT_INDEX,
5};
6use crate::proposals::workflows::assert::assert_known_proposal_type;
7use crate::proposals::{get_proposal, insert_proposal};
8use crate::proposals::{CommitProposal, CommitProposalError, Proposal, ProposalId, ProposalStatus};
9use crate::storage::heap::get_rule;
10use crate::storage::stable::{get_assets, get_content_chunks};
11use crate::strategies::{
12    CdnCommitAssetsStrategy, CdnHeapStrategy, CdnStableStrategy, CdnWorkflowStrategy,
13};
14use hex::encode;
15use junobuild_collections::types::core::CollectionKey;
16use junobuild_collections::types::rules::Rule;
17use junobuild_storage::types::store::AssetEncoding;
18use std::collections::HashMap;
19
20pub fn commit_proposal(
21    cdn_heap: &impl CdnHeapStrategy,
22    cdn_commit_assets: &impl CdnCommitAssetsStrategy,
23    cdn_stable: &impl CdnStableStrategy,
24    cdn_workflow: &impl CdnWorkflowStrategy,
25    proposition: &CommitProposal,
26) -> Result<(), CommitProposalError> {
27    let proposal = get_proposal(cdn_stable, &proposition.proposal_id).ok_or_else(|| {
28        CommitProposalError::ProposalNotFound(format!(
29            "{} ({})",
30            JUNO_CDN_PROPOSALS_ERROR_CANNOT_COMMIT, proposition.proposal_id
31        ))
32    })?;
33
34    match secure_commit_proposal(
35        cdn_heap,
36        cdn_commit_assets,
37        cdn_stable,
38        cdn_workflow,
39        proposition,
40        &proposal,
41    ) {
42        Ok(_) => {
43            let executed_proposal = Proposal::execute(&proposal);
44            insert_proposal(cdn_stable, &proposition.proposal_id, &executed_proposal);
45            Ok(())
46        }
47        Err(e @ CommitProposalError::CommitAssetsIssue(_))
48        | Err(e @ CommitProposalError::PreCommitAssetsIssue(_))
49        | Err(e @ CommitProposalError::PostCommitAssetsIssue(_)) => {
50            let failed_proposal = Proposal::fail(&proposal);
51            insert_proposal(cdn_stable, &proposition.proposal_id, &failed_proposal);
52            Err(e)
53        }
54        Err(e) => Err(e),
55    }
56}
57
58fn secure_commit_proposal(
59    cdn_heap: &impl CdnHeapStrategy,
60    cdn_commit_assets: &impl CdnCommitAssetsStrategy,
61    cdn_stable: &impl CdnStableStrategy,
62    cdn_workflow: &impl CdnWorkflowStrategy,
63    commit_proposal: &CommitProposal,
64    proposal: &Proposal,
65) -> Result<(), CommitProposalError> {
66    if proposal.status != ProposalStatus::Open {
67        return Err(CommitProposalError::ProposalNotOpen(format!(
68            "{} ({:?})",
69            JUNO_CDN_PROPOSALS_ERROR_CANNOT_COMMIT_INVALID_STATUS, proposal.status
70        )));
71    }
72
73    match &proposal.sha256 {
74        Some(sha256) if sha256 == &commit_proposal.sha256 => (),
75        _ => {
76            return Err(CommitProposalError::InvalidSha256(format!(
77                "{} ({})",
78                JUNO_CDN_PROPOSALS_ERROR_INVALID_HASH,
79                encode(commit_proposal.sha256)
80            )));
81        }
82    }
83
84    assert_known_proposal_type(proposal).map_err(CommitProposalError::InvalidType)?;
85
86    // Mark proposal as accepted.
87    let accepted_proposal = Proposal::accept(proposal);
88    insert_proposal(cdn_stable, &commit_proposal.proposal_id, &accepted_proposal);
89
90    cdn_workflow
91        .pre_commit_assets(proposal)
92        .map_err(CommitProposalError::PreCommitAssetsIssue)?;
93
94    copy_committed_assets(
95        cdn_heap,
96        cdn_commit_assets,
97        cdn_stable,
98        &commit_proposal.proposal_id,
99    )
100    .map_err(CommitProposalError::CommitAssetsIssue)?;
101
102    cdn_workflow
103        .post_commit_assets(proposal)
104        .map_err(CommitProposalError::PostCommitAssetsIssue)?;
105
106    Ok(())
107}
108
109fn copy_committed_assets(
110    cdn_heap: &impl CdnHeapStrategy,
111    cdn_commit_assets: &impl CdnCommitAssetsStrategy,
112    cdn_stable: &impl CdnStableStrategy,
113    proposal_id: &ProposalId,
114) -> Result<(), String> {
115    // Copy from stable memory to heap.
116    let assets = get_assets(cdn_stable, proposal_id);
117
118    if assets.is_empty() {
119        return Err(format!(
120            "{JUNO_CDN_PROPOSALS_ERROR_EMPTY_ASSETS} ({proposal_id})"
121        ));
122    }
123
124    // We cache the rules for performance. With current implementation
125    // there should be only one rule for all assets.
126    let mut rule_cache: HashMap<CollectionKey, Rule> = HashMap::new();
127
128    for (key, mut asset) in assets {
129        let rule = rule_cache
130            .entry(key.collection.clone())
131            .or_insert_with(|| get_rule(cdn_heap, &key.collection).unwrap())
132            .clone();
133
134        let encodings = std::mem::take(&mut asset.encodings);
135
136        for (encoding_type, encoding) in encodings {
137            let mut content_chunks = Vec::new();
138
139            for (i, _) in encoding.content_chunks.iter().enumerate() {
140                let chunks = get_content_chunks(cdn_stable, &encoding, i).ok_or_else(|| {
141                    format!(
142                        "{JUNO_CDN_PROPOSALS_ERROR_NOT_CONTENT_CHUNKS_AT_INDEX} ({encoding_type} - {i})."
143                    )
144                })?;
145
146                content_chunks.push(chunks);
147            }
148
149            if content_chunks.is_empty() {
150                return Err(format!(
151                    "{JUNO_CDN_PROPOSALS_ERROR_EMPTY_CONTENT_CHUNKS} ({encoding_type})"
152                ));
153            }
154
155            let encoding_with_content = AssetEncoding {
156                content_chunks,
157                ..encoding
158            };
159
160            cdn_commit_assets.insert_asset_encoding(
161                &key.full_path,
162                &encoding_type,
163                &encoding_with_content,
164                &mut asset,
165                &rule,
166            );
167
168            cdn_commit_assets.insert_asset(&key.collection, &key.full_path, &asset, &rule);
169        }
170    }
171
172    Ok(())
173}