mockforge_core/pillars.rs
1//! Pillar metadata system for compile-time pillar tagging
2//!
3//! This module provides types and utilities for tagging modules, functions, and features
4//! with MockForge pillars. Pillars help organize code, enable pillar-based queries for
5//! test coverage and production usage, and guide users to relevant features.
6//!
7//! ## The Five Pillars
8//!
9//! - **[Reality]** – Everything that makes mocks feel like a real, evolving backend
10//! - **[Contracts]** – Schema, drift, validation, and safety nets
11//! - **[DevX]** – SDKs, generators, playgrounds, ergonomics
12//! - **[Cloud]** – Registry, orgs, governance, monetization, marketplace
13//! - **[AI]** – LLM/voice flows, AI diff/assist, generative behaviors
14//!
15//! ## Usage
16//!
17//! Tag modules in their documentation comments:
18//!
19//! ```rust
20//! //! Pillars: [Reality][AI]
21//! //!
22//! //! This module implements Smart Personas with relationship graphs
23//! //! and AI-powered data generation.
24//! ```
25//!
26//! Or use the `Pillar` enum programmatically:
27//!
28//! ```rust
29//! use mockforge_core::pillars::{Pillar, PillarMetadata};
30//!
31//! let metadata = PillarMetadata::new()
32//! .with_pillar(Pillar::Reality)
33//! .with_pillar(Pillar::Ai);
34//! ```
35
36use serde::{Deserialize, Serialize};
37use std::collections::HashSet;
38use std::fmt;
39
40/// MockForge pillar identifier
41///
42/// Every feature in MockForge maps to one or more pillars. This structure helps:
43/// - Communicate value clearly in changelogs, docs, and marketing
44/// - Prioritize development based on pillar investment
45/// - Maintain consistency across features and releases
46/// - Guide users to the right features for their needs
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
48#[serde(rename_all = "lowercase")]
49pub enum Pillar {
50 /// Reality pillar - everything that makes mocks feel like a real, evolving backend
51 ///
52 /// Key capabilities:
53 /// - Realistic data generation with relationships and constraints
54 /// - Stateful behavior and persistence
55 /// - Network condition simulation (latency, packet loss, failures)
56 /// - Time-based mutations and temporal simulation
57 /// - Progressive data evolution and drift
58 /// - Multi-protocol support
59 Reality,
60 /// Contracts pillar - schema, drift, validation, and safety nets
61 ///
62 /// Key capabilities:
63 /// - OpenAPI/GraphQL schema validation
64 /// - Request/response validation with detailed error reporting
65 /// - Contract drift detection and monitoring
66 /// - Automatic API sync and change detection
67 /// - Schema-driven mock generation
68 Contracts,
69 /// DevX pillar - SDKs, generators, playgrounds, ergonomics
70 ///
71 /// Key capabilities:
72 /// - Multi-language SDKs (Rust, Node.js, Python, Go, Java, .NET)
73 /// - Client code generation (React, Vue, Angular, Svelte)
74 /// - Interactive playgrounds and admin UI
75 /// - CLI tooling and configuration management
76 /// - Comprehensive documentation and examples
77 /// - Plugin system for extensibility
78 DevX,
79 /// Cloud pillar - registry, orgs, governance, monetization, marketplace
80 ///
81 /// Key capabilities:
82 /// - Organization and user management
83 /// - Scenario marketplace and sharing
84 /// - Registry server for mock distribution
85 /// - Cloud workspaces and synchronization
86 /// - Governance and access controls
87 /// - Monetization infrastructure
88 Cloud,
89 /// AI pillar - LLM/voice flows, AI diff/assist, generative behaviors
90 ///
91 /// Key capabilities:
92 /// - LLM-powered mock generation
93 /// - AI-driven data synthesis
94 /// - Voice interface for mock creation
95 /// - Intelligent contract analysis
96 /// - Generative data behaviors
97 /// - Natural language to mock conversion
98 Ai,
99}
100
101impl Pillar {
102 /// Convert pillar to lowercase string representation
103 ///
104 /// # Examples
105 ///
106 /// ```
107 /// use mockforge_core::pillars::Pillar;
108 ///
109 /// assert_eq!(Pillar::Reality.as_str(), "reality");
110 /// assert_eq!(Pillar::Contracts.as_str(), "contracts");
111 /// assert_eq!(Pillar::DevX.as_str(), "devx");
112 /// assert_eq!(Pillar::Cloud.as_str(), "cloud");
113 /// assert_eq!(Pillar::Ai.as_str(), "ai");
114 /// ```
115 pub fn as_str(&self) -> &'static str {
116 match self {
117 Pillar::Reality => "reality",
118 Pillar::Contracts => "contracts",
119 Pillar::DevX => "devx",
120 Pillar::Cloud => "cloud",
121 Pillar::Ai => "ai",
122 }
123 }
124
125 /// Parse pillar from string (case-insensitive)
126 ///
127 /// # Examples
128 ///
129 /// ```
130 /// use mockforge_core::pillars::Pillar;
131 ///
132 /// assert_eq!(Pillar::from_str("reality"), Some(Pillar::Reality));
133 /// assert_eq!(Pillar::from_str("REALITY"), Some(Pillar::Reality));
134 /// assert_eq!(Pillar::from_str("Contracts"), Some(Pillar::Contracts));
135 /// assert_eq!(Pillar::from_str("devx"), Some(Pillar::DevX));
136 /// assert_eq!(Pillar::from_str("invalid"), None);
137 /// ```
138 pub fn from_str(s: &str) -> Option<Self> {
139 match s.to_lowercase().as_str() {
140 "reality" => Some(Pillar::Reality),
141 "contracts" => Some(Pillar::Contracts),
142 "devx" => Some(Pillar::DevX),
143 "cloud" => Some(Pillar::Cloud),
144 "ai" => Some(Pillar::Ai),
145 _ => None,
146 }
147 }
148
149 /// Get display name for the pillar (with brackets for changelog format)
150 ///
151 /// # Examples
152 ///
153 /// ```
154 /// use mockforge_core::pillars::Pillar;
155 ///
156 /// assert_eq!(Pillar::Reality.display_name(), "[Reality]");
157 /// assert_eq!(Pillar::Contracts.display_name(), "[Contracts]");
158 /// ```
159 pub fn display_name(&self) -> String {
160 match self {
161 Pillar::Reality => "[Reality]".to_string(),
162 Pillar::Contracts => "[Contracts]".to_string(),
163 Pillar::DevX => "[DevX]".to_string(),
164 Pillar::Cloud => "[Cloud]".to_string(),
165 Pillar::Ai => "[AI]".to_string(),
166 }
167 }
168
169 /// Get all pillars
170 ///
171 /// # Examples
172 ///
173 /// ```
174 /// use mockforge_core::pillars::Pillar;
175 ///
176 /// let all = Pillar::all();
177 /// assert_eq!(all.len(), 5);
178 /// assert!(all.contains(&Pillar::Reality));
179 /// assert!(all.contains(&Pillar::Contracts));
180 /// assert!(all.contains(&Pillar::DevX));
181 /// assert!(all.contains(&Pillar::Cloud));
182 /// assert!(all.contains(&Pillar::Ai));
183 /// ```
184 pub fn all() -> Vec<Pillar> {
185 vec![
186 Pillar::Reality,
187 Pillar::Contracts,
188 Pillar::DevX,
189 Pillar::Cloud,
190 Pillar::Ai,
191 ]
192 }
193}
194
195impl fmt::Display for Pillar {
196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197 write!(f, "{}", self.as_str())
198 }
199}
200
201/// Pillar metadata for tagging modules, functions, or features
202///
203/// This type allows associating one or more pillars with code entities.
204/// It's used for compile-time tagging and can be extracted from documentation
205/// comments or set programmatically.
206#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
207pub struct PillarMetadata {
208 /// Set of pillars associated with this entity
209 pillars: HashSet<Pillar>,
210}
211
212impl PillarMetadata {
213 /// Create new empty pillar metadata
214 pub fn new() -> Self {
215 Self {
216 pillars: HashSet::new(),
217 }
218 }
219
220 /// Create pillar metadata with a single pillar
221 pub fn with_pillar(mut self, pillar: Pillar) -> Self {
222 self.pillars.insert(pillar);
223 self
224 }
225
226 /// Create pillar metadata with multiple pillars
227 pub fn with_pillars(mut self, pillars: &[Pillar]) -> Self {
228 for pillar in pillars {
229 self.pillars.insert(*pillar);
230 }
231 self
232 }
233
234 /// Add a pillar to existing metadata
235 pub fn add_pillar(&mut self, pillar: Pillar) {
236 self.pillars.insert(pillar);
237 }
238
239 /// Check if metadata contains a specific pillar
240 pub fn has_pillar(&self, pillar: Pillar) -> bool {
241 self.pillars.contains(&pillar)
242 }
243
244 /// Get all pillars in this metadata
245 pub fn pillars(&self) -> Vec<Pillar> {
246 let mut result: Vec<Pillar> = self.pillars.iter().copied().collect();
247 // Sort for consistent output
248 result.sort_by_key(|p| p.as_str());
249 result
250 }
251
252 /// Check if metadata is empty
253 pub fn is_empty(&self) -> bool {
254 self.pillars.is_empty()
255 }
256
257 /// Format pillars as changelog-style tags (e.g., "[Reality][AI]")
258 pub fn to_changelog_tags(&self) -> String {
259 let mut pillars: Vec<Pillar> = self.pillars.iter().copied().collect();
260 // Sort for consistent output
261 pillars.sort_by_key(|p| p.as_str());
262 pillars.iter().map(|p| p.display_name()).collect::<Vec<_>>().join("")
263 }
264
265 /// Parse pillar metadata from documentation comment
266 ///
267 /// Looks for patterns like:
268 /// - `Pillars: [Reality][AI]`
269 /// - `Pillar: [Reality]`
270 /// - `//! Pillars: [Reality][AI]`
271 ///
272 /// # Examples
273 ///
274 /// ```
275 /// use mockforge_core::pillars::{Pillar, PillarMetadata};
276 ///
277 /// let doc = "//! Pillars: [Reality][AI]\n//! This module does something";
278 /// let metadata = PillarMetadata::from_doc_comment(doc).unwrap();
279 /// assert!(metadata.has_pillar(Pillar::Reality));
280 /// assert!(metadata.has_pillar(Pillar::Ai));
281 /// ```
282 pub fn from_doc_comment(doc: &str) -> Option<Self> {
283 // Look for "Pillars:" or "Pillar:" followed by bracket notation
284 let re = regex::Regex::new(r"(?i)(?:pillars?):\s*(\[[^\]]+\])+").ok()?;
285 let caps = re.captures(doc)?;
286 let full_match = caps.get(0)?;
287
288 // Extract all [Pillar] tags
289 let tag_re = regex::Regex::new(r"\[([^\]]+)\]").ok()?;
290 let mut metadata = Self::new();
291
292 for cap in tag_re.captures_iter(full_match.as_str()) {
293 if let Some(pillar_name) = cap.get(1) {
294 if let Some(pillar) = Pillar::from_str(pillar_name.as_str()) {
295 metadata.add_pillar(pillar);
296 }
297 }
298 }
299
300 if metadata.is_empty() {
301 None
302 } else {
303 Some(metadata)
304 }
305 }
306}
307
308impl Default for PillarMetadata {
309 fn default() -> Self {
310 Self::new()
311 }
312}
313
314impl From<Vec<Pillar>> for PillarMetadata {
315 fn from(pillars: Vec<Pillar>) -> Self {
316 Self {
317 pillars: pillars.into_iter().collect(),
318 }
319 }
320}
321
322impl From<&[Pillar]> for PillarMetadata {
323 fn from(pillars: &[Pillar]) -> Self {
324 Self {
325 pillars: pillars.iter().copied().collect(),
326 }
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 #[test]
335 fn test_pillar_as_str() {
336 assert_eq!(Pillar::Reality.as_str(), "reality");
337 assert_eq!(Pillar::Contracts.as_str(), "contracts");
338 assert_eq!(Pillar::DevX.as_str(), "devx");
339 assert_eq!(Pillar::Cloud.as_str(), "cloud");
340 assert_eq!(Pillar::Ai.as_str(), "ai");
341 }
342
343 #[test]
344 fn test_pillar_from_str() {
345 assert_eq!(Pillar::from_str("reality"), Some(Pillar::Reality));
346 assert_eq!(Pillar::from_str("REALITY"), Some(Pillar::Reality));
347 assert_eq!(Pillar::from_str("Contracts"), Some(Pillar::Contracts));
348 assert_eq!(Pillar::from_str("devx"), Some(Pillar::DevX));
349 assert_eq!(Pillar::from_str("invalid"), None);
350 }
351
352 #[test]
353 fn test_pillar_display_name() {
354 assert_eq!(Pillar::Reality.display_name(), "[Reality]");
355 assert_eq!(Pillar::Contracts.display_name(), "[Contracts]");
356 assert_eq!(Pillar::DevX.display_name(), "[DevX]");
357 assert_eq!(Pillar::Cloud.display_name(), "[Cloud]");
358 assert_eq!(Pillar::Ai.display_name(), "[AI]");
359 }
360
361 #[test]
362 fn test_pillar_metadata() {
363 let mut metadata = PillarMetadata::new();
364 assert!(metadata.is_empty());
365
366 metadata.add_pillar(Pillar::Reality);
367 assert!(!metadata.is_empty());
368 assert!(metadata.has_pillar(Pillar::Reality));
369 assert!(!metadata.has_pillar(Pillar::Contracts));
370
371 metadata.add_pillar(Pillar::Ai);
372 assert!(metadata.has_pillar(Pillar::Reality));
373 assert!(metadata.has_pillar(Pillar::Ai));
374 assert_eq!(metadata.pillars().len(), 2);
375 }
376
377 #[test]
378 fn test_pillar_metadata_builder() {
379 let metadata = PillarMetadata::new().with_pillar(Pillar::Reality).with_pillar(Pillar::Ai);
380
381 assert!(metadata.has_pillar(Pillar::Reality));
382 assert!(metadata.has_pillar(Pillar::Ai));
383 assert_eq!(metadata.pillars().len(), 2);
384 }
385
386 #[test]
387 fn test_pillar_metadata_changelog_tags() {
388 let metadata = PillarMetadata::from(vec![Pillar::Reality, Pillar::Ai]);
389 let tags = metadata.to_changelog_tags();
390 // Should be sorted alphabetically: [AI][Reality]
391 assert!(tags.contains("[AI]"));
392 assert!(tags.contains("[Reality]"));
393 }
394
395 #[test]
396 fn test_pillar_metadata_from_doc_comment() {
397 let doc = "//! Pillars: [Reality][AI]\n//! This module does something";
398 let metadata = PillarMetadata::from_doc_comment(doc).unwrap();
399 assert!(metadata.has_pillar(Pillar::Reality));
400 assert!(metadata.has_pillar(Pillar::Ai));
401
402 let doc2 = "/// Pillar: [Contracts]";
403 let metadata2 = PillarMetadata::from_doc_comment(doc2).unwrap();
404 assert!(metadata2.has_pillar(Pillar::Contracts));
405
406 let doc3 = "//! No pillars here";
407 assert!(PillarMetadata::from_doc_comment(doc3).is_none());
408 }
409}