fob_graph/analysis/
config.rs

1//! Shared configuration types for analysis and building.
2
3use std::fmt;
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6
7use rustc_hash::FxHashMap;
8
9use crate::runtime::Runtime;
10
11/// Default maximum depth for graph traversal (DoS protection).
12///
13/// This prevents infinite loops in circular dependencies and provides
14/// a reasonable limit for very deep dependency trees.
15pub const DEFAULT_MAX_DEPTH: usize = 1000;
16
17/// Default maximum number of modules to process (DoS protection).
18///
19/// This prevents processing extremely large codebases that could cause
20/// memory exhaustion or excessive processing time.
21pub const DEFAULT_MAX_MODULES: usize = 100_000;
22
23/// Maximum depth for graph traversal (DoS protection).
24///
25/// This newtype ensures type safety when working with depth limits.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
27pub struct MaxDepth(pub usize);
28
29impl MaxDepth {
30    /// Create a new MaxDepth value.
31    pub fn new(depth: usize) -> Self {
32        Self(depth)
33    }
34
35    /// Get the depth value.
36    pub fn value(&self) -> usize {
37        self.0
38    }
39}
40
41impl Default for MaxDepth {
42    /// Default maximum depth.
43    fn default() -> Self {
44        Self(DEFAULT_MAX_DEPTH)
45    }
46}
47
48impl From<usize> for MaxDepth {
49    fn from(depth: usize) -> Self {
50        Self(depth)
51    }
52}
53
54impl From<MaxDepth> for usize {
55    fn from(depth: MaxDepth) -> Self {
56        depth.0
57    }
58}
59
60/// Normalized path with validation.
61///
62/// This newtype ensures that paths have been validated and normalized,
63/// preventing path traversal attacks.
64#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
65pub struct NormalizedPath(pub PathBuf);
66
67impl NormalizedPath {
68    /// Create a new NormalizedPath from a PathBuf.
69    ///
70    /// # Safety
71    ///
72    /// This method assumes the path has already been validated.
73    /// For safe construction, use the validation module.
74    pub fn new(path: PathBuf) -> Self {
75        Self(path)
76    }
77
78    /// Get the path as a reference.
79    pub fn as_path(&self) -> &Path {
80        &self.0
81    }
82
83    /// Get the path as PathBuf.
84    pub fn into_path_buf(self) -> PathBuf {
85        self.0
86    }
87}
88
89impl AsRef<Path> for NormalizedPath {
90    fn as_ref(&self) -> &Path {
91        &self.0
92    }
93}
94
95impl From<NormalizedPath> for PathBuf {
96    fn from(path: NormalizedPath) -> Self {
97        path.0
98    }
99}
100
101/// Maximum file size in bytes (10 MB).
102///
103/// Files larger than this will be rejected to prevent memory exhaustion
104/// and DoS attacks.
105pub const MAX_FILE_SIZE: usize = 10 * 1024 * 1024;
106
107/// Maximum number of script tags to process in framework files.
108///
109/// Framework files can contain multiple script blocks.
110/// This limit prevents processing files with excessive script tags.
111pub const MAX_SCRIPT_TAGS: usize = 100;
112
113/// Configuration options shared between Analyzer and Builder.
114#[derive(Debug, Clone)]
115pub struct AnalyzerConfig {
116    /// Entry points to analyze.
117    pub entries: Vec<PathBuf>,
118
119    /// Packages to treat as external (not analyzed).
120    pub external: Vec<String>,
121
122    /// Path aliases for import resolution (e.g., "@" → "./src").
123    pub path_aliases: FxHashMap<String, String>,
124
125    /// Whether to follow dynamic imports.
126    pub follow_dynamic_imports: bool,
127
128    /// Whether to include TypeScript type-only imports.
129    pub include_type_imports: bool,
130
131    /// Maximum depth for graph traversal (DoS protection).
132    ///
133    /// Default: `DEFAULT_MAX_DEPTH` (1000)
134    pub max_depth: Option<usize>,
135
136    /// Maximum number of modules to process (DoS protection).
137    ///
138    /// Default: `DEFAULT_MAX_MODULES` (100,000)
139    pub max_modules: Option<usize>,
140
141    /// Runtime for filesystem operations.
142    pub runtime: Option<Arc<dyn Runtime>>,
143
144    /// Current working directory.
145    pub cwd: Option<PathBuf>,
146}
147
148impl Default for AnalyzerConfig {
149    fn default() -> Self {
150        Self {
151            entries: Vec::new(),
152            external: Vec::new(),
153            path_aliases: FxHashMap::default(),
154            follow_dynamic_imports: false,
155            include_type_imports: true,
156            max_depth: Some(DEFAULT_MAX_DEPTH),
157            max_modules: Some(DEFAULT_MAX_MODULES),
158            runtime: None,
159            cwd: None,
160        }
161    }
162}
163
164/// Result of module resolution.
165#[derive(Debug, Clone)]
166pub enum ResolveResult {
167    /// Module resolved to a local file path.
168    Local(PathBuf),
169
170    /// Module is external (npm package, etc.).
171    External(String),
172
173    /// Module could not be resolved.
174    Unresolved(String),
175}
176
177impl ResolveResult {
178    pub fn is_local(&self) -> bool {
179        matches!(self, ResolveResult::Local(_))
180    }
181
182    pub fn is_external(&self) -> bool {
183        matches!(self, ResolveResult::External(_))
184    }
185
186    pub fn is_unresolved(&self) -> bool {
187        matches!(self, ResolveResult::Unresolved(_))
188    }
189}
190
191impl fmt::Display for ResolveResult {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        match self {
194            ResolveResult::Local(path) => write!(f, "Local({})", path.display()),
195            ResolveResult::External(name) => write!(f, "External({})", name),
196            ResolveResult::Unresolved(specifier) => write!(f, "Unresolved({})", specifier),
197        }
198    }
199}