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
314/// Parse pillar tags from a list of scenario tags
315///
316/// Extracts pillar tags in the format `[PillarName]` from a list of tags.
317/// Pillar tags are recognized in formats like:
318/// - `[Cloud]`
319/// - `[Contracts]`
320/// - `[Reality]`
321/// - `[Cloud][Contracts][Reality]` (multiple pillars in one tag)
322///
323/// # Examples
324///
325/// ```
326/// use mockforge_core::pillars::{Pillar, parse_pillar_tags_from_scenario_tags};
327///
328/// let tags = vec!["[Cloud]".to_string(), "auth".to_string(), "[Contracts][Reality]".to_string()];
329/// let pillars = parse_pillar_tags_from_scenario_tags(&tags);
330/// assert!(pillars.contains(&Pillar::Cloud));
331/// assert!(pillars.contains(&Pillar::Contracts));
332/// assert!(pillars.contains(&Pillar::Reality));
333/// assert_eq!(pillars.len(), 3);
334/// ```
335pub fn parse_pillar_tags_from_scenario_tags(tags: &[String]) -> Vec<Pillar> {
336    let mut pillars = Vec::new();
337    let tag_re = regex::Regex::new(r"\[([^\]]+)\]").ok();
338
339    if tag_re.is_none() {
340        return pillars;
341    }
342
343    let tag_re = tag_re.unwrap();
344
345    for tag in tags {
346        // Extract all [PillarName] patterns from the tag
347        for cap in tag_re.captures_iter(tag) {
348            if let Some(pillar_name) = cap.get(1) {
349                if let Some(pillar) = Pillar::from_str(pillar_name.as_str()) {
350                    if !pillars.contains(&pillar) {
351                        pillars.push(pillar);
352                    }
353                }
354            }
355        }
356    }
357
358    pillars
359}
360
361/// Check if a tag string contains pillar tags
362///
363/// Returns true if the tag contains at least one valid pillar tag in bracket notation.
364///
365/// # Examples
366///
367/// ```
368/// use mockforge_core::pillars::has_pillar_tags;
369///
370/// assert!(has_pillar_tags("[Cloud]"));
371/// assert!(has_pillar_tags("[Contracts][Reality]"));
372/// assert!(has_pillar_tags("auth-[Cloud]-test"));
373/// assert!(!has_pillar_tags("auth"));
374/// assert!(!has_pillar_tags("[Invalid]"));
375/// ```
376pub fn has_pillar_tags(tag: &str) -> bool {
377    let tag_re = regex::Regex::new(r"\[([^\]]+)\]").ok();
378
379    if let Some(tag_re) = tag_re {
380        for cap in tag_re.captures_iter(tag) {
381            if let Some(pillar_name) = cap.get(1) {
382                if Pillar::from_str(pillar_name.as_str()).is_some() {
383                    return true;
384                }
385            }
386        }
387    }
388
389    false
390}
391
392/// Extract pillar metadata from scenario tags
393///
394/// Parses all pillar tags from a list of scenario tags and returns them as PillarMetadata.
395///
396/// # Examples
397///
398/// ```
399/// use mockforge_core::pillars::{Pillar, pillar_metadata_from_scenario_tags};
400///
401/// let tags = vec!["[Cloud]".to_string(), "[Contracts]".to_string(), "auth".to_string()];
402/// let metadata = pillar_metadata_from_scenario_tags(&tags);
403/// assert!(metadata.has_pillar(Pillar::Cloud));
404/// assert!(metadata.has_pillar(Pillar::Contracts));
405/// assert!(!metadata.has_pillar(Pillar::Reality));
406/// ```
407pub fn pillar_metadata_from_scenario_tags(tags: &[String]) -> PillarMetadata {
408    let pillars = parse_pillar_tags_from_scenario_tags(tags);
409    PillarMetadata::from(pillars)
410}
411
412impl From<Vec<Pillar>> for PillarMetadata {
413    fn from(pillars: Vec<Pillar>) -> Self {
414        Self {
415            pillars: pillars.into_iter().collect(),
416        }
417    }
418}
419
420impl From<&[Pillar]> for PillarMetadata {
421    fn from(pillars: &[Pillar]) -> Self {
422        Self {
423            pillars: pillars.iter().copied().collect(),
424        }
425    }
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431
432    #[test]
433    fn test_pillar_as_str() {
434        assert_eq!(Pillar::Reality.as_str(), "reality");
435        assert_eq!(Pillar::Contracts.as_str(), "contracts");
436        assert_eq!(Pillar::DevX.as_str(), "devx");
437        assert_eq!(Pillar::Cloud.as_str(), "cloud");
438        assert_eq!(Pillar::Ai.as_str(), "ai");
439    }
440
441    #[test]
442    fn test_pillar_from_str() {
443        assert_eq!(Pillar::from_str("reality"), Some(Pillar::Reality));
444        assert_eq!(Pillar::from_str("REALITY"), Some(Pillar::Reality));
445        assert_eq!(Pillar::from_str("Contracts"), Some(Pillar::Contracts));
446        assert_eq!(Pillar::from_str("devx"), Some(Pillar::DevX));
447        assert_eq!(Pillar::from_str("invalid"), None);
448    }
449
450    #[test]
451    fn test_pillar_display_name() {
452        assert_eq!(Pillar::Reality.display_name(), "[Reality]");
453        assert_eq!(Pillar::Contracts.display_name(), "[Contracts]");
454        assert_eq!(Pillar::DevX.display_name(), "[DevX]");
455        assert_eq!(Pillar::Cloud.display_name(), "[Cloud]");
456        assert_eq!(Pillar::Ai.display_name(), "[AI]");
457    }
458
459    #[test]
460    fn test_pillar_metadata() {
461        let mut metadata = PillarMetadata::new();
462        assert!(metadata.is_empty());
463
464        metadata.add_pillar(Pillar::Reality);
465        assert!(!metadata.is_empty());
466        assert!(metadata.has_pillar(Pillar::Reality));
467        assert!(!metadata.has_pillar(Pillar::Contracts));
468
469        metadata.add_pillar(Pillar::Ai);
470        assert!(metadata.has_pillar(Pillar::Reality));
471        assert!(metadata.has_pillar(Pillar::Ai));
472        assert_eq!(metadata.pillars().len(), 2);
473    }
474
475    #[test]
476    fn test_pillar_metadata_builder() {
477        let metadata = PillarMetadata::new().with_pillar(Pillar::Reality).with_pillar(Pillar::Ai);
478
479        assert!(metadata.has_pillar(Pillar::Reality));
480        assert!(metadata.has_pillar(Pillar::Ai));
481        assert_eq!(metadata.pillars().len(), 2);
482    }
483
484    #[test]
485    fn test_pillar_metadata_changelog_tags() {
486        let metadata = PillarMetadata::from(vec![Pillar::Reality, Pillar::Ai]);
487        let tags = metadata.to_changelog_tags();
488        // Should be sorted alphabetically: [AI][Reality]
489        assert!(tags.contains("[AI]"));
490        assert!(tags.contains("[Reality]"));
491    }
492
493    #[test]
494    fn test_pillar_metadata_from_doc_comment() {
495        let doc = "//! Pillars: [Reality][AI]\n//! This module does something";
496        let metadata = PillarMetadata::from_doc_comment(doc).unwrap();
497        assert!(metadata.has_pillar(Pillar::Reality));
498        assert!(metadata.has_pillar(Pillar::Ai));
499
500        let doc2 = "/// Pillar: [Contracts]";
501        let metadata2 = PillarMetadata::from_doc_comment(doc2).unwrap();
502        assert!(metadata2.has_pillar(Pillar::Contracts));
503
504        let doc3 = "//! No pillars here";
505        assert!(PillarMetadata::from_doc_comment(doc3).is_none());
506    }
507
508    #[test]
509    fn test_parse_pillar_tags_from_scenario_tags() {
510        use super::{parse_pillar_tags_from_scenario_tags, Pillar};
511
512        let tags = vec![
513            "[Cloud]".to_string(),
514            "auth".to_string(),
515            "[Contracts][Reality]".to_string(),
516        ];
517        let pillars = parse_pillar_tags_from_scenario_tags(&tags);
518        assert!(pillars.contains(&Pillar::Cloud));
519        assert!(pillars.contains(&Pillar::Contracts));
520        assert!(pillars.contains(&Pillar::Reality));
521        assert_eq!(pillars.len(), 3);
522
523        let tags2 = vec!["normal".to_string(), "test".to_string()];
524        let pillars2 = parse_pillar_tags_from_scenario_tags(&tags2);
525        assert!(pillars2.is_empty());
526
527        let tags3 = vec!["[Cloud][Contracts][Reality]".to_string()];
528        let pillars3 = parse_pillar_tags_from_scenario_tags(&tags3);
529        assert_eq!(pillars3.len(), 3);
530        assert!(pillars3.contains(&Pillar::Cloud));
531        assert!(pillars3.contains(&Pillar::Contracts));
532        assert!(pillars3.contains(&Pillar::Reality));
533    }
534
535    #[test]
536    fn test_has_pillar_tags() {
537        use super::has_pillar_tags;
538
539        assert!(has_pillar_tags("[Cloud]"));
540        assert!(has_pillar_tags("[Contracts][Reality]"));
541        assert!(has_pillar_tags("auth-[Cloud]-test"));
542        assert!(!has_pillar_tags("auth"));
543        assert!(!has_pillar_tags("[Invalid]"));
544        assert!(!has_pillar_tags(""));
545    }
546
547    #[test]
548    fn test_pillar_metadata_from_scenario_tags() {
549        use super::{pillar_metadata_from_scenario_tags, Pillar};
550
551        let tags = vec![
552            "[Cloud]".to_string(),
553            "[Contracts]".to_string(),
554            "auth".to_string(),
555        ];
556        let metadata = pillar_metadata_from_scenario_tags(&tags);
557        assert!(metadata.has_pillar(Pillar::Cloud));
558        assert!(metadata.has_pillar(Pillar::Contracts));
559        assert!(!metadata.has_pillar(Pillar::Reality));
560
561        let tags2 = vec!["[Cloud][Contracts][Reality]".to_string()];
562        let metadata2 = pillar_metadata_from_scenario_tags(&tags2);
563        assert!(metadata2.has_pillar(Pillar::Cloud));
564        assert!(metadata2.has_pillar(Pillar::Contracts));
565        assert!(metadata2.has_pillar(Pillar::Reality));
566    }
567}