(Sub-)States
The state bundles all the substates which make up the Quokka application. The state has to implement the quokka::state::State. It should
also implement a quokka::state::ProvideState<Substate> for State for every substate. To make this easy both of these traits have a derive
macro allowing you to quickly build your custom state. In most cases though the quokka::DefaultState is probably good enough for you.
The state derive macros
State and ProvideState
There are derive macros for both traits required for implementing a custom state #[derive(State, ProvideState)]. The ProvideState
also implements the ProvideStateRef. This additional trait allows you to get references of the substate directly from a &State and
&mut State. Immutable and mutable references respectively. With this you can modify a substate to register and modify it to integrate
with your bundle.
#[derive(Clone, Default)]
struct MyState {}
#[derive(Clone)]
struct MoreComplexState {
name: String,
}
fn complex_builder(name: impl ToString) -> MoreComplexState {
MoreComplexState {
name: name.to_string(),
}
}
#[derive(Clone, State, ProvideState)]
struct CustomState {
/// A state built with a `Default::default()` call
#[state(default)]
pub state: MyState,
/// A state built by calling the `complex_builder("Alice")`
///
/// As the `builder` can by any expression you can but there whatever you like, it can be a function call, variable or whatever else you
/// like to have there.
#[state(builder = complex_builder("Alice"))]
pub complex_state: MoreComplexState,
// All the quokka states that might be needed
/// A state built with `quokka::config::TryFromModule` trait
///
/// All the quokka states implement this trait for simplicity, even if they are implementing the `Default` trait
pub templating: quokka::state::Templating,
pub styling: quokka::state::Styling,
pub scripting: quokka::state::Scripting,
}
FromState
The FromState builds a given state from sub-states that implement the ProvideState for a parent state (This might sound more complicated
then it actually is). Additionally fields again support a #[from_state(builder = EXPR)] for things that are not in a state or effectively
cannot be in a state (like a String).
This example assumes, that you have an otherwise working Quokka application running with the quokka::DefaultState.
#[derive(FromState)]
struct SomeService {
/// The `quokka::state::FromState` also accepts a `#[from_state(builder = EXPR)]` attribute to provide things like a string.
///
/// As the `FromState::from_state` method gets the state in as `state: &S`
#[from_state(builder = "Alice".to_string())]
name: String,
/// **Note**: usually a trait bound is introduced for every field in the struct. As soon as you provide it with a `builder` th is
/// behavior is disabled. You can provide additional trait bounds with the `bounds` field inside the attribute to fix this up
#[from_state(builder = MoreComplexState::from_state(state).name, bounds = "MoreComplexState: FromState<State>")]
complex_name: String,
/// This field is just to demonstrate that, as long as the type can be built from the applications state, it doesn't matter how deeply a
/// service is nested
_db: Database,
}
#[derive(FromState)]
struct NestedService {
service: SomeService,
}
impl NestedService {
fn get_response(&self) -> String {
format!("Hello {}", self.service.name)
}
}
The custom State extractor
Quokka brings it's own quokka::extract::State utility, which is similar to the axum::extract::State, but utilizes the
quokka::state::{ProvideState, FromState} traits. The Quokka extractor is still type checked though as it uses the routers' state.
Building on the example from before, the Quokka state extractor works like shown here
#[axum::debug_handler(state = quokka::DefaultState)]
async fn utilize_services(srv: quokka::extract::State<NestedService>) -> String {
srv.get_response()
}
A custom Substate
The State derive macro build's it's substates (if not defined by a "builder") using the quokka::config::TryFromModule.
The "module" in the try_from_module name, refers to a config module (for more info check the Configuration chapter).
impl quokka::config::TryFromModule for CustomState {
async fn try_from_module(_: &crate::config::Module) -> Result<Option<Self>>
where
Self: Sized,
{
// Ensure that the config want's to build this substate
if module.module.ne("custom_state") {
return Ok(None);
}
// Convert the raw config to a specific one, using serde
let config: CustomConfig = module.build_config()?;
// Build the substate
Ok(Some(Self))
}
}