Skip to main content

evault_core/traits/
metadata_store.rs

1//! [`MetadataStore`] — persistent CRUD over variable and project metadata.
2//!
3//! Implementations must:
4//! - Be safe to call concurrently from the same process. (For SQLite-based
5//!   backends, the WAL journal mode is sufficient.)
6//! - Never store secret variable values — those go to a
7//!   [`SecretStore`](crate::traits::SecretStore).
8//! - Preserve referential integrity between projects, variables, and their
9//!   linkage records.
10
11use crate::error::MetadataError;
12use crate::model::{Profile, Project, ProjectId, ProjectVar, Var, VarFilter, VarId};
13
14/// Persistent storage of variable and project metadata.
15///
16/// All methods take a shared reference, so implementations must rely on
17/// interior mutability (typically a connection pool or a `Mutex`).
18pub trait MetadataStore: Send + Sync {
19    // ----- Var -------------------------------------------------------------
20
21    /// Create or update a [`Var`] by id.
22    ///
23    /// # Errors
24    /// Returns [`MetadataError::DuplicateName`] if a different variable with
25    /// the same name already exists; [`MetadataError::Backend`] for storage
26    /// failures.
27    fn upsert_var(&self, var: &Var) -> Result<(), MetadataError>;
28
29    /// Fetch a [`Var`] by id, or `Ok(None)` if not found.
30    ///
31    /// # Errors
32    /// Returns [`MetadataError::Backend`] on storage failures.
33    fn get_var(&self, id: VarId) -> Result<Option<Var>, MetadataError>;
34
35    /// Fetch a [`Var`] by its canonical name, or `Ok(None)` if not found.
36    ///
37    /// # Errors
38    /// Returns [`MetadataError::Backend`] on storage failures.
39    fn find_var_by_name(&self, name: &str) -> Result<Option<Var>, MetadataError>;
40
41    /// List variables matching `filter`.
42    ///
43    /// # Errors
44    /// Returns [`MetadataError::Backend`] on storage failures.
45    fn list_vars(&self, filter: &VarFilter) -> Result<Vec<Var>, MetadataError>;
46
47    /// Delete a [`Var`] by id. No-op if the variable does not exist.
48    ///
49    /// **Implementations MUST atomically cascade** the deletion to the
50    /// plain-value side table and to every project↔var linkage that
51    /// references this variable. The service layer relies on this atomicity
52    /// to guarantee that a variable becomes invisible to every read path in
53    /// a single observable step.
54    ///
55    /// # Errors
56    /// Returns [`MetadataError::Backend`] on storage failures.
57    fn delete_var(&self, id: VarId) -> Result<(), MetadataError>;
58
59    // ----- Plain-value side table (for VarKind::Plain) ---------------------
60
61    /// Fetch the plain (non-secret) value associated with a variable, or
62    /// `Ok(None)` if the variable does not exist or is a secret.
63    ///
64    /// # Errors
65    /// Returns [`MetadataError::Backend`] on storage failures.
66    fn get_plain_value(&self, id: VarId) -> Result<Option<String>, MetadataError>;
67
68    /// Set the plain value of a variable.
69    ///
70    /// Callers must only invoke this for [`crate::model::VarKind::Plain`]
71    /// variables. Secret values must go to a
72    /// [`SecretStore`](crate::traits::SecretStore).
73    ///
74    /// **Implementations MUST enforce this rule**: a call with a
75    /// [`VarKind::Secret`](crate::model::VarKind::Secret) variable is a
76    /// silent-failure surface (the value would be stored in the wrong tier),
77    /// so backends must reject it with
78    /// [`MetadataError::KindMismatch`] instead of silently writing.
79    ///
80    /// # Errors
81    /// Returns [`MetadataError::VarNotFound`] if the variable does not exist;
82    /// [`MetadataError::KindMismatch`] if the variable is not
83    /// [`VarKind::Plain`](crate::model::VarKind::Plain);
84    /// [`MetadataError::Backend`] on storage failures.
85    fn set_plain_value(&self, id: VarId, value: &str) -> Result<(), MetadataError>;
86
87    // ----- Project ---------------------------------------------------------
88
89    /// Create or update a [`Project`].
90    ///
91    /// # Errors
92    /// Returns [`MetadataError::Backend`] on storage failures.
93    fn upsert_project(&self, project: &Project) -> Result<(), MetadataError>;
94
95    /// Fetch a [`Project`] by id, or `Ok(None)` if not found.
96    ///
97    /// # Errors
98    /// Returns [`MetadataError::Backend`] on storage failures.
99    fn get_project(&self, id: ProjectId) -> Result<Option<Project>, MetadataError>;
100
101    /// List every known [`Project`].
102    ///
103    /// # Errors
104    /// Returns [`MetadataError::Backend`] on storage failures.
105    fn list_projects(&self) -> Result<Vec<Project>, MetadataError>;
106
107    /// Delete a project and all of its linkage records. No-op if the project
108    /// does not exist.
109    ///
110    /// **Implementations MUST atomically cascade** the deletion to every
111    /// linkage that references this project, matching the
112    /// [`Self::delete_var`] contract.
113    ///
114    /// # Errors
115    /// Returns [`MetadataError::Backend`] on storage failures.
116    fn delete_project(&self, id: ProjectId) -> Result<(), MetadataError>;
117
118    // ----- Project ↔ Var linkage ------------------------------------------
119
120    /// Create or update a [`ProjectVar`] linkage.
121    ///
122    /// # Errors
123    /// Returns [`MetadataError::ProjectNotFound`] or [`MetadataError::VarNotFound`]
124    /// if either side does not exist; [`MetadataError::Backend`] on storage
125    /// failures.
126    fn upsert_link(&self, link: &ProjectVar) -> Result<(), MetadataError>;
127
128    /// Delete a single linkage. Idempotent.
129    ///
130    /// Returns `Ok(true)` when a linkage was actually removed and `Ok(false)`
131    /// when the triple was already absent. The service layer uses this to
132    /// avoid emitting `Unlinked` audit entries for no-op calls.
133    ///
134    /// # Errors
135    /// Returns [`MetadataError::Backend`] on storage failures.
136    fn delete_link(
137        &self,
138        project_id: ProjectId,
139        var_id: VarId,
140        profile: &Profile,
141    ) -> Result<bool, MetadataError>;
142
143    /// List every linkage attached to a project (across profiles).
144    ///
145    /// # Errors
146    /// Returns [`MetadataError::Backend`] on storage failures.
147    fn list_links_for_project(
148        &self,
149        project_id: ProjectId,
150    ) -> Result<Vec<ProjectVar>, MetadataError>;
151
152    /// List every linkage that references a given variable (used by the UI to
153    /// show "where is this used").
154    ///
155    /// # Errors
156    /// Returns [`MetadataError::Backend`] on storage failures.
157    fn list_links_for_var(&self, var_id: VarId) -> Result<Vec<ProjectVar>, MetadataError>;
158}