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}