#![allow(missing_docs)]
#[cfg(engine)]
use crate::i18n::TranslationsManagerError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
ClientError(#[from] ClientError),
#[cfg(engine)]
#[error(transparent)]
ServerError(#[from] ServerError),
#[cfg(engine)]
#[error(transparent)]
EngineError(#[from] EngineError),
#[error(transparent)]
PluginError(#[from] PluginError),
}
#[derive(Error, Debug)]
#[error("plugin '{name}' returned an error (this is unlikely to be Perseus' fault)")]
pub struct PluginError {
pub name: String,
#[source]
pub source: Box<dyn std::error::Error + Send + Sync>,
}
#[cfg(engine)]
#[derive(Error, Debug)]
pub enum EngineError {
#[error(transparent)]
ServerError(#[from] ServerError),
#[error("couldn't copy static directory at '{path}' to '{dest}'")]
CopyStaticDirError {
#[source]
source: fs_extra::error::Error,
path: String,
dest: String,
},
#[error("couldn't copy static alias file from '{from}' to '{to}'")]
CopyStaticAliasFileError {
#[source]
source: std::io::Error,
from: String,
to: String,
},
#[error("couldn't copy static alias directory from '{from}' to '{to}'")]
CopyStaticAliasDirErr {
#[source]
source: fs_extra::error::Error,
from: String,
to: String,
},
#[error("couldn't write the generated error page to '{dest}'")]
WriteErrorPageError {
#[source]
source: std::io::Error,
dest: String,
},
#[error("couldn't create the parent directories needed for the nested static alias '{alias}'")]
NestedStaticAliasDirCreationFailed {
#[source]
source: std::io::Error,
alias: String,
},
}
#[derive(Error, Debug)]
pub enum ClientError {
#[error("{0}")] Panic(String),
#[error(transparent)]
PluginError(#[from] PluginError),
#[error(transparent)]
InvariantError(#[from] ClientInvariantError),
#[error(transparent)]
ThawError(#[from] ClientThawError),
#[error("an error with HTTP status code '{status}' was returned by the server: '{message}'")]
ServerError {
status: u16,
message: String,
},
#[error(transparent)]
FetchError(#[from] FetchError),
#[error(transparent)]
PlatformError(#[from] ClientPlatformError),
#[error(transparent)]
PreloadError(#[from] ClientPreloadError), }
#[derive(Debug, Error)]
pub enum ClientInvariantError {
#[error("the render configuration was not found, or was malformed")]
RenderCfg,
#[error("the global state was not found, or was malformed (even apps not using global state should have an empty one injected)")]
GlobalState,
#[error("attempted to register state on a page/capsule that had been previously declared as having no state")]
IllegalStateRegistration,
#[error(
"attempted to downcast reactive global state to the incorrect type (this is an error)"
)]
GlobalStateDowncast,
#[error("invalid page/widget state found")]
InvalidState {
#[source]
source: serde_json::Error,
},
#[error("no state was found for a page/widget that expected state (you might have forgotten to write a state generation function, like `get_build_state`)")]
NoState,
#[error("the initial state was not found, or was malformed")]
InitialState,
#[error("the initial state denoted an error, but this was malformed")]
InitialStateError {
#[source]
source: serde_json::Error,
},
#[error(
"the locale '{locale}', which is supported by this app, was not returned by the server"
)]
ValidLocaleNotProvided { locale: String },
#[error("the translations were not found, or were malformed (even apps not using i18n have a declaration of their lack of translations)")]
Translations,
#[error("we found the current page to be a 404, but the engine disagrees")]
RouterMismatch,
#[error("the widget states were not found, or were malformed (even pages not using widgets still have a declaration of these)")]
WidgetStates,
#[error("a widget was registered in the state store with only a head (but widgets do not have heads), implying a corruption")]
InvalidWidgetPssEntry,
#[error("the widget with path '{path}' was not found, indicating you are rendering an invalid widget on the browser-side only (you should refactor to always render the widget, but only have it do anything on the browser-side; that way, it can be verified on the engine-side, leading to errors at build-time rather than execution-time)")]
BadWidgetRouteMatch { path: String },
}
#[derive(Debug, Error)]
pub enum ClientPreloadError {
#[error("preloading '{path}' leads to a locale detection page, which implies a malformed url")]
PreloadLocaleDetection { path: String },
#[error("'{path}' was not found for preload")]
PreloadNotFound { path: String },
}
#[derive(Debug, Error)]
pub enum ClientPlatformError {
#[error("failed to get current url for initial load determination")]
InitialPath,
}
#[derive(Debug, Error)]
pub enum ClientThawError {
#[error("invalid frozen page/widget state")]
InvalidFrozenState {
#[source]
source: serde_json::Error,
},
#[error("invalid frozen global state")]
InvalidFrozenGlobalState {
#[source]
source: serde_json::Error,
},
#[error("this app uses global state, but the provided frozen state declared itself to have no global state")]
NoFrozenGlobalState,
#[error("invalid frozen app provided (this is likely a corruption)")]
InvalidFrozenApp {
#[source]
source: serde_json::Error,
},
}
#[cfg(engine)]
#[derive(Error, Debug)]
pub enum ServerError {
#[error("render function '{fn_name}' in template '{template_name}' failed (cause: {blame:?})")]
RenderFnFailed {
fn_name: String,
template_name: String,
blame: ErrorBlame,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("failed to minify html (you can disable the `minify` flag to avoid this; this is very likely a Sycamore bug, unless you've provided invalid custom HTML)")]
MinifyError {
#[source]
source: std::io::Error,
},
#[error("failed to decode url provided (probably malformed request)")]
UrlDecodeFailed {
#[source]
source: std::string::FromUtf8Error,
},
#[error("the template '{template_name}' had no helper build state written to the immutable store (the store has been tampered with, and the app must be rebuilt)")]
MissingBuildExtra { template_name: String },
#[error("the template '{template_name}' had invalid helper build state written to the immutable store (the store has been tampered with, and the app must be rebuilt)")]
InvalidBuildExtra {
template_name: String,
#[source]
source: serde_json::Error,
},
#[error("page state was encountered that could not be deserialized into serde_json::Value (the store has been tampered with, and the app must be rebuilt)")]
InvalidPageState {
#[source]
source: serde_json::Error,
},
#[error("attempting to resolve dependency '{widget}' in locale '{locale}' produced a locale redirection verdict (this shouldn't be possible)")]
ResolveDepLocaleRedirection { widget: String, locale: String },
#[error("attempting to resolve dependency '{widget}' in locale '{locale}' produced a not found verdict (did you mistype the widget path?)")]
ResolveDepNotFound { widget: String, locale: String },
#[error("template '{template_name}' cannot be built at build-time due to one or more of its dependencies having state that may change later; to allow this template to be built later, add `.allow_rescheduling()` to your template definition")]
TemplateCannotBeRescheduled { template_name: String },
#[error("a dependency tree was not resolved, but a function expecting it to have been was called (this is a server-side error)")]
DepTreeNotResolved,
#[error("the template name did not prefix the path (this request was severely malformed)")]
TemplateNameNotInPath,
#[error(transparent)]
StoreError(#[from] StoreError),
#[error(transparent)]
TranslationsManagerError(#[from] TranslationsManagerError),
#[error(transparent)]
BuildError(#[from] BuildError),
#[error(transparent)]
ExportError(#[from] ExportError),
#[error(transparent)]
ServeError(#[from] ServeError),
#[error(transparent)]
PluginError(#[from] PluginError),
#[error(transparent)]
ClientError(#[from] ClientError),
}
#[cfg(engine)]
pub fn err_to_status_code(err: &ServerError) -> u16 {
match err {
ServerError::ServeError(ServeError::PageNotFound { .. }) => 404,
ServerError::RenderFnFailed { blame, .. } => match blame {
ErrorBlame::Client(code) => code.unwrap_or(400),
ErrorBlame::Server(code) => code.unwrap_or(500),
},
_ => 500,
}
}
#[derive(Error, Debug)]
pub enum StoreError {
#[error("asset '{name}' not found in store")]
NotFound { name: String },
#[error("asset '{name}' couldn't be read from store")]
ReadFailed {
name: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("asset '{name}' couldn't be written to store")]
WriteFailed {
name: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
}
#[derive(Error, Debug)]
pub enum FetchError {
#[error("asset of type '{ty}' fetched from '{url}' wasn't a string")]
NotString { url: String, ty: AssetType },
#[error(
"asset of type '{ty}' fetched from '{url}' returned status code '{status}' (expected 200)"
)]
NotOk {
url: String,
status: u16,
err: String,
ty: AssetType,
},
#[error("asset of type '{ty}' fetched from '{url}' couldn't be serialized")]
SerFailed {
url: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
ty: AssetType,
},
#[error("the following error occurred while interfacing with JavaScript: {0}")]
Js(String),
}
#[derive(Debug, Clone, Copy)]
pub enum AssetType {
Page,
Widget,
Translations,
Preload,
}
impl std::fmt::Display for AssetType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[cfg(engine)]
#[derive(Error, Debug)]
pub enum BuildError {
#[error("template '{template_name}' is missing feature '{feature_name}' (required due to its properties)")]
TemplateFeatureNotEnabled {
template_name: String,
feature_name: String,
},
#[error("html shell couldn't be found at '{path}'")]
HtmlShellNotFound {
path: String,
#[source]
source: std::io::Error,
},
#[error(
"invalid indicator '{indicator}' in time string (must be one of: s, m, h, d, w, M, y)"
)]
InvalidDatetimeIntervalIndicator { indicator: String },
#[error("asset 'render_cfg.json' invalid or corrupted (try cleaning all assets)")]
RenderCfgInvalid {
#[source]
source: serde_json::Error,
},
}
#[cfg(engine)]
#[derive(Error, Debug)]
pub enum ExportError {
#[error("template '{template_name}' can't be exported because it depends on strategies that can't be run at build-time (only build state and build paths can be used in exportable templates)")]
TemplateNotExportable { template_name: String },
#[error("template '{template_name}' wasn't found in built artifacts (run `perseus clean --dist` if this persists)")]
TemplateNotFound { template_name: String },
#[error("your app can't be exported because its global state depends on strategies that can't be run at build time (only build state can be used in exportable apps)")]
GlobalStateNotExportable,
#[error("template '{template_name} can't be exported because one or more of its widget dependencies use state generation strategies that can't be run at build-time")]
DependenciesNotExportable { template_name: String },
#[error("invalid status code provided for error page export (please provide a valid http status code)")]
InvalidStatusCode,
}
#[derive(Error, Debug)]
pub enum ServeError {
#[error("page/widget at '{path}' not found")]
PageNotFound { path: String },
#[error("both build and request states were defined for a template when only one or fewer were expected (should it be able to amalgamate states?)")]
BothStatesDefined,
#[cfg(engine)]
#[error("couldn't parse revalidation datetime (try cleaning all assets)")]
BadRevalidate {
#[source]
source: chrono::ParseError,
},
}
#[derive(Debug)]
pub enum ErrorBlame {
Client(Option<u16>),
Server(Option<u16>),
}
impl Default for ErrorBlame {
fn default() -> Self {
Self::Server(None)
}
}
#[cfg(engine)]
#[derive(Debug)]
pub struct BlamedError<E: Send + Sync> {
pub error: E,
pub blame: ErrorBlame,
}
#[cfg(engine)]
impl<E: Into<Box<dyn std::error::Error + Send + Sync + 'static>> + Send + Sync> BlamedError<E> {
pub(crate) fn into_boxed(self) -> GenericBlamedError {
BlamedError {
error: self.error.into(),
blame: self.blame,
}
}
}
#[cfg(engine)]
impl<E: Into<Box<dyn std::error::Error + Send + Sync + 'static>> + Send + Sync> From<E>
for BlamedError<E>
{
fn from(error: E) -> Self {
Self {
error,
blame: ErrorBlame::default(),
}
}
}
#[cfg(engine)]
pub(crate) type GenericBlamedError = BlamedError<Box<dyn std::error::Error + Send + Sync>>;