Skip to main content

Workspace

Struct Workspace 

Source
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 in config.
  • 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

Source

pub fn rule_set_id_at(&self, rule_set: usize) -> Option<NodeId>

The NodeId of the rule set at this index, if one exists.

Source

pub fn rule_id_at(&self, rule_set: usize, rule: usize) -> Option<NodeId>

The NodeId of the rule at (rule_set, rule), if one exists. Both indices are 0-based, matching get’s JSON matched block (RFC 057’s handoff § 1.3 — set takes the same base as the machine-readable contract, not the 1-based text display).

Source

pub fn respond_id_at(&self, rule_set: usize, rule: usize) -> Option<NodeId>

The NodeId of the respond block at (rule_set, rule), if one exists. Distinct from rule_id_at’s NodeIdrespond is its own addressable node (EditCommand::UpdateRespond targets it, not the rule).

Source

pub fn describe(&self, id: NodeId) -> Option<String>

Render a NodeId back to a human-readable natural-key description — never the UUID itself. Used to label --dry-run previews and diff-summary rows without the single highest-risk mistake this RFC’s handoff calls out: serialising DiffItem (whose target: NodeId is #[serde(transparent)] over a Uuid) directly into set’s JSON output.

A plain String rather than a typed enum deliberately: RFC 057 § 2 Unresolved 3 asks for a renderer, not a second address type to keep stable — a string has no shape to freeze. Returns None only for a NodeId this workspace’s index has never seen (a stale ID from a different load()), which set’s one-load, one-invocation lifecycle should never actually produce.

Source

pub fn preview_changes(&self) -> Vec<DiffItem>

What a save() right now would write, without writing it. Thin public wrapper over the existing pub(super) compute_diff_summary — RFC 057’s --dry-run needs exactly this and nothing save()’s other side effects (the atomic writes, the baseline refresh).

Source§

impl Workspace

Source

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

Source

pub fn save(&mut self) -> Result<SaveResult, SaveError>

Save the workspace back to disk.

§Algorithm
  1. Render each editable file (root + each rule set) to a canonical TOML string and an editable-subset Table.
  2. Compare the canonical string against baseline_files. Files whose canonical output is byte-identical to the baseline are skipped entirely — nothing about them changed.
  3. For files that do differ: first confirm none of them changed on disk since we last saw them (RFC 056 §2 Q3) — checked for every file before any write, so a conflict on one file can’t leave another half-written.
  4. Mutate each file’s own previous text in place (toml_writer::apply_in_place) rather than rebuilding it, so comments, blank lines and key order survive; only the values that actually changed do. Write atomically via tempfile::NamedTempFile::persist (same-directory rename(2) on POSIX, MoveFileExW on Windows). On any single-file write failure, the partial state is whatever rename(2)s have already succeeded — see the type-level docstring on SaveError for the rationale.
  5. After all writes succeed, refresh baseline_files (to the canonical string) and original_text (to the just-written text) so a subsequent save() and conflict check both compare against what’s now actually on disk.
  6. Compute DiffItems by node, comparing the in-memory state to the load-time baseline (parsed; not text-diff).
  7. Compute requires_reload / requires_restart from the set of changed files: changes to [listener] need a restart, everything else just a reload.
Source

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

Source

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

Source

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

Source

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.

Source

pub fn has_external_changes(&self) -> bool

Returns true if any tracked config file has been modified on disk since the last load() or save().

Polls file metadata (mtime + size). Returns false on stat errors to avoid spurious “changed” signals from transient temp-file churn.

§Usage

Call periodically from the GUI and re-render when true:

if ws.has_external_changes() {
    ws.sync_from_disk().unwrap();
}
Source

pub fn sync_from_disk(&mut self) -> Result<(), WorkspaceError>

Reload all config files from disk, replacing the in-memory model.

§Every NodeId is reassigned (RFC 042)

A sync is a fresh load: the old IdIndex is discarded and a new one is seeded from scratch, so every NodeId changes, whether or not the address it names actually did. This previously stated the opposite — that IDs for unchanged addresses survived — which was never true; *self = fresh here has always replaced the whole workspace, ID index included. RFC 042 corrects the claim rather than building the preservation it described, because NodeAddress is positional (Rule { rule_set: usize, rule: usize }) and an external edit — the one case this method exists for — is exactly the case where positions shift, making “preserve by address” reassign identity onto the wrong rule as often as it would help.

After a sync, re-read the tree — do not reuse a NodeId held from before the call. A GUI calling this is already re-rendering (it just observed has_external_changes return true), so re-querying every ID from the new snapshot costs nothing extra.

On parse error, the workspace is left unchanged and the error is returned. The GUI can surface the error and retry.

After a successful sync, has_external_changes() returns false until the next external modification.

Source

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.

Source

pub fn root_path(&self) -> &Path

Access the root path. Primarily for diagnostics.

Source

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.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more