junobuild_storage/
utils.rs

1use crate::constants::{WELL_KNOWN_CUSTOM_DOMAINS, WELL_KNOWN_II_ALTERNATIVE_ORIGINS};
2use crate::http::types::HeaderField;
3use crate::strategies::StorageAssertionsStrategy;
4use crate::types::interface::AssetNoContent;
5use crate::types::state::{AssetAccessToken, FullPath};
6use crate::types::store::{Asset, AssetEncoding, AssetKey};
7use crate::well_known::types::WellKnownAsset;
8use candid::Principal;
9use junobuild_collections::constants::assets::COLLECTION_ASSET_KEY;
10use junobuild_collections::types::core::CollectionKey;
11use junobuild_collections::types::rules::Permission;
12use junobuild_shared::data::list::{filter_timestamps, matcher_regex};
13use junobuild_shared::types::core::Blob;
14use junobuild_shared::types::list::ListParams;
15use junobuild_shared::types::state::{Controllers, UserId};
16use regex::Regex;
17
18pub fn map_asset_no_content(asset: &Asset) -> (FullPath, AssetNoContent) {
19    (asset.key.full_path.clone(), AssetNoContent::from(asset))
20}
21
22pub fn filter_values<'a>(
23    caller: Principal,
24    controllers: &'a Controllers,
25    permission: &'a Permission,
26    collection: CollectionKey,
27    ListParams {
28        matcher,
29        order: _,
30        paginate: _,
31        owner,
32    }: &'a ListParams,
33    assets: &'a [(&'a FullPath, &'a Asset)],
34    assertions: &impl StorageAssertionsStrategy,
35) -> Result<Vec<(&'a FullPath, &'a Asset)>, String> {
36    let (regex_key, regex_description) = matcher_regex(matcher)?;
37
38    let result = assets
39        .iter()
40        .filter_map(|(key, asset)| {
41            if filter_collection(collection.clone(), asset)
42                && filter_full_path(&regex_key, asset)
43                && filter_description(&regex_description, asset)
44                && filter_owner(*owner, asset)
45                && filter_timestamps(matcher, *asset)
46                && assertions.assert_list_permission(
47                    permission,
48                    asset.key.owner,
49                    caller,
50                    &collection,
51                    controllers,
52                )
53            {
54                Some((*key, *asset))
55            } else {
56                None
57            }
58        })
59        .collect();
60
61    Ok(result)
62}
63
64fn filter_full_path(regex: &Option<Regex>, asset: &Asset) -> bool {
65    match regex {
66        None => true,
67        Some(re) => re.is_match(&asset.key.full_path),
68    }
69}
70
71fn filter_description(regex: &Option<Regex>, asset: &Asset) -> bool {
72    match regex {
73        None => true,
74        Some(re) => match &asset.key.description {
75            None => false,
76            Some(description) => re.is_match(description),
77        },
78    }
79}
80
81fn filter_collection(collection: CollectionKey, asset: &Asset) -> bool {
82    asset.key.collection == collection
83}
84
85fn filter_owner(filter_owner: Option<UserId>, asset: &Asset) -> bool {
86    match filter_owner {
87        None => true,
88        Some(filter_owner) => filter_owner == asset.key.owner,
89    }
90}
91
92pub fn filter_collection_values<'a>(
93    collection: CollectionKey,
94    assets: &'a [(&'a FullPath, &'a Asset)],
95) -> Vec<(&'a FullPath, &'a Asset)> {
96    assets
97        .iter()
98        .filter_map(|(key, asset)| {
99            if filter_collection(collection.clone(), asset) {
100                Some((*key, *asset))
101            } else {
102                None
103            }
104        })
105        .collect()
106}
107
108pub fn get_token_protected_asset(
109    asset: &Asset,
110    asset_token: &String,
111    token: AssetAccessToken,
112) -> Option<Asset> {
113    match token {
114        None => None,
115        Some(token) => {
116            if &token == asset_token {
117                return Some(asset.clone());
118            }
119
120            None
121        }
122    }
123}
124
125pub fn should_include_asset_for_deletion(collection: &CollectionKey, asset_path: &String) -> bool {
126    let excluded_paths = [
127        WELL_KNOWN_CUSTOM_DOMAINS.to_string(),
128        WELL_KNOWN_II_ALTERNATIVE_ORIGINS.to_string(),
129    ];
130
131    collection != COLLECTION_ASSET_KEY || !excluded_paths.contains(asset_path)
132}
133
134pub fn map_content_type_headers(content_type: &str) -> Vec<HeaderField> {
135    vec![HeaderField(
136        "content-type".to_string(),
137        content_type.to_string(),
138    )]
139}
140
141pub fn map_content_encoding(content: &Blob) -> AssetEncoding {
142    let max_chunk_size = 1_900_000; // Max 1.9 MB per chunk
143    let chunks = content
144        .chunks(max_chunk_size)
145        .map(|chunk| chunk.to_vec())
146        .collect();
147
148    AssetEncoding::from(&chunks)
149}
150
151pub fn create_asset_with_content(
152    content: &str,
153    headers: &[HeaderField],
154    existing_asset: Option<Asset>,
155    key: AssetKey,
156) -> WellKnownAsset {
157    let asset: Asset = Asset::prepare(key, headers.to_vec(), &existing_asset);
158
159    let encoding = map_content_encoding(&content.as_bytes().to_vec());
160
161    (asset, encoding)
162}
163
164pub fn clone_asset_encoding_content_chunks(encoding: &AssetEncoding, chunk_index: usize) -> Blob {
165    encoding.content_chunks[chunk_index].clone()
166}
167
168/// With heap memory the encodings are part of the asset struct.
169/// Using stable, the encoding become a key pointing to another stable tree.
170pub fn insert_encoding_into_asset(
171    encoding_type: &str,
172    encoding: &AssetEncoding,
173    asset: &mut Asset,
174) {
175    asset
176        .encodings
177        .insert(encoding_type.to_owned(), encoding.clone());
178}