Axis::Unassigned served two purposes that Option already covers:
1. "this category has no assignment yet" → None
2. "this category doesn't exist" → None
By removing the variant and changing axis_of to return Option<Axis>,
callers are forced by the compiler to handle the absent-category case
explicitly (via match or unwrap_or), rather than silently treating it
like a real axis value.
SetAxis { axis: String } also upgraded to SetAxis { axis: Axis }.
Previously, constructing SetAxis with an invalid string (e.g. "diagonal")
would compile and then silently fail at dispatch. Now the type only admits
valid axis values; the dispatch string-parser is gone.
Axis gains #[serde(rename_all = "lowercase")] so existing JSON command
files (smoke.jsonl, etc.) using "row"/"column"/"page" continue to work.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
100 lines
2.8 KiB
Rust
100 lines
2.8 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use crate::view::Axis;
|
|
|
|
/// All commands that can mutate a Model.
|
|
///
|
|
/// Serialized as `{"op": "<variant>", ...rest}` where `rest` contains
|
|
/// the variant's fields flattened into the same JSON object.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(tag = "op")]
|
|
pub enum Command {
|
|
/// Add a category (dimension).
|
|
AddCategory { name: String },
|
|
|
|
/// Add an item to a category.
|
|
AddItem { category: String, item: String },
|
|
|
|
/// Add an item inside a named group.
|
|
AddItemInGroup { category: String, item: String, group: String },
|
|
|
|
/// Set a cell value. `coords` is a list of `[category, item]` pairs.
|
|
SetCell {
|
|
coords: Vec<[String; 2]>,
|
|
#[serde(flatten)]
|
|
value: CellValueArg,
|
|
},
|
|
|
|
/// Clear a cell.
|
|
ClearCell { coords: Vec<[String; 2]> },
|
|
|
|
/// Add or replace a formula.
|
|
/// `raw` is the full formula string, e.g. "Profit = Revenue - Cost".
|
|
/// `target_category` names the category that owns the formula target.
|
|
AddFormula { raw: String, target_category: String },
|
|
|
|
/// Remove a formula by its target name and category.
|
|
RemoveFormula { target: String, target_category: String },
|
|
|
|
/// Create a new view.
|
|
CreateView { name: String },
|
|
|
|
/// Delete a view.
|
|
DeleteView { name: String },
|
|
|
|
/// Switch the active view.
|
|
SwitchView { name: String },
|
|
|
|
/// Set the axis of a category in the active view.
|
|
SetAxis { category: String, axis: Axis },
|
|
|
|
/// Set the page-axis selection for a category.
|
|
SetPageSelection { category: String, item: String },
|
|
|
|
/// Toggle collapse of a group in the active view.
|
|
ToggleGroup { category: String, group: String },
|
|
|
|
/// Save the model to a file path.
|
|
Save { path: String },
|
|
|
|
/// Load a model from a file path (replaces current model).
|
|
Load { path: String },
|
|
|
|
/// Export the active view to CSV.
|
|
ExportCsv { path: String },
|
|
|
|
/// Import a JSON file via the analyzer (non-interactive, uses auto-detected proposals).
|
|
ImportJson {
|
|
path: String,
|
|
model_name: Option<String>,
|
|
/// Dot-path to the records array (empty = root)
|
|
array_path: Option<String>,
|
|
},
|
|
}
|
|
|
|
/// Inline value for SetCell
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(untagged)]
|
|
pub enum CellValueArg {
|
|
Number { number: f64 },
|
|
Text { text: String },
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CommandResult {
|
|
pub ok: bool,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub message: Option<String>,
|
|
}
|
|
|
|
impl CommandResult {
|
|
pub fn ok() -> Self {
|
|
Self { ok: true, message: None }
|
|
}
|
|
pub fn ok_msg(msg: impl Into<String>) -> Self {
|
|
Self { ok: true, message: Some(msg.into()) }
|
|
}
|
|
pub fn err(msg: impl Into<String>) -> Self {
|
|
Self { ok: false, message: Some(msg.into()) }
|
|
}
|
|
}
|