# 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](../../examples) and the root [README](../../README.md).
## Add It To Your Crate
In your `Cargo.toml`:
```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`):
```toml
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):
```rust,ignore
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.
```rust,ignore
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):
```rust,ignore
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:
```rust,ignore
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:
```rust,ignore
#[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:
```rust,ignore
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](./LICENSE).