oxide_generator_rs 0.4.0

Procedural macros for Oxide stores, actions, and reducers (FRB-friendly surface generation).
Documentation

oxide_generator_rs

oxide_generator_rs provides proc-macro attributes used to annotate Oxide state and reducer types.

The macros embed structured metadata as Rust doc strings so that code generation tools can discover:

  • state names and field shapes
  • action names and variant shapes
  • reducer ↔ state/actions associations

This crate is intentionally usage-agnostic. For end-to-end Rust ↔ Flutter wiring, see the repository examples and the root README.

Add It To Your Crate

In your Cargo.toml:

[dependencies]
oxide_generator_rs = "0.4.0"
oxide_core = "0.4.0"

When working inside this repository, use combined version + path dependencies (Cargo prefers path locally, while published crates resolve by version):

oxide_core = { version = "0.4.0", path = "../rust/oxide_core" }
oxide_generator_rs = { version = "0.4.0", path = "../rust/oxide_generator_rs" }

Macros

#[state]

Use on a struct (single-state) or enum (multi-state).

The macro ensures these derives exist:

  • Debug, Clone, PartialEq, Eq

Serialization derives (Serialize, Deserialize) are only added when the state-persistence feature is enabled on oxide_generator_rs. When enabled, the macro also injects #[serde(crate = "oxide_core::serde")] so downstream crates do not need to depend on serde directly.

Example (struct state):

use oxide_generator_rs::state;

#[state]
pub struct AppState {
  pub counter: u64,
}

Optional: #[state(sliced = true)]

Enable sliced updates on a struct state to generate a slice enum and fieldwise slice inference.

use oxide_generator_rs::state;

#[state(sliced = true)]
pub struct AppState {
  pub counter: u64,
  pub username: String,
}

// The macro generates:
// - a slice enum named `AppStateSlice` (one variant per top-level field)
// - an `oxide_core::SlicedState` impl that compares top-level fields with `PartialEq`
// - a helper `AppState::infer_slices_impl(before, after)` returning `Vec<AppStateSlice>`

Notes:

  • Only supported on structs with named fields.
  • Not supported on enums (use a struct with top-level fields if you need slicing).

Example (enum state):

use oxide_generator_rs::state;

#[state]
pub enum SessionState {
  LoggedOut,
  LoggedIn { user_id: String },
}

#[actions]

Use on an enum representing a reducer’s action set.

The macro injects the same derives and serde crate attribute as #[state].

Example:

use oxide_generator_rs::actions;

#[actions]
pub enum AppAction {
  Increment,
  SetName { name: String },
}

#[reducer(...)]

Use on an impl oxide_core::Reducer for <Type> block.

Argument syntax

The macro uses a small, keyword-based mini-language inside the attribute. Each entry is either:

  • a key-value pair: key = <value>
  • a flag: no_frb

Entries are comma-separated and can appear in any order:

#[reducer(
  engine = MyEngine,
  snapshot = MySnapshot,
  initial = MyState { count: 0 },
  reducer = MyReducer::default(),
  persist = "my.app.persistence.key",
  persist_min_interval_ms = 200,
  no_frb,
)]

Required arguments:

  • engine = <Ident>: generated engine wrapper type
  • snapshot = <Ident>: generated snapshot type
  • initial = <expr>: initial state expression

Optional arguments:

  • reducer = <expr>: reducer construction expression (defaults to Default::default())
  • no_frb: disables emitting Flutter Rust Bridge glue for this reducer (useful for pure-Rust reducers)
  • persist = "some.key" and persist_min_interval_ms = 200: enables persistent engine wiring (requires state-persistence feature)

Sliced updates are enabled automatically when your reducer returns [oxide_core::StateChange::Infer] or [oxide_core::StateChange::Slices].

When sliced updates are enabled, the macro expects the slice enum StateSlice type to be named <StateName>Slice (as generated by #[state(sliced = true)]) and available in scope.

Value kinds

  • engine and snapshot must be plain identifiers (not paths). The macro generates a new type with that name.
  • initial is any Rust expression that evaluates to State.
  • reducer is any Rust expression that evaluates to the reducer type (defaults to Default::default()).
  • persist must be a string literal ("...").
  • persist_min_interval_ms must be an integer literal (milliseconds).
  • no_frb is a bare identifier flag.

The annotated impl must define:

  • type State = ...;
  • type Action = ...;
  • type SideEffect = ...;
  • async fn init(&mut self, ctx: oxide_core::InitContext<Self::SideEffect>)
  • fn reduce(&mut self, state: &mut Self::State, action: Self::Action) -> oxide_core::CoreResult<oxide_core::StateChange>
  • fn effect(&mut self, state: &mut Self::State, effect: Self::SideEffect) -> oxide_core::CoreResult<oxide_core::StateChange>

Example:

use oxide_generator_rs::{actions, reducer, state};

#[state]
pub struct AppState {
  pub counter: u64,
}

#[actions]
pub enum AppAction {
  Increment,
}

pub enum AppSideEffect {}

#[derive(Default)]
pub struct AppReducer {}

#[reducer(engine = AppEngine, snapshot = AppSnapshot, initial = AppState { counter: 0 }, no_frb)]
impl oxide_core::Reducer for AppReducer {
  type State = AppState;
  type Action = AppAction;
  type SideEffect = AppSideEffect;

  async fn init(&mut self, _ctx: oxide_core::InitContext<Self::SideEffect>) {}

  fn reduce(
    &mut self,
    state: &mut Self::State,
    action: Self::Action,
  ) -> oxide_core::CoreResult<oxide_core::StateChange> {
    match action {
      AppAction::Increment => state.counter = state.counter.saturating_add(1),
    }
    Ok(oxide_core::StateChange::Full)
  }

  fn effect(
    &mut self,
    _state: &mut Self::State,
    _effect: Self::SideEffect,
  ) -> oxide_core::CoreResult<oxide_core::StateChange> {
    Ok(oxide_core::StateChange::None)
  }
}

Features

  • frb (default): enables FRB export generation for #[reducer(...)] (can still be disabled per reducer via no_frb)
  • state-persistence: enables serde derives injection for #[state] / #[actions] and enables persistence arguments for #[reducer(...)]
  • full: enables all features

Metadata

The macros add doc strings containing oxide:meta:<json>. The JSON includes user doc comments and structural information needed for generators.

License

Dual-licensed under MIT OR Apache-2.0. See LICENSE.