1#![allow(missing_docs)]
2
3#[cfg(engine)]
4use crate::i18n::TranslationsManagerError;
5use thiserror::Error;
6
7#[derive(Error, Debug)]
9pub enum Error {
10 #[error(transparent)]
11 ClientError(#[from] ClientError),
12 #[cfg(engine)]
13 #[error(transparent)]
14 ServerError(#[from] ServerError),
15 #[cfg(engine)]
16 #[error(transparent)]
17 EngineError(#[from] EngineError),
18 #[error(transparent)]
20 PluginError(#[from] PluginError),
21}
22
23#[derive(Error, Debug)]
24#[error("plugin '{name}' returned an error (this is unlikely to be Perseus' fault)")]
25pub struct PluginError {
26 pub name: String,
27 #[source]
28 pub source: Box<dyn std::error::Error + Send + Sync>,
29}
30
31#[cfg(engine)]
34#[derive(Error, Debug)]
35pub enum EngineError {
36 #[error(transparent)]
38 ServerError(#[from] ServerError),
39 #[error("couldn't copy static directory at '{path}' to '{dest}'")]
40 CopyStaticDirError {
41 #[source]
42 source: fs_extra::error::Error,
43 path: String,
44 dest: String,
45 },
46 #[error("couldn't copy static alias file from '{from}' to '{to}'")]
47 CopyStaticAliasFileError {
48 #[source]
49 source: std::io::Error,
50 from: String,
51 to: String,
52 },
53 #[error("couldn't copy static alias directory from '{from}' to '{to}'")]
54 CopyStaticAliasDirErr {
55 #[source]
56 source: fs_extra::error::Error,
57 from: String,
58 to: String,
59 },
60 #[error("couldn't write the generated error page to '{dest}'")]
61 WriteErrorPageError {
62 #[source]
63 source: std::io::Error,
64 dest: String,
65 },
66 #[error("couldn't create the parent directories needed for the nested static alias '{alias}'")]
67 NestedStaticAliasDirCreationFailed {
68 #[source]
69 source: std::io::Error,
70 alias: String,
71 },
72}
73
74#[derive(Error, Debug)]
109pub enum ClientError {
110 #[error("{0}")] Panic(String),
112 #[error(transparent)]
113 PluginError(#[from] PluginError),
114 #[error(transparent)]
115 InvariantError(#[from] ClientInvariantError),
116 #[error(transparent)]
117 ThawError(#[from] ClientThawError),
118 #[error("an error with HTTP status code '{status}' was returned by the server: '{message}'")]
120 ServerError {
121 status: u16,
122 message: String,
124 },
125 #[error(transparent)]
126 FetchError(#[from] FetchError),
127 #[error(transparent)]
128 PlatformError(#[from] ClientPlatformError),
129 #[error(transparent)]
130 PreloadError(#[from] ClientPreloadError), }
149
150#[derive(Debug, Error)]
157pub enum ClientInvariantError {
158 #[error("the render configuration was not found, or was malformed")]
159 RenderCfg,
160 #[error("the global state was not found, or was malformed (even apps not using global state should have an empty one injected)")]
161 GlobalState,
162 #[error("attempted to register state on a page/capsule that had been previously declared as having no state")]
164 IllegalStateRegistration,
165 #[error(
166 "attempted to downcast reactive global state to the incorrect type (this is an error)"
167 )]
168 GlobalStateDowncast,
169 #[error("invalid page/widget state found")]
172 InvalidState {
173 #[source]
174 source: serde_json::Error,
175 },
176 #[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`)")]
179 NoState,
180 #[error("the initial state was not found, or was malformed")]
181 InitialState,
182 #[error("the initial state denoted an error, but this was malformed")]
183 InitialStateError {
184 #[source]
185 source: serde_json::Error,
186 },
187 #[error(
188 "the locale '{locale}', which is supported by this app, was not returned by the server"
189 )]
190 ValidLocaleNotProvided { locale: String },
191 #[error("the translations were not found, or were malformed (even apps not using i18n have a declaration of their lack of translations)")]
193 Translations,
194 #[error("we found the current page to be a 404, but the engine disagrees")]
195 RouterMismatch,
196 #[error("the widget states were not found, or were malformed (even pages not using widgets still have a declaration of these)")]
197 WidgetStates,
198 #[error("a widget was registered in the state store with only a head (but widgets do not have heads), implying a corruption")]
199 InvalidWidgetPssEntry,
200 #[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)")]
201 BadWidgetRouteMatch { path: String },
202}
203
204#[derive(Debug, Error)]
209pub enum ClientPreloadError {
210 #[error("preloading '{path}' leads to a locale detection page, which implies a malformed url")]
211 PreloadLocaleDetection { path: String },
212 #[error("'{path}' was not found for preload")]
213 PreloadNotFound { path: String },
214}
215
216#[derive(Debug, Error)]
221pub enum ClientPlatformError {
222 #[error("failed to get current url for initial load determination")]
223 InitialPath,
224}
225
226#[derive(Debug, Error)]
229pub enum ClientThawError {
230 #[error("invalid frozen page/widget state")]
231 InvalidFrozenState {
232 #[source]
233 source: serde_json::Error,
234 },
235 #[error("invalid frozen global state")]
236 InvalidFrozenGlobalState {
237 #[source]
238 source: serde_json::Error,
239 },
240 #[error("this app uses global state, but the provided frozen state declared itself to have no global state")]
241 NoFrozenGlobalState,
242 #[error("invalid frozen app provided (this is likely a corruption)")]
243 InvalidFrozenApp {
244 #[source]
245 source: serde_json::Error,
246 },
247}
248
249#[cfg(engine)]
251#[derive(Error, Debug)]
252pub enum ServerError {
253 #[error("render function '{fn_name}' in template '{template_name}' failed (cause: {blame:?})")]
254 RenderFnFailed {
255 fn_name: String,
257 template_name: String,
258 blame: ErrorBlame,
259 #[source]
262 source: Box<dyn std::error::Error + Send + Sync>,
263 },
264 #[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)")]
267 MinifyError {
268 #[source]
269 source: std::io::Error,
270 },
271 #[error("failed to decode url provided (probably malformed request)")]
272 UrlDecodeFailed {
273 #[source]
274 source: std::string::FromUtf8Error,
275 },
276 #[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)")]
277 MissingBuildExtra { template_name: String },
278 #[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)")]
279 InvalidBuildExtra {
280 template_name: String,
281 #[source]
282 source: serde_json::Error,
283 },
284 #[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)")]
285 InvalidPageState {
286 #[source]
287 source: serde_json::Error,
288 },
289
290 #[error("attempting to resolve dependency '{widget}' in locale '{locale}' produced a locale redirection verdict (this shouldn't be possible)")]
292 ResolveDepLocaleRedirection { widget: String, locale: String },
293 #[error("attempting to resolve dependency '{widget}' in locale '{locale}' produced a not found verdict (did you mistype the widget path?)")]
294 ResolveDepNotFound { widget: String, locale: String },
295
296 #[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")]
297 TemplateCannotBeRescheduled { template_name: String },
298 #[error("a dependency tree was not resolved, but a function expecting it to have been was called (this is a server-side error)")]
300 DepTreeNotResolved,
301 #[error("the template name did not prefix the path (this request was severely malformed)")]
302 TemplateNameNotInPath,
303
304 #[error(transparent)]
305 StoreError(#[from] StoreError),
306 #[error(transparent)]
307 TranslationsManagerError(#[from] TranslationsManagerError),
308 #[error(transparent)]
309 BuildError(#[from] BuildError),
310 #[error(transparent)]
311 ExportError(#[from] ExportError),
312 #[error(transparent)]
313 ServeError(#[from] ServeError),
314 #[error(transparent)]
315 PluginError(#[from] PluginError),
316 #[error(transparent)]
318 ClientError(#[from] ClientError),
319}
320#[cfg(engine)]
322pub fn err_to_status_code(err: &ServerError) -> u16 {
323 match err {
324 ServerError::ServeError(ServeError::PageNotFound { .. }) => 404,
325 ServerError::RenderFnFailed { blame, .. } => match blame {
327 ErrorBlame::Client(code) => code.unwrap_or(400),
328 ErrorBlame::Server(code) => code.unwrap_or(500),
329 },
330 _ => 500,
332 }
333}
334
335#[derive(Error, Debug)]
339pub enum StoreError {
340 #[error("asset '{name}' not found in store")]
341 NotFound { name: String },
342 #[error("asset '{name}' couldn't be read from store")]
343 ReadFailed {
344 name: String,
345 #[source]
346 source: Box<dyn std::error::Error + Send + Sync>,
347 },
348 #[error("asset '{name}' couldn't be written to store")]
349 WriteFailed {
350 name: String,
351 #[source]
352 source: Box<dyn std::error::Error + Send + Sync>,
353 },
354}
355
356#[derive(Error, Debug)]
358pub enum FetchError {
359 #[error("asset of type '{ty}' fetched from '{url}' wasn't a string")]
360 NotString { url: String, ty: AssetType },
361 #[error(
362 "asset of type '{ty}' fetched from '{url}' returned status code '{status}' (expected 200)"
363 )]
364 NotOk {
365 url: String,
366 status: u16,
367 err: String,
369 ty: AssetType,
370 },
371 #[error("asset of type '{ty}' fetched from '{url}' couldn't be serialized")]
372 SerFailed {
373 url: String,
374 #[source]
375 source: Box<dyn std::error::Error + Send + Sync>,
376 ty: AssetType,
377 },
378 #[error("the following error occurred while interfacing with JavaScript: {0}")]
380 Js(String),
381}
382
383#[derive(Debug, Clone, Copy)]
387pub enum AssetType {
388 Page,
390 Widget,
392 Translations,
394 Preload,
396}
397impl std::fmt::Display for AssetType {
398 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
399 write!(f, "{:?}", self)
400 }
401}
402
403#[cfg(engine)]
405#[derive(Error, Debug)]
406pub enum BuildError {
407 #[error("template '{template_name}' is missing feature '{feature_name}' (required due to its properties)")]
408 TemplateFeatureNotEnabled {
409 template_name: String,
410 feature_name: String,
411 },
412 #[error("html shell couldn't be found at '{path}'")]
413 HtmlShellNotFound {
414 path: String,
415 #[source]
416 source: std::io::Error,
417 },
418 #[error(
419 "invalid indicator '{indicator}' in time string (must be one of: s, m, h, d, w, M, y)"
420 )]
421 InvalidDatetimeIntervalIndicator { indicator: String },
422 #[error("asset 'render_cfg.json' invalid or corrupted (try cleaning all assets)")]
423 RenderCfgInvalid {
424 #[source]
425 source: serde_json::Error,
426 },
427}
428
429#[cfg(engine)]
431#[derive(Error, Debug)]
432pub enum ExportError {
433 #[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)")]
434 TemplateNotExportable { template_name: String },
435 #[error("template '{template_name}' wasn't found in built artifacts (run `perseus clean --dist` if this persists)")]
436 TemplateNotFound { template_name: String },
437 #[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)")]
438 GlobalStateNotExportable,
439 #[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")]
440 DependenciesNotExportable { template_name: String },
441 #[error("invalid status code provided for error page export (please provide a valid http status code)")]
443 InvalidStatusCode,
444}
445
446#[derive(Error, Debug)]
448pub enum ServeError {
449 #[error("page/widget at '{path}' not found")]
450 PageNotFound { path: String },
451 #[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?)")]
452 BothStatesDefined,
453 #[cfg(engine)]
454 #[error("couldn't parse revalidation datetime (try cleaning all assets)")]
455 BadRevalidate {
456 #[source]
457 source: chrono::ParseError,
458 },
459}
460
461#[derive(Debug)]
467pub enum ErrorBlame {
468 Client(Option<u16>),
469 Server(Option<u16>),
470}
471impl Default for ErrorBlame {
472 fn default() -> Self {
473 Self::Server(None)
474 }
475}
476
477#[cfg(engine)]
486#[derive(Debug)]
487pub struct BlamedError<E: Send + Sync> {
488 pub error: E,
490 pub blame: ErrorBlame,
492}
493#[cfg(engine)]
494impl<E: Into<Box<dyn std::error::Error + Send + Sync + 'static>> + Send + Sync> BlamedError<E> {
495 pub(crate) fn into_boxed(self) -> GenericBlamedError {
498 BlamedError {
499 error: self.error.into(),
500 blame: self.blame,
501 }
502 }
503}
504#[cfg(engine)]
507impl<E: Into<Box<dyn std::error::Error + Send + Sync + 'static>> + Send + Sync> From<E>
508 for BlamedError<E>
509{
510 fn from(error: E) -> Self {
511 Self {
512 error,
513 blame: ErrorBlame::default(),
514 }
515 }
516}
517
518#[cfg(engine)]
520pub(crate) type GenericBlamedError = BlamedError<Box<dyn std::error::Error + Send + Sync>>;