pub struct Workspace { /* private fields */ }Expand description
Editable view of an apimock workspace.
§Internal layout
The Workspace holds the loaded TOML model (as a Config) plus
two index maps:
id_to_address: NodeId → where the node lives inconfig.address_to_id: reverse — used when rebuilding snapshots.
On every apply() that could move nodes around (Add / Remove /
Move), these tables are partially rebuilt. Reloading the config
discards them and re-seeds with fresh IDs.
Implementations§
Source§impl Workspace
impl Workspace
Sourcepub fn apply(&mut self, cmd: EditCommand) -> Result<ApplyResult, ApplyError>
pub fn apply(&mut self, cmd: EditCommand) -> Result<ApplyResult, ApplyError>
Apply one edit command, mutating the in-memory workspace.
§Shape of the implementation
Each EditCommand variant maps to a small helper method. The
helpers return Result<Vec<NodeId>, ApplyError>; apply wraps
the ok-path in an ApplyResult with the right requires_reload
flag and reruns validation so the result carries up-to-date
diagnostics.
§ID stability on structural changes
Commands that change positional layout (Remove / Delete / Move
/ Add) touch self.ids carefully so NodeIds that refer to the
same logical node survive the operation. For example, after
RemoveRuleSet { id } at index i, rule sets at positions
i+1.. shift down by one: the code below explicitly migrates
their IDs so a GUI that selected rule-set #3 before the edit
still has the same ID pointing at what is now rule-set #2.
Source§impl Workspace
impl Workspace
Sourcepub fn save(&mut self) -> Result<SaveResult, SaveError>
pub fn save(&mut self) -> Result<SaveResult, SaveError>
Save the workspace back to disk.
§Algorithm
- Render each editable file (root + each rule set) to TOML text.
- Compare against
baseline_files. Files whose rendered output is byte-identical to the baseline are skipped — the user’s formatting / comments survive untouched in that case. - For files that do differ, write atomically via
tempfile::NamedTempFile::persist(same-directory rename(2) on POSIX,MoveFileExWon Windows). On any single-file failure, the partial state is whatever rename(2)s have already succeeded — see the type-level docstring onSaveErrorfor the rationale. - After all writes succeed, refresh
baseline_filesso a subsequent save() won’t re-write the same files needlessly. - Compute
DiffItems by node, comparing the in-memory state to the load-time baseline (parsed; not text-diff). - Compute
requires_reload/requires_restartfrom the set of changed files: changes to[listener]need a restart, everything else just a reload.
§The “save loses comments” diagnostic
Per the GUI spec §6 / §11, save is allowed to lose comments and
formatting. We surface this as an Info-severity diagnostic
the first time a save would actually overwrite a file that has
non-trivial formatting (any file whose TOML round-trip is not
byte-identical, which is essentially every hand-edited file).
A polished GUI shows it once per session.
Sourcepub fn has_unsaved_changes(&self) -> bool
pub fn has_unsaved_changes(&self) -> bool
True when at least one editable file’s rendered output differs from its load-time baseline.
§Use case
A GUI’s “unsaved changes” indicator polls this. Cheap relative to a full save (no file I/O, just renders + string compares).
Source§impl Workspace
impl Workspace
Sourcepub fn snapshot(&self) -> WorkspaceSnapshot
pub fn snapshot(&self) -> WorkspaceSnapshot
Build a snapshot for GUI rendering.
§Allocation cost
A snapshot fully owns its data (no borrows into the workspace) so the GUI can serialise / send / store it without lifetime gymnastics. This is O(total editable nodes) allocation per call; the GUI should call it once per edit, not once per render frame.
Source§impl Workspace
impl Workspace
Sourcepub fn validate(&self) -> ValidationReport
pub fn validate(&self) -> ValidationReport
Validate the workspace and return a GUI-ready report.
Uses the same per-node checks snapshot() does so the numbers
line up: a node rendered with a red underline in the snapshot
will appear in report.diagnostics with the same message.
Source§impl Workspace
impl Workspace
Sourcepub fn load(root: PathBuf) -> Result<Self, WorkspaceError>
pub fn load(root: PathBuf) -> Result<Self, WorkspaceError>
Load a workspace rooted at the given apimock.toml-like path.
Accepts either a direct path to the config file or the
directory containing one; a missing file-path is searched for
as apimock.toml inside root. Mirrors the CLI’s existing
resolution rules.
Sourcepub fn config(&self) -> &Config
pub fn config(&self) -> &Config
Access the underlying Config. Intended for embedders that
need to build a running Server from the same workspace. Edit
via apply() instead of touching Config directly — changes
made through this reference are invisible to the ID index.
Sourcepub fn list_directory(&self, path: &Path) -> Vec<FileNodeView>
pub fn list_directory(&self, path: &Path) -> Vec<FileNodeView>
Expand a directory in the file tree on demand.
§When the GUI calls this
Workspace::snapshot() returns a FileTreeView populated with
just the top-level entries of the fallback respond dir. Each
directory entry carries children: Some(Vec::new()) to flag it
as expandable. When a user clicks to expand one of those nodes,
the GUI calls list_directory(&entry.path) and gets back the
next depth’s entries (still not recursed past that depth — the
same lazy contract holds).
§Why path-based and not NodeId-based
File-tree entries don’t carry NodeIds (see FileNodeView). The
reason is lifecycle: the editable node space (rules, rule sets,
respond blocks) is small, stable, and survives apply() calls
— perfect for UUID-keyed state. The file tree is large,
transient, and reflects the filesystem rather than the model;
keying it by path keeps the API simple and avoids mixing two
kinds of identity.