pub struct JsonFileStore { /* private fields */ }Expand description
JSON-file-backed state store.
Each namespace is a single JSON file at
{root}/{namespace}.json. Writes are atomic: the new state is
written to a .tmp sibling and then renamed.
The root directory is provided at construction time; callers are
expected to resolve it from the service-layer AppDir abstraction
(typically ~/.algocline/state/).
§Concurrency
Per-namespace locks (std::sync::Mutex) prevent lost updates under
concurrent alc.state.* calls within the same process. The lock
is acquired for the full load → mutate → atomic-rename cycle, so
two tokio tasks operating on the same namespace are serialised.
Rationale for std::sync::Mutex over tokio::sync::Mutex:
all I/O inside the lock uses std::fs (synchronous, no .await),
so a standard mutex is sufficient and avoids holding a tokio mutex
across potential scheduler context switches.
Multi-process safety is NOT provided. If multiple alc
processes share the same state directory (uncommon), use a backend
with native INCR (Redis) or transactions (SQLite).
Implementations§
Source§impl JsonFileStore
impl JsonFileStore
Sourcepub fn new(root: PathBuf) -> Self
pub fn new(root: PathBuf) -> Self
Construct a store rooted at an explicit path.
The directory is not created eagerly; it is created lazily
on the first set / set_nx / incr call via Self::state_path.
Sourcepub fn state_path(&self, ns: &str) -> Result<PathBuf, String>
pub fn state_path(&self, ns: &str) -> Result<PathBuf, String>
Resolve the JSON file path for a namespace, validating the name and creating the root directory on demand.
Sourcepub fn list_dispatched(
&self,
namespace: &str,
) -> Result<Vec<String>, StateError>
pub fn list_dispatched( &self, namespace: &str, ) -> Result<Vec<String>, StateError>
List all keys in the dispatched layout for a namespace.
Enumerates {root}/{namespace}/*.json and returns the file names
stripped of the .json extension, sorted lexicographically.
Files with extensions other than .json, and .bak / .tmp
siblings, are excluded. If the namespace directory does not exist
the result is an empty Vec (namespace-absent ≡ zero keys).
§Arguments
namespace— the directory name underroot; must pass [is_safe_segment] validation
§Returns
A sorted list of key strings, or a StateError on I/O / validation
failure.
§Errors
StateError::UnsafeSegmentifnamespacefails path-safety checkStateError::IoReadif reading the directory fails
Sourcepub fn show_dispatched(
&self,
namespace: &str,
key: &str,
) -> Result<Value, StateError>
pub fn show_dispatched( &self, namespace: &str, key: &str, ) -> Result<Value, StateError>
Read the full JSON value for a dispatched-layout key.
Reads {root}/{namespace}/{key}.json and deserializes it.
§Arguments
namespace— the subdirectory name; must pass [is_safe_segment]key— the file stem; must pass [is_safe_segment]
§Returns
The deserialized serde_json::Value on success.
§Errors
StateError::UnsafeSegmentif either segment fails path-safety checkStateError::KeyNotFoundif the file does not existStateError::IoReadif the file cannot be readStateError::Serdeif the content is not valid JSON
Sourcepub fn reset_dispatched_with_backup(
&self,
namespace: &str,
key: &str,
steps: &[String],
fields: &[String],
) -> Result<ResetReport, StateError>
pub fn reset_dispatched_with_backup( &self, namespace: &str, key: &str, steps: &[String], fields: &[String], ) -> Result<ResetReport, StateError>
Atomically reset a dispatched-layout state file with a backup.
Performs the following sequence in order (Crux atomicity contract):
- Validate
namespaceandkeywith [is_safe_segment]. - Compute target path:
{root}/{namespace}/{key}.json. - Return
StateError::KeyNotFoundif the file does not exist. - Acquire the per-path mutex via
Self::ns_lock; hold until rename. - Copy the live file to
{root}/{namespace}/{key}.json.bak— the live file is not touched before this point. - Load and parse the live file.
- Apply in-memory mutations:
- Remove each element of
stepsfromdata.completed_steps(if the array exists). - Delete each element of
fieldsfrom thedatatop-level object. - If the top-level
datafield is absent or not an object, returnStateError::ShapeInvalid.
- Remove each element of
- Write the mutated value to
{target}.tmp. - Rename
.tmp→ target (POSIX atomic on same filesystem).
A crash between steps 5 and 9 leaves the .bak intact and the live
file unmodified (or only partially written to .tmp), so the original
state is always recoverable.
§Arguments
namespace— subdirectory name; must pass [is_safe_segment]key— file stem; must pass [is_safe_segment]steps— step names to remove fromdata.completed_stepsfields— field names to delete from thedatatop-level object
§Returns
A ResetReport with the backup path and counts of removed items.
§Errors
StateError::UnsafeSegmentif either segment fails path-safety checkStateError::KeyNotFoundif the file does not existStateError::ShapeInvalidif the lock is poisoned or the JSON structure is not a{data: {...}}objectStateError::IoBackupif the.bakcopy failsStateError::IoReadif loading the live file failsStateError::IoWriteif the.tmpwrite or rename failsStateError::Serdeif the file content is not valid JSON
Trait Implementations§
Source§impl StateStore for JsonFileStore
impl StateStore for JsonFileStore
Source§fn get(&self, ns: &str, key: &str) -> Result<Option<Value>, String>
fn get(&self, ns: &str, key: &str) -> Result<Option<Value>, String>
None if the key does not exist.Source§fn set(&self, ns: &str, key: &str, value: Value) -> Result<(), String>
fn set(&self, ns: &str, key: &str, value: Value) -> Result<(), String>
Source§fn delete(&self, ns: &str, key: &str) -> Result<bool, String>
fn delete(&self, ns: &str, key: &str) -> Result<bool, String>
true if it existed.Source§fn has(&self, ns: &str, key: &str) -> Result<bool, String>
fn has(&self, ns: &str, key: &str) -> Result<bool, String>
Auto Trait Implementations§
impl !Freeze for JsonFileStore
impl RefUnwindSafe for JsonFileStore
impl Send for JsonFileStore
impl Sync for JsonFileStore
impl Unpin for JsonFileStore
impl UnsafeUnpin for JsonFileStore
impl UnwindSafe for JsonFileStore
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreimpl<T> MaybeSend for Twhere
T: Send,
Source§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.