diff --git a/examples/basic/.perseus/src/lib.rs b/examples/basic/.perseus/src/lib.rs index 1b713c9db5..9654d69689 100644 --- a/examples/basic/.perseus/src/lib.rs +++ b/examples/basic/.perseus/src/lib.rs @@ -10,7 +10,7 @@ use perseus::{ shell::{app_shell, get_initial_state, get_render_cfg, InitialState, ShellProps}, }, plugins::PluginAction, - state::{AnyFreeze, FrozenApp, PageStateStore}, + state::{FrozenApp, GlobalState, PageStateStore}, templates::{RouterState, TemplateNodeType, ThawPrefs}, DomNode, }; @@ -68,19 +68,9 @@ pub fn run() -> Result<(), JsValue> { // Create a page state store to use let pss = PageStateStore::default(); // Create a new global state set to `None`, which will be updated and handled entirely by the template macro from here on - let global_state: Rc>> = - Rc::new(RefCell::new(Box::new(Option::<()>::None))); + let global_state = GlobalState::default(); - // TODO Try to fetch a previous frozen app - // let frozen_app: Option> = Some(Rc::new(FrozenApp { - // global_state: r#"{"test":"Hello from the frozen app!"}"#.to_string(), - // route: "".to_string(), - // page_state_store: { - // let mut map = std::collections::HashMap::new(); - // map.insert("".to_string(), r#"{"username":"Sam"}"#.to_string()); - // map - // }, - // })); + // Instantiate an empty frozen app that can persist across templates (with interior mutability for possible thawing) let frozen_app: Rc>> = Rc::new(RefCell::new(None)); // Create the router we'll use for this app, based on the user's app definition diff --git a/packages/perseus-macro/src/template_rx.rs b/packages/perseus-macro/src/template_rx.rs index 3413fa8630..6e453a58c9 100644 --- a/packages/perseus-macro/src/template_rx.rs +++ b/packages/perseus-macro/src/template_rx.rs @@ -175,53 +175,13 @@ pub fn template_impl(input: TemplateFn, attr_args: AttributeArgs) -> TokenStream render_ctx.register_global_state_str::<#global_state_rx>(&props.global_state.unwrap()).unwrap(); } - // // Deserialize the global state, make it reactive, and register it with the `RenderCtx` - // // If it's already there, we'll leave it - // // This means that we can pass an `Option` around safely and then deal with it at the template site (here) - // let frozen_app = render_ctx.frozen_app; - // let global_state_refcell = render_ctx.global_state; - // let global_state = global_state_refcell.borrow(); - // // This will work if the global state hasn't been initialized yet, because it's the default value that Perseus sets - // if global_state.as_any().downcast_ref::<::std::option::Option::<()>>().is_some() { - // // We can downcast it as the type set by the core render system, so we're the first page to be loaded - // // In that case, we'll set the global state properly - // drop(global_state); - // let mut global_state_mut = global_state_refcell.borrow_mut(); - // // If there's a frozen app, we'll try to use that - // let new_global_state = match frozen_app { - // // If it hadn't been initialized yet when we froze, it would've been set to `None` here, and we'll use the one from the server - // ::std::option::Option::Some(frozen_app) if frozen_app.global_state != "None" => { - // let global_state_str = frozen_app.global_state.clone(); - // let global_state = ::serde_json::from_str::<<#global_state_rx as ::perseus::state::MakeUnrx>::Unrx>(&global_state_str); - // // We don't control the source of the frozen app, so we have to assume that it could well be invalid, in whcih case we'll turn to the server - // match global_state { - // ::std::result::Result::Ok(global_state) => global_state, - // ::std::result::Result::Err(_) => { - // // This will be defined if we're the first page - // let global_state_str = props.global_state.unwrap(); - // // That's from the server, so it's unrecoverable if it doesn't deserialize - // ::serde_json::from_str::<<#global_state_rx as ::perseus::state::MakeUnrx>::Unrx>(&global_state_str).unwrap() - // } - // } - // }, - // _ => { - // // This will be defined if we're the first page - // let global_state_str = props.global_state.unwrap(); - // // That's from the server, so it's unrecoverable if it doesn't deserialize - // ::serde_json::from_str::<<#global_state_rx as ::perseus::state::MakeUnrx>::Unrx>(&global_state_str).unwrap() - // } - // }; - // let new_global_state_rx = new_global_state.make_rx(); - // *global_state_mut = ::std::boxed::Box::new(new_global_state_rx); - // // The component function can now access this in `RenderCtx` - // } // The user's function // We know this won't be async because Sycamore doesn't allow that #(#attrs)* #[::sycamore::component(#component_name<#type_param>)] fn #name#generics(#state_arg) -> #return_type { let #global_state_arg_pat: #global_state_rx = { - let global_state = ::perseus::get_render_ctx!().global_state; + let global_state = ::perseus::get_render_ctx!().global_state.0; let global_state = global_state.borrow(); // We can guarantee that it will downcast correctly now, because we'll only invoke the component from this function, which sets up the global state correctly let global_state_ref = global_state.as_any().downcast_ref::<#global_state_rx>().unwrap(); @@ -286,40 +246,6 @@ pub fn template_impl(input: TemplateFn, attr_args: AttributeArgs) -> TokenStream render_ctx.register_page_state_str::<#rx_props_ty>(&props.path, &props.state.unwrap()).unwrap() } } - // // Check if properties of the reactive type are already in the page state store - // // If they are, we'll use them (so state persists for templates across the whole app) - // let render_ctx = ::perseus::get_render_ctx!(); - // let frozen_app = render_ctx.frozen_app; - // let mut pss = render_ctx.page_state_store; - // match pss.get(&props.path) { - // ::std::option::Option::Some(old_state) => old_state, - // // Check if there's anything in the previous frozen state - // ::std::option::Option::None => { - // let state = match frozen_app { - // ::std::option::Option::Some(frozen_app) if frozen_app.page_state_store.contains_key(&props.path) => { - // let old_state = frozen_app.page_state_store.get(&props.path).unwrap(); - // // As with the global state, this may not deserialize correctly, in which case we'll fall back to the generated state - // let state = ::serde_json::from_str::<<#rx_props_ty as ::perseus::state::MakeUnrx>::Unrx>(&old_state); - // match state { - // ::std::result::Result::Ok(state) => state, - // ::std::result::Result::Err(_) => { - // // If there are props, they will always be provided, the compiler just doesn't know that - // // If the user is using this macro, they sure should be using `#[make_rx(...)]` or similar! - // ::serde_json::from_str::<<#rx_props_ty as ::perseus::state::MakeUnrx>::Unrx>(&props.state.unwrap()).unwrap() - // } - // } - // }, - // _ => { - // // If there are props, they will always be provided, the compiler just doesn't know that - // // If the user is using this macro, they sure should be using `#[make_rx(...)]` or similar! - // ::serde_json::from_str::<<#rx_props_ty as ::perseus::state::MakeUnrx>::Unrx>(&props.state.unwrap()).unwrap() - // } - // }; - // let rx_state: #rx_props_ty = state.make_rx(); - // pss.add(&props.path, rx_state.clone()); - // rx_state - // } - // } } ) } diff --git a/packages/perseus/src/client_translations_manager.rs b/packages/perseus/src/client_translations_manager.rs index 5577677f71..151b5d4399 100644 --- a/packages/perseus/src/client_translations_manager.rs +++ b/packages/perseus/src/client_translations_manager.rs @@ -7,6 +7,7 @@ use crate::translator::Translator; /// Manages translations in the app shell. This handles fetching translations from the server as well as caching for performance. /// This is distinct from `TranslationsManager` in that it operates on the client-side rather than on the server. This optimizes for /// users viewing many pages in the same locale, which is by far the most common use of most websites in terms of i18n. +#[derive(Debug)] pub struct ClientTranslationsManager { /// The cached translator. If the same locale is requested again, this will simply be returned. cached_translator: Option, diff --git a/packages/perseus/src/error_pages.rs b/packages/perseus/src/error_pages.rs index c67653d6e6..fd16b18e14 100644 --- a/packages/perseus/src/error_pages.rs +++ b/packages/perseus/src/error_pages.rs @@ -17,6 +17,11 @@ pub struct ErrorPages { status_pages: HashMap>, fallback: ErrorPageTemplate, } +impl std::fmt::Debug for ErrorPages { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ErrorPages").finish() + } +} impl ErrorPages { /// Creates a new definition of error pages with just a fallback. pub fn new( @@ -122,7 +127,7 @@ impl ErrorPages { /// A representation of an error page, particularly for storage in transit so that server-side rendered error pages can be hydrated on /// the client-side. -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub struct ErrorPageData { /// The URL for the error. pub url: String, diff --git a/packages/perseus/src/global_state.rs b/packages/perseus/src/global_state.rs index 4ea118dca4..fa5250138b 100644 --- a/packages/perseus/src/global_state.rs +++ b/packages/perseus/src/global_state.rs @@ -17,6 +17,16 @@ pub struct GlobalStateCreator { /// The function that creates state at build-time. This is roughly equivalent to the *build state* strategy for templates. build: Option, } +impl std::fmt::Debug for GlobalStateCreator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GlobalStateCreator") + .field( + "build", + &self.build.as_ref().map(|_| "GlobalStateCreatorFn"), + ) + .finish() + } +} impl GlobalStateCreator { /// Creates a new instance of this `struct`. pub fn new() -> Self { diff --git a/packages/perseus/src/html_shell.rs b/packages/perseus/src/html_shell.rs index c82755fc5a..9df1f9dbaf 100644 --- a/packages/perseus/src/html_shell.rs +++ b/packages/perseus/src/html_shell.rs @@ -16,7 +16,7 @@ fn escape_page_data(data: &str) -> String { } /// Represents a shell of an HTML file. It may have content that gets interpolated into the file. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct HtmlShell<'a> { /// The actual shell content, on whcih interpolations will be performed. shell: String, diff --git a/packages/perseus/src/lib.rs b/packages/perseus/src/lib.rs index 826bf30a8a..4ca9274b30 100644 --- a/packages/perseus/src/lib.rs +++ b/packages/perseus/src/lib.rs @@ -33,6 +33,8 @@ */ #![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![forbid(unsafe_code)] #![recursion_limit = "256"] pub mod errors; @@ -103,7 +105,7 @@ pub mod state { pub use crate::global_state::GlobalStateCreator; pub use crate::page_state_store::PageStateStore; pub use crate::rx_state::*; - pub use crate::template::{FrozenApp, PageThawPrefs, ThawPrefs}; + pub use crate::template::{FrozenApp, GlobalState, PageThawPrefs, ThawPrefs}; } /// A series of exports that should be unnecessary for nearly all uses of Perseus. These are used principally in developing alternative /// engines. diff --git a/packages/perseus/src/locales.rs b/packages/perseus/src/locales.rs index 1d257fd44b..021eb6fb35 100644 --- a/packages/perseus/src/locales.rs +++ b/packages/perseus/src/locales.rs @@ -1,5 +1,5 @@ /// Defines app information about i18n, specifically about which locales are supported. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Locales { /// The default locale, which will be used as a fallback if the user's locale can't be extracted. This will be built for at build-time. pub default: String, diff --git a/packages/perseus/src/page_state_store.rs b/packages/perseus/src/page_state_store.rs index a14263e7cc..9479052bf0 100644 --- a/packages/perseus/src/page_state_store.rs +++ b/packages/perseus/src/page_state_store.rs @@ -10,7 +10,6 @@ use crate::state::AnyFreeze; /// /// Note that the same pages in different locales will have different entries here. If you need to store state for a page across locales, you should use the global state system instead. For apps /// not using i18n, the page URL will not include any locale. -// TODO Make this work with multiple pages for a single template #[derive(Default, Clone)] pub struct PageStateStore { /// A map of type IDs to anything, allowing one storage of each type (each type is intended to a properties `struct` for a template). Entries must be `Clone`able because we assume them @@ -50,6 +49,11 @@ impl PageStateStore { str_map } } +impl std::fmt::Debug for PageStateStore { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PageStateStore").finish() + } +} // TODO Use `trybuild` properly with all this //// These are tests for the `#[make_rx]` proc macro (here temporarily) diff --git a/packages/perseus/src/plugins/control.rs b/packages/perseus/src/plugins/control.rs index 86d8abf6e0..a3bea6c069 100644 --- a/packages/perseus/src/plugins/control.rs +++ b/packages/perseus/src/plugins/control.rs @@ -55,9 +55,17 @@ impl Default for ControlPluginAction { } } } +impl std::fmt::Debug for ControlPluginAction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ControlPluginAction") + .field("controller_name", &self.controller_name) + .field("runner", &self.runner.as_ref().map(|_| "Runner")) + .finish() + } +} /// All the control actions that a plugin can take. -#[derive(Default)] +#[derive(Default, Debug)] pub struct ControlPluginActions { /// Actions pertaining to the modification of settings created with the `define_app!` macro. pub settings_actions: ControlPluginSettingsActions, @@ -72,7 +80,7 @@ pub struct ControlPluginActions { } /// Control actions that pertain to altering settings from `define_app!`. -#[derive(Default)] +#[derive(Default, Debug)] pub struct ControlPluginSettingsActions { /// Sets an immutable store to be used everywhere. This will provided the current immutable store for reference. pub set_immutable_store: @@ -83,14 +91,14 @@ pub struct ControlPluginSettingsActions { pub set_app_root: ControlPluginAction<(), String>, } /// Control actions that pertain to the build process. -#[derive(Default)] +#[derive(Default, Debug)] pub struct ControlPluginBuildActions {} /// Control actions that pertain to the export process. -#[derive(Default)] +#[derive(Default, Debug)] pub struct ControlPluginExportActions {} /// Control actions that pertain to the server. -#[derive(Default)] +#[derive(Default, Debug)] pub struct ControlPluginServerActions {} /// Control actions that pertain to the client-side code. As yet, there are none of these. -#[derive(Default)] +#[derive(Default, Debug)] pub struct ControlPluginClientActions {} diff --git a/packages/perseus/src/plugins/functional.rs b/packages/perseus/src/plugins/functional.rs index 9ea6bcb977..5a3836a2fc 100644 --- a/packages/perseus/src/plugins/functional.rs +++ b/packages/perseus/src/plugins/functional.rs @@ -47,8 +47,16 @@ impl Default for FunctionalPluginAction { } } } +impl std::fmt::Debug for FunctionalPluginAction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FunctionalPluginAction") + .field("runners", &"HashMap") + .finish() + } +} /// Actions designed to be compatible with other plugins such that two plugins can execute the same action. +#[derive(Debug)] pub struct FunctionalPluginActions { /// The all-powerful action that can modify the Perseus engine itself. Because modifying the code you're running doesn't work with /// compiled languages like Rust, this has its own command in the CLI, `perseus tinker`. This is best used for modifying @@ -86,6 +94,7 @@ impl Default for FunctionalPluginActions { } /// Functional actions that pertain to altering the settings exported from the `define_app!` macro. +#[derive(Debug)] pub struct FunctionalPluginSettingsActions { /// Adds additional static aliases. Note that a static alias is a mapping of a URL path to a filesystem path (relative to the /// project root). These will be vetted to ensure they don't access anything outside the project root for security reasons. If they @@ -111,7 +120,7 @@ impl Default for FunctionalPluginSettingsActions { /// Functional actions that pertain to the build process. Note that these actions are not available for the build /// stage of the export process, and those should be registered separately. -#[derive(Default)] +#[derive(Default, Debug)] pub struct FunctionalPluginBuildActions { /// Runs before the build process. pub before_build: FunctionalPluginAction<(), ()>, @@ -124,7 +133,7 @@ pub struct FunctionalPluginBuildActions { FunctionalPluginAction, } /// Functional actions that pertain to the export process. -#[derive(Default)] +#[derive(Default, Debug)] pub struct FunctionalPluginExportActions { /// Runs before the export process. pub before_export: FunctionalPluginAction<(), ()>, @@ -149,7 +158,7 @@ pub struct FunctionalPluginExportActions { FunctionalPluginAction, } /// Functional actions that pertain to the process of exporting an error page. -#[derive(Default)] +#[derive(Default, Debug)] pub struct FunctionalPluginExportErrorPageActions { /// Runs before the process of exporting an error page, providing the HTTP status code to be exported and the output filename (relative to the root of the project, not to `.perseus/`). pub before_export_error_page: FunctionalPluginAction<(u16, String), ()>, @@ -159,14 +168,14 @@ pub struct FunctionalPluginExportErrorPageActions { pub after_failed_write: FunctionalPluginAction<(std::io::Error, String), ()>, } /// Functional actions that pertain to the server. -#[derive(Default)] +#[derive(Default, Debug)] pub struct FunctionalPluginServerActions { /// Runs before the server activates. This runs AFTER the current directory has been appropriately set for a standalone binary vs /// running in the development environment (inside `.perseus/`). pub before_serve: FunctionalPluginAction<(), ()>, } /// Functional actions that pertain to the client-side code. These in particular should be as fast as possible. -#[derive(Default)] +#[derive(Default, Debug)] pub struct FunctionalPluginClientActions { /// Runs before anything else in the browser. Note that this runs after panics have been set to go to the console. pub start: FunctionalPluginAction<(), ()>, diff --git a/packages/perseus/src/plugins/plugin.rs b/packages/perseus/src/plugins/plugin.rs index aa879935c4..619d3d1e7e 100644 --- a/packages/perseus/src/plugins/plugin.rs +++ b/packages/perseus/src/plugins/plugin.rs @@ -8,7 +8,7 @@ type FunctionalActionsRegistrar = type ControlActionsRegistrar = Box ControlPluginActions>; /// The environments a plugin can run in. These will affect Wasm bundle size. -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Debug)] pub enum PluginEnv { /// The plugin should only run on the client-side, and will be included in the final Wasm binary. More specifically, the plugin /// will only be included if the target architecture is `wasm32`. @@ -36,6 +36,14 @@ pub struct Plugin { plugin_data_type: PhantomData, } +impl std::fmt::Debug for Plugin { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Plugin") + .field("name", &self.name) + .field("env", &self.env) + .finish() + } +} impl Plugin { /// Creates a new plugin with a name, functional actions, control actions, and whether or not the plugin is tinker-only. pub fn new( diff --git a/packages/perseus/src/plugins/plugins_list.rs b/packages/perseus/src/plugins/plugins_list.rs index 73d53c0e77..064a3d21df 100644 --- a/packages/perseus/src/plugins/plugins_list.rs +++ b/packages/perseus/src/plugins/plugins_list.rs @@ -25,6 +25,14 @@ impl Default for Plugins { } } } +impl std::fmt::Debug for Plugins { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Plugins") + .field("functional_actions", &self.functional_actions) + .field("control_actions", &self.control_actions) + .finish() + } +} impl Plugins { /// Creates a new instance of `Plugins`, with no actions taken by any plugins, and the data map empty. pub fn new() -> Self { diff --git a/packages/perseus/src/router.rs b/packages/perseus/src/router.rs index 5344c083ac..1cb1ae87db 100644 --- a/packages/perseus/src/router.rs +++ b/packages/perseus/src/router.rs @@ -216,6 +216,7 @@ pub fn match_route_atomic<'a, G: Html>( /// Information about a route, which, combined with error pages and a client-side translations manager, allows the initialization of /// the app shell and the rendering of a page. +#[derive(Debug)] pub struct RouteInfo { /// The actual path of the route. pub path: String, @@ -231,6 +232,7 @@ pub struct RouteInfo { /// The possible outcomes of matching a route. This is an alternative implementation of Sycamore's `Route` trait to enable greater /// control and tighter integration of routing with templates. This can only be used if `Routes` has been defined in context (done /// automatically by the CLI). +#[derive(Debug)] pub enum RouteVerdict { /// The given route was found, and route information is attached. Found(RouteInfo), @@ -245,6 +247,7 @@ pub enum RouteVerdict { /// /// This version is designed for multithreaded scenarios, and stores a reference to a template rather than an `Rc>`. That means this is not compatible /// with Perseus on the client-side, only on the server-side. +#[derive(Debug)] pub struct RouteInfoAtomic<'a, G: Html> { /// The actual path of the route. pub path: String, @@ -262,6 +265,7 @@ pub struct RouteInfoAtomic<'a, G: Html> { /// automatically by the CLI). /// /// This version uses `RouteInfoAtomic`, and is designed for multithreaded scenarios (i.e. on the server). +#[derive(Debug)] pub enum RouteVerdictAtomic<'a, G: Html> { /// The given route was found, and route information is attached. Found(RouteInfoAtomic<'a, G>), @@ -294,7 +298,7 @@ macro_rules! create_app_route { } /// The state for the router. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct RouterState { /// The router's current load state. load_state: Signal, @@ -319,7 +323,7 @@ impl RouterState { } /// The current load state of the router. You can use this to be warned of when a new page is about to be loaded (and display a loading bar or the like, perhaps). -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum RouterLoadState { /// The page has been loaded. Loaded { diff --git a/packages/perseus/src/server/options.rs b/packages/perseus/src/server/options.rs index 24039c26e5..af92b435ad 100644 --- a/packages/perseus/src/server/options.rs +++ b/packages/perseus/src/server/options.rs @@ -9,6 +9,7 @@ use std::collections::HashMap; /// The options for setting up all server integrations. This should be literally constructed, as nothing is optional. If integrations need further properties, /// they should expose their own options in addition to these. These should be accessed through an `Arc`/`Rc` for integration developers. +#[derive(Debug)] pub struct ServerOptions { /// The location on the filesystem of your JavaScript bundle. pub js_bundle: String, @@ -37,6 +38,7 @@ pub struct ServerOptions { } /// The full set of properties that all server integrations take. +#[derive(Debug)] pub struct ServerProps { /// The options for setting up the server. pub opts: ServerOptions, diff --git a/packages/perseus/src/shell.rs b/packages/perseus/src/shell.rs index f8c4b57379..536b8426dc 100644 --- a/packages/perseus/src/shell.rs +++ b/packages/perseus/src/shell.rs @@ -3,10 +3,10 @@ use crate::error_pages::ErrorPageData; use crate::errors::*; use crate::page_data::PageData; use crate::path_prefix::get_path_prefix_client; -use crate::state::{AnyFreeze, PageStateStore}; +use crate::state::PageStateStore; use crate::template::Template; use crate::templates::{ - FrozenApp, PageProps, RouterLoadState, RouterState, TemplateNodeType, ThawPrefs, + FrozenApp, GlobalState, PageProps, RouterLoadState, RouterState, TemplateNodeType, ThawPrefs, }; use crate::ErrorPages; use fmterr::fmt_err; @@ -224,6 +224,7 @@ pub fn checkpoint(name: &str) { /// A representation of whether or not the initial state was present. If it was, it could be `None` (some templates take no state), and /// if not, then this isn't an initial load, and we need to request the page from the server. It could also be an error that the server /// has rendered. +#[derive(Debug)] pub enum InitialState { /// A non-error initial state has been injected. Present(Option), @@ -234,6 +235,7 @@ pub enum InitialState { } /// Properties for the app shell. These should be constructed literally when working with the app shell. +#[derive(Debug)] pub struct ShellProps { /// The path we're rendering for (not the template path, the full path, though parsed a little). pub path: String, @@ -256,7 +258,7 @@ pub struct ShellProps { /// The container for reactive content. pub container_rx_elem: Element, /// The global state store. Brekaing it out here prevents it being overriden every time a new template loads. - pub global_state: Rc>>, + pub global_state: GlobalState, /// A previous frozen state to be gradully rehydrated. This should always be `None`, it only serves to provide continuity across templates. pub frozen_app: Rc>>, } diff --git a/packages/perseus/src/stores/immutable.rs b/packages/perseus/src/stores/immutable.rs index e6622fc078..700ab87730 100644 --- a/packages/perseus/src/stores/immutable.rs +++ b/packages/perseus/src/stores/immutable.rs @@ -9,7 +9,7 @@ use tokio::{ /// anything not involved in the *revalidation* or *incremental generation* strategies. /// /// Note: the `.write()` methods on this implementation will create any missing parent directories automatically. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ImmutableStore { root_path: String, } diff --git a/packages/perseus/src/stores/mutable.rs b/packages/perseus/src/stores/mutable.rs index 3bdfedcec1..dcf736b06b 100644 --- a/packages/perseus/src/stores/mutable.rs +++ b/packages/perseus/src/stores/mutable.rs @@ -7,7 +7,7 @@ use tokio::{ /// A trait for mutable stores. This is abstracted away so that users can implement a non-filesystem mutable store, which is useful /// for read-only filesystem environments, as on many modern hosting providers. See the book for further details on this subject. #[async_trait::async_trait] -pub trait MutableStore: Clone + Send + Sync { +pub trait MutableStore: std::fmt::Debug + Clone + Send + Sync { /// Reads data from the named asset. async fn read(&self, name: &str) -> Result; /// Writes data to the named asset. This will create a new asset if one doesn't exist already. @@ -19,7 +19,7 @@ pub trait MutableStore: Clone + Send + Sync { /// implementation of `MutableStore` should be preferred. /// /// Note: the `.write()` methods on this implementation will create any missing parent directories automatically. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct FsMutableStore { root_path: String, } diff --git a/packages/perseus/src/template.rs b/packages/perseus/src/template.rs index f41e360f57..09631626ae 100644 --- a/packages/perseus/src/template.rs +++ b/packages/perseus/src/template.rs @@ -27,7 +27,7 @@ use sycamore::prelude::{view, View}; use sycamore_router::navigate; /// The properties that every page will be initialized with. You shouldn't ever need to interact with this unless you decide not to use the template macros. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct PageProps { /// The path it's rendering at. pub path: String, @@ -84,9 +84,23 @@ impl PageThawPrefs { } } +/// A representation of the global state in an app. +#[derive(Clone)] +pub struct GlobalState(pub Rc>>); +impl Default for GlobalState { + fn default() -> Self { + Self(Rc::new(RefCell::new(Box::new(Option::<()>::None)))) + } +} +impl std::fmt::Debug for GlobalState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GlobalState").finish() + } +} + /// This encapsulates all elements of context currently provided to Perseus templates. While this can be used manually, there are macros /// to make this easier for each thing in here. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct RenderCtx { /// Whether or not we're being executed on the server-side. This can be used to gate `web_sys` functions and the like that expect /// to be run in the browser. @@ -105,7 +119,7 @@ pub struct RenderCtx { /// /// Because we store `dyn Any` in here, we initialize it as `Option::None`, and then the template macro (which does the heavy lifting for global state) will find that it can't downcast /// to the user's global state type, which will prompt it to deserialize whatever global state it was given and then write that here. - pub global_state: Rc>>, + pub global_state: GlobalState, /// A previous state the app was once in, still serialized. This will be rehydrated gradually by the template macro. pub frozen_app: Rc>>, } @@ -113,7 +127,7 @@ impl Freeze for RenderCtx { /// 'Freezes' the relevant parts of the render configuration to a serialized `String` that can later be used to re-initialize the app to the same state at the time of freezing. fn freeze(&self) -> String { let frozen_app = FrozenApp { - global_state: self.global_state.borrow().freeze(), + global_state: self.global_state.0.borrow().freeze(), route: match &*self.router.get_load_state().get_untracked() { RouterLoadState::Loaded { path, .. } => path, RouterLoadState::Loading { path, .. } => path, @@ -222,6 +236,7 @@ impl RenderCtx { // If there's nothing in the frozen state, we'll fall back to the active state "None" => self .global_state + .0 .borrow() .as_any() .downcast_ref::<::Rx>() @@ -235,6 +250,7 @@ impl RenderCtx { Err(_) => { return self .global_state + .0 .borrow() .as_any() .downcast_ref::<::Rx>() @@ -245,7 +261,7 @@ impl RenderCtx { // Then we convince the compiler that that actually is `R` with the ludicrous trait bound at the beginning of this function let rx = unrx.make_rx(); // And we'll register this as the new active global state - let mut active_global_state = self.global_state.borrow_mut(); + let mut active_global_state = self.global_state.0.borrow_mut(); *active_global_state = Box::new(rx.clone()); // Now we should remove this from the frozen state so we don't fall back to it again drop(frozen_app_full); @@ -260,6 +276,7 @@ impl RenderCtx { } else { // The page state store stores the reactive state already, so we don't need to do anything more self.global_state + .0 .borrow() .as_any() .downcast_ref::<::Rx>() @@ -270,6 +287,7 @@ impl RenderCtx { // This stores the reactive state already, so we don't need to do anything more // If we can't downcast the stored state to the user's type, it's almost certainly `None` instead (the initial value) self.global_state + .0 .borrow() .as_any() .downcast_ref::<::Rx>() @@ -309,7 +327,7 @@ impl RenderCtx { let unrx = serde_json::from_str::(state_str) .map_err(|err| ClientError::StateInvalid { source: err })?; let rx = unrx.make_rx(); - let mut active_global_state = self.global_state.borrow_mut(); + let mut active_global_state = self.global_state.0.borrow_mut(); *active_global_state = Box::new(rx.clone()); Ok(rx) @@ -318,7 +336,7 @@ impl RenderCtx { /// Represents all the different states that can be generated for a single template, allowing amalgamation logic to be run with the knowledge /// of what did what (rather than blindly working on a vector). -#[derive(Default)] +#[derive(Default, Debug)] pub struct States { /// Any state generated by the *build state* strategy. pub build_state: Option, @@ -496,6 +514,44 @@ pub struct Template { /// uses both `build_state` and `request_state`. If not specified and both are generated, request state will be prioritized. amalgamate_states: Option, } +impl std::fmt::Debug for Template { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Template") + .field("path", &self.path) + .field("template", &"TemplateFn") + .field("head", &"HeadFn") + .field("set_headers", &"SetHeadersFn") + .field( + "get_build_paths", + &self.get_build_paths.as_ref().map(|_| "GetBuildPathsFn"), + ) + .field( + "get_build_state", + &self.get_build_state.as_ref().map(|_| "GetBuildStateFn"), + ) + .field( + "get_request_state", + &self.get_request_state.as_ref().map(|_| "GetRequestStateFn"), + ) + .field( + "should_revalidate", + &self + .should_revalidate + .as_ref() + .map(|_| "ShouldRevalidateFn"), + ) + .field("revalidate_after", &self.revalidate_after) + .field( + "amalgamate_states", + &self + .amalgamate_states + .as_ref() + .map(|_| "AmalgamateStatesFn"), + ) + .field("incremental_generation", &self.incremental_generation) + .finish() + } +} impl Template { /// Creates a new template definition. pub fn new(path: impl Into + std::fmt::Display) -> Self { @@ -526,7 +582,7 @@ impl Template { is_server: bool, router_state: RouterState, page_state_store: PageStateStore, - global_state: Rc>>, + global_state: GlobalState, // This should always be empty, it just allows us to persist the value across template loads frozen_app: Rc>>, ) -> View { @@ -562,7 +618,7 @@ impl Template { translator: translator.clone(), router: router_state, page_state_store, - global_state: Rc::new(RefCell::new(Box::new(Option::<()>::None))), + global_state: GlobalState::default(), // Hydrating state on the server-side is pointless frozen_app: Rc::new(RefCell::new(None)) }, @@ -585,7 +641,7 @@ impl Template { // The head string is rendered to a string, and so never has information about router or page state router: RouterState::default(), page_state_store: PageStateStore::default(), - global_state: Rc::new(RefCell::new(Box::new(Option::<()>::None))), + global_state: GlobalState::default(), // Hydrating state on the server-side is pointless frozen_app: Rc::new(RefCell::new(None)), }, diff --git a/packages/perseus/src/translations_manager.rs b/packages/perseus/src/translations_manager.rs index 139dcb0d5d..ace8ee4aef 100644 --- a/packages/perseus/src/translations_manager.rs +++ b/packages/perseus/src/translations_manager.rs @@ -33,7 +33,7 @@ use tokio::io::AsyncReadExt; /// A trait for systems that manage where to put translations. At simplest, we'll just write them to static files, but they might also /// be stored in a CMS. It is **strongly** advised that any implementations use some form of caching, guided by `FsTranslationsManager`. #[async_trait::async_trait] -pub trait TranslationsManager: Clone + Send + Sync { +pub trait TranslationsManager: std::fmt::Debug + Clone + Send + Sync { /// Gets a translator for the given locale. async fn get_translator_for_locale( &self, @@ -67,7 +67,7 @@ async fn get_translations_str_and_cache( /// The default translations manager. This will store static files in the specified location on disk. This should be suitable for /// nearly all development and serverful use-cases. Serverless is another matter though (more development needs to be done). This /// mandates that translations be stored as files named as the locale they describe (e.g. 'en-US.ftl', 'en-US.json', etc.). -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct FsTranslationsManager { root_path: String, /// A map of locales to cached translations. This decreases the number of file reads significantly for the locales specified. This @@ -174,7 +174,7 @@ impl TranslationsManager for FsTranslationsManager { /// A dummy translations manager for use if you don't want i18n. This avoids errors of not being able to find translations. If you set /// `no_i18n: true` in the `locales` section of `define_app!`, this will be used by default. If you intend to use i18n, do not use this! /// Using the `link!` macro with this will NOT prepend the path prefix, and it will result in a nonsensical URL that won't work. -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct DummyTranslationsManager; impl DummyTranslationsManager { /// Creates a new dummy translations manager. diff --git a/packages/perseus/src/translator/dummy.rs b/packages/perseus/src/translator/dummy.rs index 34b986ffb3..38c762fb29 100644 --- a/packages/perseus/src/translator/dummy.rs +++ b/packages/perseus/src/translator/dummy.rs @@ -8,7 +8,7 @@ pub const DUMMY_TRANSLATOR_FILE_EXT: &str = ""; /// /// If you're using i18n, enable the `translator-fluent` feature flag to replace this with `FluentTranslator`, which will actually translate /// things. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct DummyTranslator; impl DummyTranslator { /// Creates a new dummy translator, accepting the usual parameters for translators. diff --git a/packages/perseus/src/translator/fluent.rs b/packages/perseus/src/translator/fluent.rs index 764d0aa784..b143f2169c 100644 --- a/packages/perseus/src/translator/fluent.rs +++ b/packages/perseus/src/translator/fluent.rs @@ -24,6 +24,13 @@ pub struct FluentTranslator { /// The locale for which translations are being managed by this instance. locale: String, } +impl std::fmt::Debug for FluentTranslator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FluentTranslator") + .field("locale", &self.locale) + .finish() + } +} impl FluentTranslator { /// Creates a new translator for a given locale, passing in translations in FTL syntax form. pub fn new(locale: String, ftl_string: String) -> Result {