1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#![deny(missing_docs)]

//! # axoasset
//! > 📮 load, write, and copy remote and local assets
//!
//! this library is a utility focused on managing both local (filesystem) assets
//! and remote (via http/https) assets. the bulk of the logic is not terribly
//! interesting or uniquely engineered; the purpose this library is primarily
//! to unify and co-locate the logic to make debugging simpler and error handling
//! more consistent and comprehensive.

use std::path::PathBuf;

#[cfg(any(feature = "compression-zip", feature = "compression-tar"))]
pub(crate) mod compression;
pub(crate) mod dirs;
pub(crate) mod error;
pub(crate) mod local;
#[cfg(feature = "remote")]
pub(crate) mod remote;
pub(crate) mod source;
pub(crate) mod spanned;

use camino::Utf8PathBuf;
pub use error::AxoassetError;
use error::Result;
pub use local::LocalAsset;
#[cfg(feature = "remote")]
pub use remote::RemoteAsset;
pub use source::SourceFile;
pub use spanned::Spanned;

/// An asset can either be a local asset, which is designated by a path on the
/// local file system, or a remote asset, which is designated by an http or
/// https url.
#[derive(Debug)]
pub enum Asset {
    /// An asset is a local asset if it is located on the local filesystem
    LocalAsset(LocalAsset),
    /// An asset is a remote asset if it is located at a http or https URL
    #[cfg(feature = "remote")]
    RemoteAsset(RemoteAsset),
}

impl Asset {
    /// Creates a new local asset. Does not write to filesystem. Will fail if
    /// passed a URL.
    pub fn new(origin_path: &str, contents: Vec<u8>) -> Result<Asset> {
        if is_remote(origin_path)? {
            Err(AxoassetError::CannotCreateRemoteAsset {
                origin_path: origin_path.to_string(),
            })
        } else {
            Ok(Asset::LocalAsset(LocalAsset::new(origin_path, contents)?))
        }
    }

    /// Loads an asset, either locally or remotely, returning an Asset enum
    /// variant containing the contents as bytes.
    pub async fn load(origin_path: &str) -> Result<Asset> {
        #[cfg(feature = "remote")]
        if is_remote(origin_path)? {
            return Ok(Asset::RemoteAsset(RemoteAsset::load(origin_path).await?));
        }
        Ok(Asset::LocalAsset(LocalAsset::load(origin_path)?))
    }

    /// Loads an asset, returning its contents as a String.
    pub async fn load_string(origin_path: &str) -> Result<String> {
        #[cfg(feature = "remote")]
        if is_remote(origin_path)? {
            return RemoteAsset::load_string(origin_path).await;
        }
        LocalAsset::load_string(origin_path)
    }

    /// Loads an asset, returning its contents as a vector of bytes.
    pub async fn load_bytes(origin_path: &str) -> Result<Vec<u8>> {
        #[cfg(feature = "remote")]
        if is_remote(origin_path)? {
            return RemoteAsset::load_bytes(origin_path).await;
        }
        LocalAsset::load_bytes(origin_path)
    }

    /// Copies an asset, returning the path to the copy destination on the
    /// local filesystem.
    pub async fn copy(origin_path: &str, dest_dir: &str) -> Result<Utf8PathBuf> {
        #[cfg(feature = "remote")]
        if is_remote(origin_path)? {
            return RemoteAsset::copy(origin_path, dest_dir).await;
        }
        LocalAsset::copy(origin_path, dest_dir)
    }

    /// Writes an asset, returning the path to the write destination on the
    /// local filesystem.
    pub async fn write(self, dest_dir: &str) -> Result<PathBuf> {
        match self {
            #[cfg(feature = "remote")]
            Asset::RemoteAsset(a) => a.write(dest_dir).await,
            Asset::LocalAsset(a) => a.write(dest_dir),
        }
    }
}

fn is_remote(origin_path: &str) -> Result<bool> {
    if origin_path.starts_with("http") {
        match origin_path.parse() {
            Ok(url) => {
                if is_http(url) {
                    Ok(true)
                } else {
                    Err(AxoassetError::RemoteAssetPathSchemeNotSupported {
                        origin_path: origin_path.to_string(),
                    })
                }
            }
            Err(details) => Err(AxoassetError::RemoteAssetPathParseError {
                origin_path: origin_path.to_string(),
                details,
            }),
        }
    } else {
        Ok(false)
    }
}

fn is_http(url: url::Url) -> bool {
    url.scheme() == "https" || url.scheme() == "http"
}