Skip to main content

evault_core/model/
project.rs

1//! [`Project`] entity and its [`Var`](crate::model::Var) linkage.
2
3use std::fmt;
4use std::path::PathBuf;
5
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9use crate::model::{Profile, VarId};
10
11/// Stable identifier of a [`Project`].
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct ProjectId(Uuid);
14
15impl ProjectId {
16    /// Generate a fresh identifier using [`Uuid::new_v4`].
17    #[must_use]
18    pub fn new_v4() -> Self {
19        Self(Uuid::new_v4())
20    }
21
22    /// Wrap an existing [`Uuid`] (used when rehydrating from a backend).
23    #[must_use]
24    pub const fn from_uuid(id: Uuid) -> Self {
25        Self(id)
26    }
27
28    /// Borrow the inner [`Uuid`].
29    #[must_use]
30    pub const fn as_uuid(&self) -> &Uuid {
31        &self.0
32    }
33}
34
35impl fmt::Display for ProjectId {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        fmt::Display::fmt(&self.0, f)
38    }
39}
40
41/// A project that has variables linked to it via an `evault.toml` manifest.
42///
43/// `Project` records the location of the manifest and a human-friendly name.
44/// Linkage details live in [`ProjectVar`] records.
45///
46/// # Examples
47/// ```
48/// use evault_core::model::Project;
49/// use std::path::PathBuf;
50///
51/// let p = Project::new("my-app", PathBuf::from("./my-app"));
52/// assert_eq!(p.name(), "my-app");
53/// ```
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct Project {
56    id: ProjectId,
57    name: String,
58    path: PathBuf,
59}
60
61impl Project {
62    /// Create a new [`Project`] with a fresh identifier.
63    ///
64    /// `path` is the directory that contains (or will contain) the
65    /// `evault.toml` manifest.
66    pub fn new(name: impl Into<String>, path: PathBuf) -> Self {
67        Self {
68            id: ProjectId::new_v4(),
69            name: name.into(),
70            path,
71        }
72    }
73
74    /// Rehydrate a [`Project`] from already-stored fields.
75    #[must_use]
76    pub const fn from_parts(id: ProjectId, name: String, path: PathBuf) -> Self {
77        Self { id, name, path }
78    }
79
80    /// Returns the project's identifier.
81    #[must_use]
82    pub const fn id(&self) -> ProjectId {
83        self.id
84    }
85
86    /// Returns the project name.
87    #[must_use]
88    pub fn name(&self) -> &str {
89        &self.name
90    }
91
92    /// Returns the path that contains the project's `evault.toml`.
93    #[must_use]
94    pub fn path(&self) -> &std::path::Path {
95        self.path.as_path()
96    }
97}
98
99/// Linkage record between a [`Project`] and a [`crate::model::Var`].
100///
101/// Each link can optionally rename the variable in the project context
102/// (`alias`) and is scoped to a [`Profile`].
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub struct ProjectVar {
105    /// Owning project.
106    pub project_id: ProjectId,
107    /// Linked variable in the central registry.
108    pub var_id: VarId,
109    /// Optional rename: the project sees the variable under this name.
110    pub alias: Option<String>,
111    /// Profile this link applies to (`default`, `dev`, `prod`, etc.).
112    pub profile: Profile,
113}
114
115impl ProjectVar {
116    /// Construct a linkage for the default profile with no alias.
117    #[must_use]
118    pub fn new(project_id: ProjectId, var_id: VarId) -> Self {
119        Self {
120            project_id,
121            var_id,
122            alias: None,
123            profile: Profile::default_profile(),
124        }
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn new_project_has_fresh_id() {
134        let a = Project::new("a", PathBuf::from("./a"));
135        let b = Project::new("b", PathBuf::from("./b"));
136        assert_ne!(a.id(), b.id());
137    }
138
139    #[test]
140    fn project_var_default_profile_no_alias() {
141        let pid = ProjectId::new_v4();
142        let vid = VarId::new_v4();
143        let pv = ProjectVar::new(pid, vid);
144        assert_eq!(pv.project_id, pid);
145        assert_eq!(pv.var_id, vid);
146        assert!(pv.alias.is_none());
147        assert!(pv.profile.is_default());
148    }
149
150    #[test]
151    fn project_id_display_matches_uuid() {
152        let id = ProjectId::new_v4();
153        assert_eq!(id.to_string(), id.as_uuid().to_string());
154    }
155}