Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

(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))
    }
}