dioxus_asset_resolver/lib.rs
1#![warn(missing_docs)]
2//! The asset resolver for the Dioxus bundle format. Each platform has its own way of resolving assets. This crate handles
3//! resolving assets in a cross-platform way.
4//!
5//! There are two broad locations for assets depending on the platform:
6//! - **Web**: Assets are stored on a remote server and fetched via HTTP requests.
7//! - **Native**: Assets are read from the local bundle. Each platform has its own bundle structure which may store assets
8//! as a file at a specific path or in an opaque format like Android's AssetManager.
9//!
10//! [`read_asset_bytes`]( abstracts over both of these methods, allowing you to read the bytes of an asset
11//! regardless of the platform.
12//!
13//! If you know you are on a desktop platform, you can use [`asset_path`] to resolve the path of an asset and read
14//! the contents with [`std::fs`].
15//!
16//! ## Example
17//! ```rust
18//! # async fn asset_example() {
19//! use dioxus::prelude::*;
20//!
21//! // Bundle the static JSON asset into the application
22//! static JSON_ASSET: Asset = asset!("/assets/data.json");
23//!
24//! // Read the bytes of the JSON asset
25//! let bytes = dioxus::asset_resolver::read_asset_bytes(&JSON_ASSET).await.unwrap();
26//!
27//! // Deserialize the JSON data
28//! let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
29//! assert_eq!(json["key"].as_str(), Some("value"));
30//! # }
31//! ```
32
33use std::{fmt::Debug, path::PathBuf};
34
35#[cfg(feature = "native")]
36pub mod native;
37
38#[cfg(feature = "web")]
39mod web;
40
41/// An error that can occur when resolving an asset to a path. Not all platforms can represent assets as paths,
42/// an error may mean that the asset doesn't exist or it cannot be represented as a path.
43#[non_exhaustive]
44#[derive(Debug, thiserror::Error)]
45pub enum AssetPathError {
46 /// The asset was not found by the resolver.
47 #[error("Failed to find the path in the asset directory")]
48 NotFound,
49
50 /// The asset may exist, but it cannot be represented as a path.
51 #[error("Asset cannot be represented as a path")]
52 CannotRepresentAsPath,
53}
54
55/// Tries to resolve the path of an asset from a given URI path. Depending on the platform, this may
56/// return an error even if the asset exists because some platforms cannot represent assets as paths.
57/// You should prefer [`read_asset_bytes`] to read the asset bytes directly
58/// for cross-platform compatibility.
59///
60/// ## Platform specific behavior
61///
62/// This function will only work on desktop platforms. It will always return an error in web and Android
63/// bundles. On Android assets are bundled in the APK, and cannot be represented as paths. In web bundles,
64/// Assets are fetched via HTTP requests and don't have a filesystem path.
65///
66/// ## Example
67/// ```rust
68/// use dioxus::prelude::*;
69///
70/// // Bundle the static JSON asset into the application
71/// static JSON_ASSET: Asset = asset!("/assets/data.json");
72///
73/// // Resolve the path of the asset. This will not work in web or Android bundles
74/// let path = dioxus::asset_resolver::asset_path(&JSON_ASSET).unwrap();
75///
76/// println!("Asset path: {:?}", path);
77///
78/// // Read the bytes of the JSON asset
79/// let bytes = std::fs::read(path).unwrap();
80///
81/// // Deserialize the JSON data
82/// let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
83/// assert_eq!(json["key"].as_str(), Some("value"));
84/// ```
85///
86/// ## Resolving assets from a folder
87///
88/// To resolve an asset from a folder, you can pass the path of the file joined with your folder asset as a string:
89/// ```rust
90/// # async fn asset_example() {
91/// use dioxus::prelude::*;
92///
93/// // Bundle the whole assets folder into the application
94/// static ASSETS: Asset = asset!("/assets");
95///
96/// // Resolve the path of the asset. This will not work in web or Android bundles
97/// let path = dioxus::asset_resolver::asset_path(format!("{ASSETS}/data.json")).unwrap();
98///
99/// println!("Asset path: {:?}", path);
100///
101/// // Read the bytes of the JSON asset
102/// let bytes = std::fs::read(path).unwrap();
103///
104/// // Deserialize the JSON data
105/// let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
106/// assert_eq!(json["key"].as_str(), Some("value"));
107/// # }
108/// ```
109#[allow(unused)]
110pub fn asset_path(asset: impl ToString) -> Result<PathBuf, AssetPathError> {
111 #[cfg(all(feature = "web", target_arch = "wasm32"))]
112 return Err(AssetPathError::CannotRepresentAsPath);
113
114 #[cfg(feature = "native")]
115 return native::resolve_native_asset_path(asset.to_string().as_str());
116
117 Err(AssetPathError::NotFound)
118}
119
120/// An error that can occur when resolving an asset.
121#[non_exhaustive]
122#[derive(Debug, thiserror::Error)]
123pub enum AssetResolveError {
124 /// An error occurred while resolving a native asset.
125 #[error("Failed to resolve native asset: {0}")]
126 Native(#[from] NativeAssetResolveError),
127
128 /// An error occurred while resolving a web asset.
129 #[error("Failed to resolve web asset: {0}")]
130 Web(#[from] WebAssetResolveError),
131
132 /// An error that occurs when no asset resolver is available for the current platform.
133 #[error("Asset resolution is not supported on this platform")]
134 UnsupportedPlatform,
135}
136
137/// Read the bytes of an asset. This will work on both web and native platforms. On the web,
138/// it will fetch the asset via HTTP, and on native platforms, it will read the asset from the filesystem or bundle.
139///
140/// ## Errors
141/// This function will return an error if the asset cannot be found or if it fails to read which may be due to I/O errors or
142/// network issues.
143///
144/// ## Example
145///
146/// ```rust
147/// # async fn asset_example() {
148/// use dioxus::prelude::*;
149///
150/// // Bundle the static JSON asset into the application
151/// static JSON_ASSET: Asset = asset!("/assets/data.json");
152///
153/// // Read the bytes of the JSON asset
154/// let bytes = dioxus::asset_resolver::read_asset_bytes(&JSON_ASSET).await.unwrap();
155///
156/// // Deserialize the JSON data
157/// let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
158/// assert_eq!(json["key"].as_str(), Some("value"));
159/// # }
160/// ```
161///
162/// ## Loading assets from a folder
163///
164/// To load an asset from a folder, you can pass the path of the file joined with your folder asset as a string:
165/// ```rust
166/// # async fn asset_example() {
167/// use dioxus::prelude::*;
168///
169/// // Bundle the whole assets folder into the application
170/// static ASSETS: Asset = asset!("/assets");
171///
172/// // Read the bytes of the JSON asset
173/// let bytes = dioxus::asset_resolver::read_asset_bytes(format!("{ASSETS}/data.json")).await.unwrap();
174///
175/// // Deserialize the JSON data
176/// let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
177/// assert_eq!(json["key"].as_str(), Some("value"));
178/// # }
179/// ```
180#[allow(unused)]
181pub async fn read_asset_bytes(asset: impl ToString) -> Result<Vec<u8>, AssetResolveError> {
182 let path = asset.to_string();
183
184 #[cfg(feature = "web")]
185 return web::resolve_web_asset(&path)
186 .await
187 .map_err(AssetResolveError::Web);
188
189 #[cfg(feature = "native")]
190 return tokio::task::spawn_blocking(move || native::resolve_native_asset(&path))
191 .await
192 .map_err(|err| AssetResolveError::Native(NativeAssetResolveError::JoinError(err)))
193 .and_then(|result| result.map_err(AssetResolveError::Native));
194
195 Err(AssetResolveError::UnsupportedPlatform)
196}
197
198/// An error that occurs when resolving a native asset.
199#[non_exhaustive]
200#[derive(Debug, thiserror::Error)]
201pub enum NativeAssetResolveError {
202 /// An I/O error occurred while reading the asset from the filesystem.
203 #[error("Failed to read asset: {0}")]
204 IoError(#[from] std::io::Error),
205
206 /// The asset resolver failed to complete and could not be joined.
207 #[cfg(feature = "native")]
208 #[error("Asset resolver join failed: {0}")]
209 JoinError(tokio::task::JoinError),
210}
211
212/// An error that occurs when resolving an asset on the web.
213pub struct WebAssetResolveError {
214 #[cfg(feature = "web")]
215 error: js_sys::Error,
216}
217
218impl Debug for WebAssetResolveError {
219 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220 let mut debug = f.debug_struct("WebAssetResolveError");
221 #[cfg(feature = "web")]
222 debug.field("name", &self.error.name());
223 #[cfg(feature = "web")]
224 debug.field("message", &self.error.message());
225 debug.finish()
226 }
227}
228
229impl std::fmt::Display for WebAssetResolveError {
230 #[allow(unreachable_code)]
231 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
232 #[cfg(feature = "web")]
233 return write!(f, "{}", self.error.message());
234 write!(f, "WebAssetResolveError")
235 }
236}
237
238impl std::error::Error for WebAssetResolveError {}