cuenv_workspaces/core/
traits.rs

1//! Core traits for workspace discovery, lockfile parsing, and dependency resolution.
2
3use crate::core::types::{DependencyRef, LockfileEntry, Workspace, WorkspaceMember};
4use crate::error::Result;
5use std::path::Path;
6
7/// Type alias for dependency graphs using petgraph.
8///
9/// The graph nodes are [`DependencyRef`]s representing packages, and edges
10/// represent dependency relationships (no edge data needed).
11pub type DependencyGraph = petgraph::Graph<DependencyRef, ()>;
12
13/// Discovers workspace configuration from a root directory.
14///
15/// Implementations of this trait handle the package manager-specific logic
16/// for finding workspace members, validating their structure, and building
17/// a complete workspace representation.
18///
19/// # Example
20///
21/// ```rust,ignore
22/// use cuenv_workspaces::{WorkspaceDiscovery, Workspace};
23/// use std::path::Path;
24///
25/// struct CargoDiscovery;
26///
27/// impl WorkspaceDiscovery for CargoDiscovery {
28///     fn discover(&self, root: &Path) -> Result<Workspace> {
29///         // Find Cargo.toml, parse workspace members, etc.
30///         todo!()
31///     }
32///
33///     fn find_members(&self, root: &Path) -> Result<Vec<WorkspaceMember>> {
34///         // Use glob patterns from Cargo.toml to find member crates
35///         todo!()
36///     }
37///
38///     fn validate_member(&self, member_path: &Path) -> Result<bool> {
39///         // Check for Cargo.toml in member directory
40///         todo!()
41///     }
42/// }
43/// ```
44pub trait WorkspaceDiscovery {
45    /// Discovers the complete workspace configuration from a root directory.
46    ///
47    /// This method should:
48    /// 1. Locate the workspace configuration file (e.g., `package.json`, `Cargo.toml`)
49    /// 2. Parse workspace member patterns
50    /// 3. Find all workspace members
51    /// 4. Validate each member
52    /// 5. Build and return a complete [`Workspace`]
53    ///
54    /// # Errors
55    ///
56    /// Returns an error if:
57    /// - The workspace configuration file is not found
58    /// - The configuration is invalid
59    /// - Members cannot be discovered or validated
60    fn discover(&self, root: &Path) -> Result<Workspace>;
61
62    /// Finds all workspace members using glob patterns or other discovery mechanisms.
63    ///
64    /// This method should scan the workspace root for members based on the
65    /// package manager's conventions (e.g., `packages/*` for npm workspaces,
66    /// `members = [...]` for Cargo).
67    ///
68    /// # Errors
69    ///
70    /// Returns an error if member discovery fails (e.g., invalid glob patterns,
71    /// I/O errors).
72    fn find_members(&self, root: &Path) -> Result<Vec<WorkspaceMember>>;
73
74    /// Validates that a potential workspace member has the required manifest files.
75    ///
76    /// For example:
77    /// - npm/Bun/pnpm/Yarn: Check for `package.json`
78    /// - Cargo: Check for `Cargo.toml`
79    ///
80    /// # Errors
81    ///
82    /// Returns an error if validation cannot be performed (e.g., I/O errors).
83    /// Returns `Ok(false)` if the member is invalid, `Ok(true)` if valid.
84    fn validate_member(&self, member_path: &Path) -> Result<bool>;
85}
86
87/// Parses package manager-specific lockfiles into structured entries.
88///
89/// Each package manager has its own lockfile format:
90/// - npm: `package-lock.json`
91/// - Bun: `bun.lock` (JSONC format)
92/// - pnpm: `pnpm-lock.yaml`
93/// - Yarn Classic: `yarn.lock`
94/// - Yarn Modern: `yarn.lock` (different format)
95/// - Cargo: `Cargo.lock`
96///
97/// Implementations handle the parsing logic for these formats.
98///
99/// # Example
100///
101/// ```rust,ignore
102/// use cuenv_workspaces::{LockfileParser, LockfileEntry};
103/// use std::path::Path;
104///
105/// struct NpmLockfileParser;
106///
107/// impl LockfileParser for NpmLockfileParser {
108///     fn parse(&self, lockfile_path: &Path) -> Result<Vec<LockfileEntry>> {
109///         // Parse package-lock.json and convert to LockfileEntry structs
110///         todo!()
111///     }
112///
113///     fn supports_lockfile(&self, path: &Path) -> bool {
114///         path.file_name()
115///             .and_then(|n| n.to_str())
116///             .map(|n| n == "package-lock.json")
117///             .unwrap_or(false)
118///     }
119///
120///     fn lockfile_name(&self) -> &str {
121///         "package-lock.json"
122///     }
123/// }
124/// ```
125pub trait LockfileParser {
126    /// Parses a lockfile into structured entries.
127    ///
128    /// Each entry represents a resolved dependency with its version, source,
129    /// checksum, and direct dependencies.
130    ///
131    /// # Errors
132    ///
133    /// Returns an error if:
134    /// - The lockfile cannot be read
135    /// - The lockfile format is invalid or corrupted
136    /// - Required fields are missing
137    fn parse(&self, lockfile_path: &Path) -> Result<Vec<LockfileEntry>>;
138
139    /// Checks if this parser supports the given lockfile path.
140    ///
141    /// This is typically a simple filename check, but may involve inspecting
142    /// file contents for formats that can't be distinguished by name alone.
143    fn supports_lockfile(&self, path: &Path) -> bool;
144
145    /// Returns the expected lockfile name for this parser.
146    ///
147    /// For example: `"package-lock.json"`, `"Cargo.lock"`, `"pnpm-lock.yaml"`.
148    fn lockfile_name(&self) -> &str;
149}
150
151/// Builds dependency graphs from workspace and lockfile data.
152///
153/// This trait combines workspace-internal dependencies (workspace protocol references)
154/// with external dependencies from lockfiles to build a complete dependency graph.
155///
156/// # Example
157///
158/// ```rust,ignore
159/// use cuenv_workspaces::{DependencyResolver, Workspace, LockfileEntry};
160///
161/// struct NpmDependencyResolver;
162///
163/// impl DependencyResolver for NpmDependencyResolver {
164///     fn resolve_dependencies(
165///         &self,
166///         workspace: &Workspace,
167///         lockfile: &[LockfileEntry],
168///     ) -> Result<DependencyGraph> {
169///         // Build graph with both workspace and external dependencies
170///         todo!()
171///     }
172///
173///     fn resolve_workspace_deps(&self, workspace: &Workspace) -> Result<Vec<DependencyRef>> {
174///         // Find dependencies using "workspace:*" protocol
175///         todo!()
176///     }
177///
178///     fn resolve_external_deps(&self, lockfile: &[LockfileEntry]) -> Result<Vec<DependencyRef>> {
179///         // Extract all external dependencies from lockfile
180///         todo!()
181///     }
182///
183///     fn detect_workspace_protocol(&self, spec: &str) -> bool {
184///         spec.starts_with("workspace:")
185///     }
186/// }
187/// ```
188pub trait DependencyResolver {
189    /// Builds a complete dependency graph from workspace and lockfile data.
190    ///
191    /// The graph should include:
192    /// - Workspace members as nodes
193    /// - Workspace-internal dependencies (workspace protocol)
194    /// - External dependencies from the lockfile
195    /// - Edges representing dependency relationships
196    ///
197    /// # Errors
198    ///
199    /// Returns an error if:
200    /// - Dependencies cannot be resolved
201    /// - Circular dependencies are detected (if validation is enabled)
202    /// - Required dependencies are missing from the lockfile
203    fn resolve_dependencies(
204        &self,
205        workspace: &Workspace,
206        lockfile: &[LockfileEntry],
207    ) -> Result<DependencyGraph>;
208
209    /// Resolves workspace-internal dependencies.
210    ///
211    /// These are dependencies that reference other workspace members using
212    /// package manager-specific protocols:
213    /// - npm/pnpm/Yarn: `"workspace:*"`, `"workspace:^"`, etc.
214    /// - Cargo: `{ workspace = true }`
215    ///
216    /// # Errors
217    ///
218    /// Returns an error if workspace dependencies cannot be resolved.
219    fn resolve_workspace_deps(&self, workspace: &Workspace) -> Result<Vec<DependencyRef>>;
220
221    /// Resolves external dependencies from lockfile entries.
222    ///
223    /// Extracts all non-workspace dependencies from the lockfile.
224    ///
225    /// # Errors
226    ///
227    /// Returns an error if dependencies cannot be extracted or validated.
228    fn resolve_external_deps(&self, lockfile: &[LockfileEntry]) -> Result<Vec<DependencyRef>>;
229
230    /// Detects if a dependency specification uses the workspace protocol.
231    ///
232    /// # Examples
233    ///
234    /// - npm/pnpm/Yarn: `"workspace:*"` → `true`
235    /// - Cargo: Requires parsing TOML to check `workspace = true`
236    /// - Regular semver: `"^1.0.0"` → `false`
237    fn detect_workspace_protocol(&self, spec: &str) -> bool;
238}