use oxigdal_core::error::OxiGdalError;
use std::fmt;
use wasm_bindgen::prelude::*;
pub type WasmResult<T> = std::result::Result<T, WasmError>;
#[derive(Debug, Clone)]
pub enum WasmError {
Fetch(FetchError),
Canvas(CanvasError),
Worker(WorkerError),
TileCache(TileCacheError),
JsInterop(JsInteropError),
OxiGdal(String),
InvalidOperation {
operation: String,
reason: String,
},
NotFound {
resource: String,
identifier: String,
},
OutOfMemory {
requested: usize,
available: Option<usize>,
},
Timeout {
operation: String,
duration_ms: u64,
},
Format {
expected: String,
actual: String,
},
}
#[derive(Debug, Clone)]
pub enum FetchError {
NetworkFailure {
url: String,
message: String,
},
HttpError {
status: u16,
status_text: String,
url: String,
},
CorsError {
url: String,
details: String,
},
RangeNotSupported {
url: String,
},
ParseError {
expected: String,
message: String,
},
Timeout {
url: String,
timeout_ms: u64,
},
RetryLimitExceeded {
url: String,
attempts: u32,
},
InvalidSize {
expected: u64,
actual: u64,
},
}
#[derive(Debug, Clone)]
pub enum CanvasError {
ImageDataCreation {
width: u32,
height: u32,
message: String,
},
InvalidDimensions {
width: u32,
height: u32,
reason: String,
},
ColorSpaceConversion {
from: String,
to: String,
details: String,
},
BufferSizeMismatch {
expected: usize,
actual: usize,
},
ContextUnavailable {
context_type: String,
},
RenderingFailed {
operation: String,
message: String,
},
InvalidParameter(String),
}
#[derive(Debug, Clone)]
pub enum WorkerError {
CreationFailed {
message: String,
},
Terminated {
worker_id: u32,
},
PostMessageFailed {
worker_id: u32,
message: String,
},
PoolExhausted {
pool_size: usize,
pending_jobs: usize,
},
ResponseTimeout {
worker_id: u32,
job_id: u64,
timeout_ms: u64,
},
InvalidResponse {
expected: String,
actual: String,
},
}
#[derive(Debug, Clone)]
pub enum TileCacheError {
Miss {
key: String,
},
Full {
current_size: usize,
max_size: usize,
},
InvalidCoordinates {
level: u32,
x: u32,
y: u32,
reason: String,
},
SizeMismatch {
expected: usize,
actual: usize,
},
EvictionFailed {
message: String,
},
}
#[derive(Debug, Clone)]
pub enum JsInteropError {
TypeConversion {
expected: String,
actual: String,
},
PropertyAccess {
property: String,
message: String,
},
FunctionCall {
function: String,
message: String,
},
PromiseRejection {
promise: String,
reason: String,
},
InvalidJsValue {
expected: String,
details: String,
},
}
impl fmt::Display for WasmError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Fetch(e) => write!(f, "Fetch error: {e}"),
Self::Canvas(e) => write!(f, "Canvas error: {e}"),
Self::Worker(e) => write!(f, "Worker error: {e}"),
Self::TileCache(e) => write!(f, "Tile cache error: {e}"),
Self::JsInterop(e) => write!(f, "JS interop error: {e}"),
Self::OxiGdal(msg) => write!(f, "OxiGDAL error: {msg}"),
Self::InvalidOperation { operation, reason } => {
write!(f, "Invalid operation '{operation}': {reason}")
}
Self::NotFound {
resource,
identifier,
} => {
write!(f, "{resource} not found: {identifier}")
}
Self::OutOfMemory {
requested,
available,
} => {
if let Some(avail) = available {
write!(
f,
"Out of memory: requested {requested} bytes, {avail} available"
)
} else {
write!(f, "Out of memory: requested {requested} bytes")
}
}
Self::Timeout {
operation,
duration_ms,
} => {
write!(f, "Operation '{operation}' timed out after {duration_ms}ms")
}
Self::Format { expected, actual } => {
write!(f, "Format error: expected {expected}, got {actual}")
}
}
}
}
impl fmt::Display for FetchError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NetworkFailure { url, message } => {
write!(f, "Network failure for {url}: {message}")
}
Self::HttpError {
status,
status_text,
url,
} => {
write!(f, "HTTP {status} {status_text} for {url}")
}
Self::CorsError { url, details } => {
write!(f, "CORS error for {url}: {details}")
}
Self::RangeNotSupported { url } => {
write!(f, "Range requests not supported for {url}")
}
Self::ParseError { expected, message } => {
write!(f, "Parse error: expected {expected}, {message}")
}
Self::Timeout { url, timeout_ms } => {
write!(f, "Request to {url} timed out after {timeout_ms}ms")
}
Self::RetryLimitExceeded { url, attempts } => {
write!(
f,
"Retry limit exceeded for {url} after {attempts} attempts"
)
}
Self::InvalidSize { expected, actual } => {
write!(
f,
"Invalid response size: expected {expected}, got {actual}"
)
}
}
}
}
impl fmt::Display for CanvasError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ImageDataCreation {
width,
height,
message,
} => {
write!(f, "Failed to create ImageData {width}x{height}: {message}")
}
Self::InvalidDimensions {
width,
height,
reason,
} => {
write!(f, "Invalid dimensions {width}x{height}: {reason}")
}
Self::ColorSpaceConversion { from, to, details } => {
write!(f, "Color space conversion {from} -> {to} failed: {details}")
}
Self::BufferSizeMismatch { expected, actual } => {
write!(f, "Buffer size mismatch: expected {expected}, got {actual}")
}
Self::ContextUnavailable { context_type } => {
write!(f, "Canvas context '{context_type}' unavailable")
}
Self::RenderingFailed { operation, message } => {
write!(f, "Rendering operation '{operation}' failed: {message}")
}
Self::InvalidParameter(msg) => {
write!(f, "Invalid parameter: {msg}")
}
}
}
}
impl fmt::Display for WorkerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CreationFailed { message } => {
write!(f, "Worker creation failed: {message}")
}
Self::Terminated { worker_id } => {
write!(f, "Worker {worker_id} terminated unexpectedly")
}
Self::PostMessageFailed { worker_id, message } => {
write!(f, "Failed to post message to worker {worker_id}: {message}")
}
Self::PoolExhausted {
pool_size,
pending_jobs,
} => {
write!(
f,
"Worker pool exhausted: {pool_size} workers, {pending_jobs} pending jobs"
)
}
Self::ResponseTimeout {
worker_id,
job_id,
timeout_ms,
} => {
write!(
f,
"Worker {worker_id} job {job_id} timed out after {timeout_ms}ms"
)
}
Self::InvalidResponse { expected, actual } => {
write!(
f,
"Invalid worker response: expected {expected}, got {actual}"
)
}
}
}
}
impl fmt::Display for TileCacheError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Miss { key } => {
write!(f, "Cache miss for tile {key}")
}
Self::Full {
current_size,
max_size,
} => {
write!(f, "Cache full: {current_size}/{max_size} bytes")
}
Self::InvalidCoordinates {
level,
x,
y,
reason,
} => {
write!(f, "Invalid tile coordinates ({level}, {x}, {y}): {reason}")
}
Self::SizeMismatch { expected, actual } => {
write!(f, "Tile size mismatch: expected {expected}, got {actual}")
}
Self::EvictionFailed { message } => {
write!(f, "Cache eviction failed: {message}")
}
}
}
}
impl fmt::Display for JsInteropError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TypeConversion { expected, actual } => {
write!(
f,
"Type conversion failed: expected {expected}, got {actual}"
)
}
Self::PropertyAccess { property, message } => {
write!(f, "Property access failed for '{property}': {message}")
}
Self::FunctionCall { function, message } => {
write!(f, "Function call failed for '{function}': {message}")
}
Self::PromiseRejection { promise, reason } => {
write!(f, "Promise rejected for '{promise}': {reason}")
}
Self::InvalidJsValue { expected, details } => {
write!(f, "Invalid JsValue: expected {expected}, {details}")
}
}
}
}
impl std::error::Error for WasmError {}
impl std::error::Error for FetchError {}
impl std::error::Error for CanvasError {}
impl std::error::Error for WorkerError {}
impl std::error::Error for TileCacheError {}
impl std::error::Error for JsInteropError {}
impl From<WasmError> for JsValue {
fn from(err: WasmError) -> Self {
JsValue::from_str(&err.to_string())
}
}
impl From<OxiGdalError> for WasmError {
fn from(err: OxiGdalError) -> Self {
Self::OxiGdal(err.to_string())
}
}
impl From<FetchError> for WasmError {
fn from(err: FetchError) -> Self {
Self::Fetch(err)
}
}
impl From<CanvasError> for WasmError {
fn from(err: CanvasError) -> Self {
Self::Canvas(err)
}
}
impl From<WorkerError> for WasmError {
fn from(err: WorkerError) -> Self {
Self::Worker(err)
}
}
impl From<TileCacheError> for WasmError {
fn from(err: TileCacheError) -> Self {
Self::TileCache(err)
}
}
impl From<JsInteropError> for WasmError {
fn from(err: JsInteropError) -> Self {
Self::JsInterop(err)
}
}
#[allow(dead_code)]
pub fn js_to_wasm_error(js_val: JsValue, context: &str) -> WasmError {
let message = if let Some(s) = js_val.as_string() {
s
} else {
format!("{js_val:?}")
};
WasmError::JsInterop(JsInteropError::FunctionCall {
function: context.to_string(),
message,
})
}
#[allow(dead_code)]
pub fn to_js_value<E: std::fmt::Display>(err: E) -> JsValue {
JsValue::from_str(&err.to_string())
}
#[allow(dead_code)]
pub struct WasmErrorBuilder;
#[allow(dead_code)]
impl WasmErrorBuilder {
pub fn fetch_network(url: impl Into<String>, message: impl Into<String>) -> WasmError {
WasmError::Fetch(FetchError::NetworkFailure {
url: url.into(),
message: message.into(),
})
}
pub fn fetch_http(
status: u16,
status_text: impl Into<String>,
url: impl Into<String>,
) -> WasmError {
WasmError::Fetch(FetchError::HttpError {
status,
status_text: status_text.into(),
url: url.into(),
})
}
pub fn canvas_image_data(width: u32, height: u32, message: impl Into<String>) -> WasmError {
WasmError::Canvas(CanvasError::ImageDataCreation {
width,
height,
message: message.into(),
})
}
pub fn worker_creation(message: impl Into<String>) -> WasmError {
WasmError::Worker(WorkerError::CreationFailed {
message: message.into(),
})
}
pub fn cache_miss(key: impl Into<String>) -> WasmError {
WasmError::TileCache(TileCacheError::Miss { key: key.into() })
}
pub fn invalid_op(operation: impl Into<String>, reason: impl Into<String>) -> WasmError {
WasmError::InvalidOperation {
operation: operation.into(),
reason: reason.into(),
}
}
pub fn not_found(resource: impl Into<String>, identifier: impl Into<String>) -> WasmError {
WasmError::NotFound {
resource: resource.into(),
identifier: identifier.into(),
}
}
pub fn out_of_memory(requested: usize, available: Option<usize>) -> WasmError {
WasmError::OutOfMemory {
requested,
available,
}
}
pub fn timeout(operation: impl Into<String>, duration_ms: u64) -> WasmError {
WasmError::Timeout {
operation: operation.into(),
duration_ms,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = WasmErrorBuilder::fetch_network("https://example.com", "Connection refused");
assert!(err.to_string().contains("Network failure"));
assert!(err.to_string().contains("example.com"));
}
#[test]
fn test_error_conversion() {
let fetch_err = FetchError::HttpError {
status: 404,
status_text: "Not Found".to_string(),
url: "https://example.com".to_string(),
};
let wasm_err: WasmError = fetch_err.into();
assert!(matches!(wasm_err, WasmError::Fetch(_)));
}
#[test]
#[cfg(target_arch = "wasm32")]
fn test_js_value_conversion() {
let err = WasmErrorBuilder::invalid_op("test", "invalid");
let js_val: JsValue = err.into();
assert!(js_val.is_string());
}
#[test]
fn test_error_builder() {
let err = WasmErrorBuilder::out_of_memory(1024, Some(512));
assert!(matches!(err, WasmError::OutOfMemory { .. }));
assert!(err.to_string().contains("1024"));
}
}