Skip to content

Commit

Permalink
feat(ic-asset-certification): add asset map for querying assets in th…
Browse files Browse the repository at this point in the history
…e asset router
  • Loading branch information
nathanosdev committed Oct 28, 2024
1 parent 8f11afd commit 9953d4b
Show file tree
Hide file tree
Showing 7 changed files with 716 additions and 210 deletions.
19 changes: 19 additions & 0 deletions packages/ic-asset-certification/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -749,3 +749,22 @@ use ic_cdk::api::set_certified_data;
set_certified_data(&asset_router.root_hash());
```

## Querying assets

The `AssetRouter` has two functions to retrieve an `AssetMap` containing assets.

The `get_assets()` function returns all standard assets, while the `get_fallback_assets()` function returns all fallback assets.

The `AssetMap` can be used to query assets by `path`, `encoding`, and `starting_range`.

For standard assets, the path refers to the asset's path, e.g. `/index.html`.

For fallback assets, the path refers to the scope that the fallback is valid for, e.g. `/`. See the `fallback_for` config option for more information on fallback scopes.

For all types of assets, the encoding refers to the encoding of the asset, see `AssetEncoding`.

Assets greater than 2mb are split into multiple ranges, the starting range allows retrieval of
individual chunks of these large assets. The first range is `Some(0)`, the second range is
`Some(2_000_000)`, the third range is `Some(4_000_000)`, and so on. The entire asset can
also be retrieved by passing `None` as the `starting_range`.
26 changes: 18 additions & 8 deletions packages/ic-asset-certification/src/asset_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,9 @@ pub enum AssetRedirectKind {
/// The encoding of an asset.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AssetEncoding {
/// The asset is not encoded.
Identity,

/// The asset is encoded with the Brotli algorithm.
Brotli,

Expand Down Expand Up @@ -437,26 +440,31 @@ impl AssetEncoding {
///
/// let (encoding, extension) = AssetEncoding::Brotli.default_config();
/// assert_eq!(encoding, AssetEncoding::Brotli);
/// assert_eq!(extension, "br");
/// assert_eq!(extension, ".br");
///
/// let (encoding, extension) = AssetEncoding::Zstd.default_config();
/// assert_eq!(encoding, AssetEncoding::Zstd);
/// assert_eq!(extension, "zst");
/// assert_eq!(extension, ".zst");
///
/// let (encoding, extension) = AssetEncoding::Gzip.default_config();
/// assert_eq!(encoding, AssetEncoding::Gzip);
/// assert_eq!(extension, "gz");
/// assert_eq!(extension, ".gz");
///
/// let (encoding, extension) = AssetEncoding::Deflate.default_config();
/// assert_eq!(encoding, AssetEncoding::Deflate);
/// assert_eq!(extension, "zz");
/// assert_eq!(extension, ".zz");
///
/// let (encoding, extension) = AssetEncoding::Identity.default_config();
/// assert_eq!(encoding, AssetEncoding::Identity);
/// assert_eq!(extension, "");
/// ```
pub fn default_config(self) -> (AssetEncoding, String) {
let file_extension = match self {
AssetEncoding::Brotli => "br".to_string(),
AssetEncoding::Zstd => "zst".to_string(),
AssetEncoding::Gzip => "gz".to_string(),
AssetEncoding::Deflate => "zz".to_string(),
AssetEncoding::Identity => "".to_string(),
AssetEncoding::Brotli => ".br".to_string(),
AssetEncoding::Zstd => ".zst".to_string(),
AssetEncoding::Gzip => ".gz".to_string(),
AssetEncoding::Deflate => ".zz".to_string(),
};

(self, file_extension)
Expand Down Expand Up @@ -484,6 +492,7 @@ impl AssetEncoding {
impl Display for AssetEncoding {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str = match self {
AssetEncoding::Identity => "identity".to_string(),
AssetEncoding::Brotli => "br".to_string(),
AssetEncoding::Zstd => "zstd".to_string(),
AssetEncoding::Gzip => "gzip".to_string(),
Expand Down Expand Up @@ -718,5 +727,6 @@ mod tests {
assert_eq!(AssetEncoding::Zstd.to_string(), "zstd");
assert_eq!(AssetEncoding::Gzip.to_string(), "gzip");
assert_eq!(AssetEncoding::Deflate.to_string(), "deflate");
assert_eq!(AssetEncoding::Identity.to_string(), "identity");
}
}
80 changes: 80 additions & 0 deletions packages/ic-asset-certification/src/asset_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use crate::{AssetEncoding, CertifiedAssetResponse, RequestKey};
use ic_http_certification::HttpResponse;
use std::collections::{hash_map::Iter, HashMap};

/// A map of assets, indexed by path, encoding, and the starting range.
pub trait AssetMap<'content> {
/// Get an asset by path, encoding, and starting range.
///
/// For standard assets, the path refers to the asset's path, e.g. `/index.html`.
///
/// For fallback assets, the path refers to the scope that the fallback is valid for, e.g. `/`.
/// See the [fallback_for](crate::AssetConfig::File::fallback_for) config option for more information
/// on fallback scopes.
///
/// For all types of assets, the encoding refers to the encoding of the asset, see [AssetEncoding].
///
/// Assets greater than 2mb are split into multiple ranges, the starting range allows retrieval of
/// individual chunks of these large assets. The first range is `Some(0)`, the second range is
/// `Some(2_000_000)`, the third range is `Some(4_000_000)`, and so on. The entire asset can
/// also be retrieved by passing `None` as the starting range.
fn get(
&self,
path: impl Into<String>,
encoding: Option<AssetEncoding>,
starting_range: Option<usize>,
) -> Option<&HttpResponse<'content>>;

/// Returns the number of assets in the map.
fn len(&self) -> usize;

/// Returns an iterator over the assets in the map.
fn iter(&'content self) -> AssetMapIterator<'content>;
}

impl<'content> AssetMap<'content> for HashMap<RequestKey, CertifiedAssetResponse<'content>> {
fn get(
&self,
path: impl Into<String>,
encoding: Option<AssetEncoding>,
range_begin: Option<usize>,
) -> Option<&HttpResponse<'content>> {
let req_key = RequestKey::new(path, encoding.map(|e| e.to_string()), range_begin);

self.get(&req_key).map(|e| &e.response)
}

fn len(&self) -> usize {
self.len()
}

fn iter(&'content self) -> AssetMapIterator<'content> {
AssetMapIterator { inner: self.iter() }
}
}

/// An iterator over the assets in an asset map.
#[derive(Debug)]
pub struct AssetMapIterator<'content> {
inner: Iter<'content, RequestKey, CertifiedAssetResponse<'content>>,
}

impl<'content> Iterator for AssetMapIterator<'content> {
type Item = (
(&'content str, Option<&'content str>, Option<usize>),
&'content HttpResponse<'content>,
);

fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(key, asset)| {
(
(
key.path.as_str(),
key.encoding.as_ref().map(|e| e.as_str()),
key.range_begin,
),
&asset.response,
)
})
}
}
Loading

0 comments on commit 9953d4b

Please sign in to comment.