Skip to main content

mockforge_intelligence/voice/
command_parser.rs

1//! LLM-based command parser for voice commands
2//!
3//! This module parses natural language voice commands and extracts API requirements
4//! using MockForge's LLM infrastructure.
5
6use crate::intelligent_behavior::{
7    config::IntelligentBehaviorConfig, llm_client::LlmClient, types::LlmGenerationRequest,
8};
9use mockforge_foundation::Result;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13/// Voice command parser that uses LLM to interpret natural language commands
14pub struct VoiceCommandParser {
15    /// LLM client for parsing commands
16    llm_client: LlmClient,
17    /// Configuration
18    #[allow(dead_code)]
19    config: IntelligentBehaviorConfig,
20}
21
22impl VoiceCommandParser {
23    /// Create a new voice command parser
24    pub fn new(config: IntelligentBehaviorConfig) -> Self {
25        let behavior_model = config.behavior_model.clone();
26        let llm_client = LlmClient::new(behavior_model);
27
28        Self { llm_client, config }
29    }
30
31    /// Parse a natural language command into structured API requirements
32    ///
33    /// This method uses the LLM to extract:
34    /// - API type (e-commerce, social media, etc.)
35    /// - Endpoints and HTTP methods
36    /// - Data models and relationships
37    /// - Sample data counts
38    /// - Business flows (checkout, auth, etc.)
39    pub async fn parse_command(&self, command: &str) -> Result<ParsedCommand> {
40        // Build system prompt for command parsing
41        let system_prompt = r#"You are an expert API designer. Your task is to parse natural language commands
42that describe API requirements and extract structured information.
43
44Extract the following information from the command:
451. API type/category (e.g., e-commerce, social media, blog, todo app)
462. Endpoints with HTTP methods (GET, POST, PUT, DELETE, PATCH)
473. Data models with fields and types
484. Relationships between models
495. Sample data counts (e.g., "20 products")
506. Business flows (e.g., checkout, authentication, user registration)
51
52Return your response as a JSON object with this structure:
53{
54  "api_type": "string (e.g., e-commerce, social-media, blog)",
55  "title": "string (API title)",
56  "description": "string (API description)",
57  "endpoints": [
58    {
59      "path": "string (e.g., /api/products)",
60      "method": "string (GET, POST, PUT, DELETE, PATCH)",
61      "description": "string",
62      "request_body": {
63        "schema": "object schema if applicable",
64        "required": ["array of required fields"]
65      },
66      "response": {
67        "status": 200,
68        "schema": "object schema",
69        "is_array": false,
70        "count": null or number if specified
71      }
72    }
73  ],
74  "models": [
75    {
76      "name": "string (e.g., Product)",
77      "fields": [
78        {
79          "name": "string",
80          "type": "string (string, number, integer, boolean, array, object)",
81          "description": "string",
82          "required": true
83        }
84      ]
85    }
86  ],
87  "relationships": [
88    {
89      "from": "string (model name)",
90      "to": "string (model name)",
91      "type": "string (one-to-many, many-to-many, one-to-one)"
92    }
93  ],
94  "sample_counts": {
95    "model_name": number
96  },
97  "flows": [
98    {
99      "name": "string (e.g., checkout)",
100      "description": "string",
101      "steps": ["array of step descriptions"]
102    }
103  ]
104}
105
106Be specific and extract all details mentioned in the command. If something is not mentioned,
107don't include it in the response."#;
108
109        // Build user prompt with the command
110        let user_prompt =
111            format!("Parse this API creation command and extract all requirements:\n\n{}", command);
112
113        // Create LLM request
114        let llm_request = LlmGenerationRequest {
115            system_prompt: system_prompt.to_string(),
116            user_prompt,
117            temperature: 0.3, // Lower temperature for more consistent parsing
118            max_tokens: 2000,
119            schema: None,
120            seed: None,
121        };
122
123        // Generate response from LLM
124        let response = self.llm_client.generate(&llm_request).await?;
125
126        // Parse the response into ParsedCommand
127        let response_str = serde_json::to_string(&response).unwrap_or_default();
128        let parsed: ParsedCommand = serde_json::from_value(response).map_err(|e| {
129            mockforge_foundation::Error::config(format!(
130                "Failed to parse LLM response as ParsedCommand: {}. Response: {}",
131                e, response_str
132            ))
133        })?;
134
135        // Record the voice-command event so the AI pillar dashboard reflects usage.
136        mockforge_foundation::pillar_tracking::track_ai_pillar_telemetry(
137            None,
138            None,
139            "voice_command",
140            serde_json::json!({
141                "kind": "parse_command",
142                "api_type": parsed.api_type,
143                "endpoints": parsed.endpoints.len(),
144            }),
145        )
146        .await;
147
148        Ok(parsed)
149    }
150
151    /// Parse a conversational command (for multi-turn interactions)
152    ///
153    /// This method parses commands that modify or extend an existing API specification.
154    /// It takes the current conversation context into account.
155    pub async fn parse_conversational_command(
156        &self,
157        command: &str,
158        context: &super::conversation::ConversationContext,
159    ) -> Result<ParsedCommand> {
160        // Build system prompt for conversational parsing
161        let system_prompt = r#"You are an expert API designer helping to build an API through conversation.
162The user is providing incremental commands to modify or extend an existing API specification.
163
164Extract the following information from the command:
1651. What is being added/modified (endpoints, models, flows)
1662. Details about the addition/modification
1673. Any relationships or dependencies
168
169Return your response as a JSON object with the same structure as parse_command, but focus only
170on what is NEW or MODIFIED. If the command is asking to add something, include it. If it's asking
171to modify something, include the modified version.
172
173If the command is asking a question or requesting confirmation, return an empty endpoints array
174and include a "question" or "confirmation" field in the response."#;
175
176        // Build context summary
177        let context_summary = format!(
178            "Current API: {}\nExisting endpoints: {}\nExisting models: {}",
179            context.current_spec.as_ref().map(|s| s.title()).unwrap_or("None"),
180            context
181                .current_spec
182                .as_ref()
183                .map(|s| {
184                    s.all_paths_and_operations()
185                        .iter()
186                        .map(|(path, ops)| {
187                            format!(
188                                "{} ({})",
189                                path,
190                                ops.keys().map(|s| s.as_str()).collect::<Vec<_>>().join(", ")
191                            )
192                        })
193                        .collect::<Vec<_>>()
194                        .join(", ")
195                })
196                .unwrap_or_else(|| "None".to_string()),
197            context
198                .current_spec
199                .as_ref()
200                .and_then(|s| s.spec.components.as_ref())
201                .map(|c| c.schemas.keys().cloned().collect::<Vec<_>>().join(", "))
202                .unwrap_or_else(|| "None".to_string())
203        );
204
205        // Build user prompt
206        let user_prompt = format!("Context:\n{}\n\nNew command:\n{}", context_summary, command);
207
208        // Create LLM request
209        let llm_request = LlmGenerationRequest {
210            system_prompt: system_prompt.to_string(),
211            user_prompt,
212            temperature: 0.3,
213            max_tokens: 2000,
214            schema: None,
215            seed: None,
216        };
217
218        // Generate response
219        let response = self.llm_client.generate(&llm_request).await?;
220
221        // Parse response
222        let response_str = serde_json::to_string(&response).unwrap_or_default();
223        let parsed: ParsedCommand = serde_json::from_value(response).map_err(|e| {
224            mockforge_foundation::Error::config(format!(
225                "Failed to parse conversational LLM response: {}. Response: {}",
226                e, response_str
227            ))
228        })?;
229
230        Ok(parsed)
231    }
232
233    /// Parse a workspace scenario description
234    ///
235    /// This method extracts information about creating a complete workspace scenario,
236    /// including domain, chaos characteristics, initial data, and API requirements.
237    pub async fn parse_workspace_scenario_command(
238        &self,
239        command: &str,
240    ) -> Result<ParsedWorkspaceScenario> {
241        // Build system prompt for workspace scenario parsing
242        let system_prompt = r#"You are an expert at parsing natural language descriptions of workspace scenarios
243and extracting structured information for creating complete mock environments.
244
245Extract the following information from the command:
2461. Domain/industry (e.g., bank, e-commerce, healthcare, etc.)
2472. Chaos/failure characteristics (flaky rates, slow KYC, high latency, etc.)
2483. Initial data requirements (number of users, disputes, orders, etc.)
2494. API endpoints needed for the domain
2505. Behavioral rules (failure rates, latency patterns, etc.)
2516. Data models and relationships
252
253Return your response as a JSON object with this structure:
254{
255  "domain": "string (e.g., bank, e-commerce, healthcare)",
256  "title": "string (workspace title)",
257  "description": "string (workspace description)",
258  "chaos_characteristics": [
259    {
260      "type": "string (latency|failure|rate_limit|etc.)",
261      "description": "string (e.g., flaky foreign exchange rates)",
262      "config": {
263        "probability": 0.0-1.0,
264        "delay_ms": number,
265        "error_rate": 0.0-1.0,
266        "error_codes": [500, 502, 503],
267        "details": "additional configuration details"
268      }
269    }
270  ],
271  "initial_data": {
272    "users": number,
273    "disputes": number,
274    "orders": number,
275    "custom": {
276      "entity_name": number
277    }
278  },
279  "api_requirements": {
280    "endpoints": [
281      {
282        "path": "string",
283        "method": "string",
284        "description": "string"
285      }
286    ],
287    "models": [
288      {
289        "name": "string",
290        "fields": [
291          {
292            "name": "string",
293            "type": "string"
294          }
295        ]
296      }
297    ]
298  },
299  "behavioral_rules": [
300    {
301      "description": "string",
302      "type": "string",
303      "config": {}
304    }
305  ]
306}
307
308Be specific and extract all details mentioned in the command."#;
309
310        // Build user prompt with the command
311        let user_prompt = format!(
312            "Parse this workspace scenario description and extract all requirements:\n\n{}",
313            command
314        );
315
316        // Create LLM request
317        let llm_request = LlmGenerationRequest {
318            system_prompt: system_prompt.to_string(),
319            user_prompt,
320            temperature: 0.3,
321            max_tokens: 3000,
322            schema: None,
323            seed: None,
324        };
325
326        // Generate response from LLM
327        let response = self.llm_client.generate(&llm_request).await?;
328
329        // Parse the response into ParsedWorkspaceScenario
330        let response_str = serde_json::to_string(&response).unwrap_or_default();
331        let parsed: ParsedWorkspaceScenario = serde_json::from_value(response).map_err(|e| {
332            mockforge_foundation::Error::config(format!(
333                "Failed to parse LLM response as ParsedWorkspaceScenario: {}. Response: {}",
334                e, response_str
335            ))
336        })?;
337
338        Ok(parsed)
339    }
340
341    /// Parse a workspace creation command
342    ///
343    /// This method extracts information about creating a complete workspace including:
344    /// - Workspace name and description
345    /// - Entities (customers, orders, payments, etc.)
346    /// - Personas with relationships
347    /// - Behavioral scenarios (happy path, failure, slow path)
348    /// - Reality continuum preferences
349    /// - Drift budget preferences
350    pub async fn parse_workspace_creation_command(
351        &self,
352        command: &str,
353    ) -> Result<ParsedWorkspaceCreation> {
354        // Build system prompt for workspace creation parsing
355        let system_prompt = r#"You are an expert at parsing natural language descriptions of workspace creation
356and extracting structured information for creating complete mock backends with personas, scenarios, and configuration.
357
358Extract the following information from the command:
3591. Workspace name and description
3602. Entities (customers, orders, payments, products, etc.)
3613. Personas with their traits and relationships (e.g., customer owns orders)
3624. Behavioral scenarios:
363   - Happy path scenarios (successful flows)
364   - Failure path scenarios (error cases)
365   - Slow path scenarios (latency/performance issues)
3665. Reality continuum preferences (e.g., "80% mock, 20% real prod for catalog only")
3676. Drift budget preferences (e.g., "strict drift budget", "moderate tolerance")
368
369Return your response as a JSON object with this structure:
370{
371  "workspace_name": "string (e.g., e-commerce-workspace)",
372  "workspace_description": "string",
373  "entities": [
374    {
375      "name": "string (e.g., Customer, Order, Payment)",
376      "description": "string",
377      "endpoints": [
378        {
379          "path": "string",
380          "method": "string",
381          "description": "string"
382        }
383      ],
384      "fields": [
385        {
386          "name": "string",
387          "type": "string",
388          "description": "string"
389        }
390      ]
391    }
392  ],
393  "personas": [
394    {
395      "name": "string (e.g., premium-customer, regular-customer)",
396      "description": "string",
397      "traits": {
398        "trait_name": "trait_value"
399      },
400      "relationships": [
401        {
402          "type": "string (e.g., owns, belongs_to, has)",
403          "target_entity": "string (e.g., Order, Payment)"
404        }
405      ]
406    }
407  ],
408  "scenarios": [
409    {
410      "name": "string (e.g., happy-path-checkout, failed-payment, slow-shipping)",
411      "type": "string (happy_path|failure|slow_path)",
412      "description": "string",
413      "steps": [
414        {
415          "description": "string (e.g., Create order, Process payment)",
416          "endpoint": "string (e.g., POST /api/orders)",
417          "expected_outcome": "string"
418        }
419      ]
420    }
421  ],
422  "reality_continuum": {
423    "default_ratio": 0.0-1.0 (0.0 = 100% mock, 1.0 = 100% real),
424    "route_rules": [
425      {
426        "pattern": "string (e.g., /api/catalog/*)",
427        "ratio": 0.0-1.0,
428        "description": "string"
429      }
430    ],
431    "transition_mode": "string (manual|time_based|scheduled)"
432  },
433  "drift_budget": {
434    "strictness": "string (strict|moderate|lenient)",
435    "max_breaking_changes": number,
436    "max_non_breaking_changes": number,
437    "description": "string"
438  }
439}
440
441Be specific and extract all details mentioned in the command. Ensure at least 2-3 endpoints per entity,
4422-3 personas with relationships, and 2-3 behavioral scenarios."#;
443
444        // Build user prompt with the command
445        let user_prompt = format!(
446            "Parse this workspace creation command and extract all requirements:\n\n{}",
447            command
448        );
449
450        // Create LLM request
451        let llm_request = LlmGenerationRequest {
452            system_prompt: system_prompt.to_string(),
453            user_prompt,
454            temperature: 0.3,
455            max_tokens: 4000,
456            schema: None,
457            seed: None,
458        };
459
460        // Generate response from LLM
461        let response = self.llm_client.generate(&llm_request).await?;
462
463        // Parse the response into ParsedWorkspaceCreation
464        let response_str = serde_json::to_string(&response).unwrap_or_default();
465        let parsed: ParsedWorkspaceCreation = serde_json::from_value(response).map_err(|e| {
466            mockforge_foundation::Error::config(format!(
467                "Failed to parse LLM response as ParsedWorkspaceCreation: {}. Response: {}",
468                e, response_str
469            ))
470        })?;
471
472        Ok(parsed)
473    }
474
475    /// Parse a reality continuum configuration command
476    ///
477    /// This method extracts reality continuum preferences from natural language,
478    /// such as "80% mock, 20% real prod for catalog only".
479    pub async fn parse_reality_continuum_command(
480        &self,
481        command: &str,
482    ) -> Result<ParsedRealityContinuum> {
483        // Build system prompt for reality continuum parsing
484        let system_prompt = r#"You are an expert at parsing natural language descriptions of reality continuum
485configuration and extracting structured blend ratio settings.
486
487Extract the following information from the command:
4881. Default blend ratio (e.g., "80% mock, 20% real" means ratio 0.2)
4892. Route-specific rules (e.g., "catalog only", "for /api/products/*")
4903. Transition mode preferences (manual, time-based, scheduled)
491
492Return your response as a JSON object with this structure:
493{
494  "default_ratio": 0.0-1.0 (0.0 = 100% mock, 1.0 = 100% real),
495  "enabled": true/false,
496  "route_rules": [
497    {
498      "pattern": "string (e.g., /api/catalog/*, /api/products/*)",
499      "ratio": 0.0-1.0,
500      "description": "string"
501    }
502  ],
503  "transition_mode": "string (manual|time_based|scheduled)",
504  "merge_strategy": "string (field_level|weighted|body_blend)"
505}
506
507Examples:
508- "80% mock, 20% real" → default_ratio: 0.2
509- "Make catalog 50% real" → route_rules: [{pattern: "/api/catalog/*", ratio: 0.5}]
510- "100% mock for now" → default_ratio: 0.0, enabled: true"#;
511
512        // Build user prompt with the command
513        let user_prompt =
514            format!("Parse this reality continuum configuration command:\n\n{}", command);
515
516        // Create LLM request
517        let llm_request = LlmGenerationRequest {
518            system_prompt: system_prompt.to_string(),
519            user_prompt,
520            temperature: 0.3,
521            max_tokens: 2000,
522            schema: None,
523            seed: None,
524        };
525
526        // Generate response from LLM
527        let response = self.llm_client.generate(&llm_request).await?;
528
529        // Parse the response into ParsedRealityContinuum
530        let response_str = serde_json::to_string(&response).unwrap_or_default();
531        let parsed: ParsedRealityContinuum = serde_json::from_value(response).map_err(|e| {
532            mockforge_foundation::Error::config(format!(
533                "Failed to parse LLM response as ParsedRealityContinuum: {}. Response: {}",
534                e, response_str
535            ))
536        })?;
537
538        Ok(parsed)
539    }
540
541    /// Parse a drift budget configuration command
542    ///
543    /// This method extracts drift budget preferences from natural language,
544    /// such as "strict drift budget" or "moderate tolerance for changes".
545    pub async fn parse_drift_budget_command(&self, command: &str) -> Result<ParsedDriftBudget> {
546        // Build system prompt for drift budget parsing
547        let system_prompt = r#"You are an expert at parsing natural language descriptions of drift budget
548configuration and extracting structured budget settings.
549
550Extract the following information from the command:
5511. Strictness level (strict, moderate, lenient)
5522. Breaking change tolerance
5533. Non-breaking change tolerance
5544. Per-service/endpoint preferences
555
556Return your response as a JSON object with this structure:
557{
558  "strictness": "string (strict|moderate|lenient)",
559  "enabled": true/false,
560  "max_breaking_changes": number (0 for strict, higher for lenient),
561  "max_non_breaking_changes": number,
562  "max_field_churn_percent": number (0.0-100.0, optional),
563  "time_window_days": number (optional, for percentage-based budgets),
564  "per_service_budgets": {
565    "service_name": {
566      "max_breaking_changes": number,
567      "max_non_breaking_changes": number
568    }
569  },
570  "description": "string"
571}
572
573Examples:
574- "strict drift budget" → strictness: "strict", max_breaking_changes: 0, max_non_breaking_changes: 5
575- "moderate tolerance" → strictness: "moderate", max_breaking_changes: 1, max_non_breaking_changes: 10
576- "lenient, allow up to 5 breaking changes" → strictness: "lenient", max_breaking_changes: 5"#;
577
578        // Build user prompt with the command
579        let user_prompt = format!("Parse this drift budget configuration command:\n\n{}", command);
580
581        // Create LLM request
582        let llm_request = LlmGenerationRequest {
583            system_prompt: system_prompt.to_string(),
584            user_prompt,
585            temperature: 0.3,
586            max_tokens: 2000,
587            schema: None,
588            seed: None,
589        };
590
591        // Generate response from LLM
592        let response = self.llm_client.generate(&llm_request).await?;
593
594        // Parse the response into ParsedDriftBudget
595        let response_str = serde_json::to_string(&response).unwrap_or_default();
596        let parsed: ParsedDriftBudget = serde_json::from_value(response).map_err(|e| {
597            mockforge_foundation::Error::config(format!(
598                "Failed to parse LLM response as ParsedDriftBudget: {}. Response: {}",
599                e, response_str
600            ))
601        })?;
602
603        Ok(parsed)
604    }
605}
606
607/// Parsed command structure containing extracted API requirements
608#[derive(Debug, Clone, Serialize, Deserialize)]
609pub struct ParsedCommand {
610    /// API type/category
611    pub api_type: String,
612    /// API title
613    pub title: String,
614    /// API description
615    pub description: String,
616    /// List of endpoints
617    pub endpoints: Vec<EndpointRequirement>,
618    /// List of data models
619    pub models: Vec<ModelRequirement>,
620    /// Relationships between models
621    #[serde(default)]
622    pub relationships: Vec<RelationshipRequirement>,
623    /// Sample data counts per model
624    #[serde(default)]
625    pub sample_counts: HashMap<String, usize>,
626    /// Business flows
627    #[serde(default)]
628    pub flows: Vec<FlowRequirement>,
629}
630
631/// Endpoint requirement extracted from command
632#[derive(Debug, Clone, Serialize, Deserialize)]
633pub struct EndpointRequirement {
634    /// Path (e.g., /api/products)
635    pub path: String,
636    /// HTTP method
637    pub method: String,
638    /// Description
639    pub description: String,
640    /// Request body schema (if applicable)
641    #[serde(default)]
642    pub request_body: Option<RequestBodyRequirement>,
643    /// Response schema
644    #[serde(default)]
645    pub response: Option<ResponseRequirement>,
646}
647
648/// Request body requirement
649#[derive(Debug, Clone, Serialize, Deserialize)]
650pub struct RequestBodyRequirement {
651    /// Schema definition
652    #[serde(default)]
653    pub schema: Option<serde_json::Value>,
654    /// Required fields
655    #[serde(default)]
656    pub required: Vec<String>,
657}
658
659/// Response requirement
660#[derive(Debug, Clone, Serialize, Deserialize)]
661pub struct ResponseRequirement {
662    /// HTTP status code
663    #[serde(default = "default_status")]
664    pub status: u16,
665    /// Response schema
666    #[serde(default)]
667    pub schema: Option<serde_json::Value>,
668    /// Whether response is an array
669    #[serde(default)]
670    pub is_array: bool,
671    /// Count of items (if specified)
672    #[serde(default)]
673    pub count: Option<usize>,
674}
675
676fn default_status() -> u16 {
677    200
678}
679
680/// Model requirement extracted from command
681#[derive(Debug, Clone, Serialize, Deserialize)]
682pub struct ModelRequirement {
683    /// Model name
684    pub name: String,
685    /// List of fields
686    pub fields: Vec<FieldRequirement>,
687}
688
689/// Field requirement
690#[derive(Debug, Clone, Serialize, Deserialize)]
691pub struct FieldRequirement {
692    /// Field name
693    pub name: String,
694    /// Field type
695    pub r#type: String,
696    /// Field description
697    #[serde(default)]
698    pub description: String,
699    /// Whether field is required
700    #[serde(default = "default_true")]
701    pub required: bool,
702}
703
704fn default_true() -> bool {
705    true
706}
707
708/// Relationship requirement
709#[derive(Debug, Clone, Serialize, Deserialize)]
710pub struct RelationshipRequirement {
711    /// Source model
712    pub from: String,
713    /// Target model
714    pub to: String,
715    /// Relationship type
716    pub r#type: String,
717}
718
719/// Flow requirement
720#[derive(Debug, Clone, Serialize, Deserialize)]
721pub struct FlowRequirement {
722    /// Flow name
723    pub name: String,
724    /// Flow description
725    pub description: String,
726    /// Steps in the flow
727    #[serde(default)]
728    pub steps: Vec<String>,
729}
730
731/// Alias for API requirement (for backwards compatibility)
732pub type ApiRequirement = ParsedCommand;
733
734/// Parsed workspace scenario structure
735#[derive(Debug, Clone, Serialize, Deserialize)]
736pub struct ParsedWorkspaceScenario {
737    /// Domain/industry
738    pub domain: String,
739    /// Workspace title
740    pub title: String,
741    /// Workspace description
742    pub description: String,
743    /// Chaos characteristics
744    #[serde(default)]
745    pub chaos_characteristics: Vec<ChaosCharacteristic>,
746    /// Initial data requirements
747    #[serde(default)]
748    pub initial_data: InitialDataRequirements,
749    /// API requirements
750    #[serde(default)]
751    pub api_requirements: ApiRequirements,
752    /// Behavioral rules
753    #[serde(default)]
754    pub behavioral_rules: Vec<BehavioralRule>,
755}
756
757/// Chaos characteristic
758#[derive(Debug, Clone, Serialize, Deserialize)]
759pub struct ChaosCharacteristic {
760    /// Type of chaos (latency, failure, rate_limit, etc.)
761    pub r#type: String,
762    /// Description
763    pub description: String,
764    /// Configuration details
765    #[serde(default)]
766    pub config: serde_json::Value,
767}
768
769/// Initial data requirements
770#[derive(Debug, Clone, Serialize, Deserialize, Default)]
771pub struct InitialDataRequirements {
772    /// Number of users
773    #[serde(default)]
774    pub users: Option<usize>,
775    /// Number of disputes
776    #[serde(default)]
777    pub disputes: Option<usize>,
778    /// Number of orders
779    #[serde(default)]
780    pub orders: Option<usize>,
781    /// Custom entity counts
782    #[serde(default)]
783    pub custom: HashMap<String, usize>,
784}
785
786/// API requirements for the scenario
787#[derive(Debug, Clone, Serialize, Deserialize, Default)]
788pub struct ApiRequirements {
789    /// List of endpoints
790    #[serde(default)]
791    pub endpoints: Vec<EndpointRequirement>,
792    /// List of models
793    #[serde(default)]
794    pub models: Vec<ModelRequirement>,
795}
796
797/// Behavioral rule
798#[derive(Debug, Clone, Serialize, Deserialize)]
799pub struct BehavioralRule {
800    /// Rule description
801    pub description: String,
802    /// Rule type
803    pub r#type: String,
804    /// Rule configuration
805    #[serde(default)]
806    pub config: serde_json::Value,
807}
808
809/// Parsed workspace creation structure
810#[derive(Debug, Clone, Serialize, Deserialize)]
811pub struct ParsedWorkspaceCreation {
812    /// Workspace name
813    pub workspace_name: String,
814    /// Workspace description
815    pub workspace_description: String,
816    /// List of entities
817    #[serde(default)]
818    pub entities: Vec<EntityRequirement>,
819    /// List of personas
820    #[serde(default)]
821    pub personas: Vec<PersonaRequirement>,
822    /// List of behavioral scenarios
823    #[serde(default)]
824    pub scenarios: Vec<ScenarioRequirement>,
825    /// Reality continuum preferences
826    #[serde(default)]
827    pub reality_continuum: Option<ParsedRealityContinuum>,
828    /// Drift budget preferences
829    #[serde(default)]
830    pub drift_budget: Option<ParsedDriftBudget>,
831}
832
833/// Entity requirement for workspace creation
834#[derive(Debug, Clone, Serialize, Deserialize)]
835pub struct EntityRequirement {
836    /// Entity name (e.g., Customer, Order, Payment)
837    pub name: String,
838    /// Entity description
839    pub description: String,
840    /// Endpoints for this entity
841    #[serde(default)]
842    pub endpoints: Vec<EntityEndpointRequirement>,
843    /// Fields for this entity
844    #[serde(default)]
845    pub fields: Vec<FieldRequirement>,
846}
847
848/// Endpoint requirement for an entity
849#[derive(Debug, Clone, Serialize, Deserialize)]
850pub struct EntityEndpointRequirement {
851    /// Path (e.g., /api/customers)
852    pub path: String,
853    /// HTTP method
854    pub method: String,
855    /// Description
856    pub description: String,
857}
858
859/// Persona requirement for workspace creation
860#[derive(Debug, Clone, Serialize, Deserialize)]
861pub struct PersonaRequirement {
862    /// Persona name (e.g., premium-customer, regular-customer)
863    pub name: String,
864    /// Persona description
865    pub description: String,
866    /// Persona traits
867    #[serde(default)]
868    pub traits: HashMap<String, String>,
869    /// Relationships to other entities
870    #[serde(default)]
871    pub relationships: Vec<PersonaRelationship>,
872}
873
874/// Persona relationship
875#[derive(Debug, Clone, Serialize, Deserialize)]
876pub struct PersonaRelationship {
877    /// Relationship type (e.g., owns, belongs_to, has)
878    pub r#type: String,
879    /// Target entity name
880    pub target_entity: String,
881}
882
883/// Scenario requirement for workspace creation
884#[derive(Debug, Clone, Serialize, Deserialize)]
885pub struct ScenarioRequirement {
886    /// Scenario name (e.g., happy-path-checkout, failed-payment)
887    pub name: String,
888    /// Scenario type (happy_path, failure, slow_path)
889    pub r#type: String,
890    /// Scenario description
891    pub description: String,
892    /// Steps in the scenario
893    #[serde(default)]
894    pub steps: Vec<ScenarioStepRequirement>,
895}
896
897/// Scenario step requirement
898#[derive(Debug, Clone, Serialize, Deserialize)]
899pub struct ScenarioStepRequirement {
900    /// Step description
901    pub description: String,
902    /// Endpoint for this step (e.g., POST /api/orders)
903    pub endpoint: String,
904    /// Expected outcome
905    pub expected_outcome: String,
906}
907
908/// Parsed reality continuum configuration
909#[derive(Debug, Clone, Serialize, Deserialize)]
910pub struct ParsedRealityContinuum {
911    /// Default blend ratio (0.0 = 100% mock, 1.0 = 100% real)
912    #[serde(default = "default_blend_ratio")]
913    pub default_ratio: f64,
914    /// Whether reality continuum is enabled
915    #[serde(default = "default_true")]
916    pub enabled: bool,
917    /// Route-specific rules
918    #[serde(default)]
919    pub route_rules: Vec<ParsedContinuumRule>,
920    /// Transition mode
921    #[serde(default)]
922    pub transition_mode: String,
923    /// Merge strategy
924    #[serde(default)]
925    pub merge_strategy: String,
926}
927
928fn default_blend_ratio() -> f64 {
929    0.0
930}
931
932/// Parsed continuum rule
933#[derive(Debug, Clone, Serialize, Deserialize)]
934pub struct ParsedContinuumRule {
935    /// Path pattern (e.g., /api/catalog/*)
936    pub pattern: String,
937    /// Blend ratio for this route
938    pub ratio: f64,
939    /// Description
940    #[serde(default)]
941    pub description: String,
942}
943
944/// Parsed drift budget configuration
945#[derive(Debug, Clone, Serialize, Deserialize)]
946pub struct ParsedDriftBudget {
947    /// Strictness level (strict, moderate, lenient)
948    pub strictness: String,
949    /// Whether drift budget is enabled
950    #[serde(default = "default_true")]
951    pub enabled: bool,
952    /// Maximum breaking changes allowed
953    #[serde(default)]
954    pub max_breaking_changes: u32,
955    /// Maximum non-breaking changes allowed
956    #[serde(default)]
957    pub max_non_breaking_changes: u32,
958    /// Maximum field churn percentage (optional)
959    #[serde(default, skip_serializing_if = "Option::is_none")]
960    pub max_field_churn_percent: Option<f64>,
961    /// Time window in days (optional)
962    #[serde(default, skip_serializing_if = "Option::is_none")]
963    pub time_window_days: Option<u32>,
964    /// Per-service budgets
965    #[serde(default)]
966    pub per_service_budgets: HashMap<String, ParsedServiceBudget>,
967    /// Description
968    #[serde(default)]
969    pub description: String,
970}
971
972/// Parsed service budget
973#[derive(Debug, Clone, Serialize, Deserialize)]
974pub struct ParsedServiceBudget {
975    /// Maximum breaking changes for this service
976    #[serde(default)]
977    pub max_breaking_changes: u32,
978    /// Maximum non-breaking changes for this service
979    #[serde(default)]
980    pub max_non_breaking_changes: u32,
981}
982
983#[cfg(test)]
984mod tests {
985    use super::*;
986    use crate::intelligent_behavior::config::IntelligentBehaviorConfig;
987    use serde_json::json;
988
989    #[test]
990    fn test_voice_command_parser_new() {
991        let config = IntelligentBehaviorConfig::default();
992        let _parser = VoiceCommandParser::new(config);
993        // Just verify it doesn't panic and creates the parser
994        // The llm_client is private, so we can't directly test it
995    }
996
997    #[test]
998    fn test_parsed_command_creation() {
999        let command = ParsedCommand {
1000            api_type: "e-commerce".to_string(),
1001            title: "Shop API".to_string(),
1002            description: "An e-commerce API".to_string(),
1003            endpoints: vec![],
1004            models: vec![],
1005            relationships: vec![],
1006            sample_counts: HashMap::new(),
1007            flows: vec![],
1008        };
1009
1010        assert_eq!(command.api_type, "e-commerce");
1011        assert_eq!(command.title, "Shop API");
1012        assert_eq!(command.description, "An e-commerce API");
1013    }
1014
1015    #[test]
1016    fn test_endpoint_requirement_creation() {
1017        let endpoint = EndpointRequirement {
1018            path: "/api/products".to_string(),
1019            method: "GET".to_string(),
1020            description: "Get all products".to_string(),
1021            request_body: None,
1022            response: None,
1023        };
1024
1025        assert_eq!(endpoint.path, "/api/products");
1026        assert_eq!(endpoint.method, "GET");
1027        assert_eq!(endpoint.description, "Get all products");
1028    }
1029
1030    #[test]
1031    fn test_endpoint_requirement_with_body() {
1032        let request_body = RequestBodyRequirement {
1033            schema: Some(json!({"type": "object"})),
1034            required: vec!["name".to_string(), "price".to_string()],
1035        };
1036
1037        let response = ResponseRequirement {
1038            status: 201,
1039            schema: Some(json!({"type": "object"})),
1040            is_array: false,
1041            count: None,
1042        };
1043
1044        let endpoint = EndpointRequirement {
1045            path: "/api/products".to_string(),
1046            method: "POST".to_string(),
1047            description: "Create a product".to_string(),
1048            request_body: Some(request_body),
1049            response: Some(response),
1050        };
1051
1052        assert!(endpoint.request_body.is_some());
1053        assert!(endpoint.response.is_some());
1054        assert_eq!(endpoint.response.unwrap().status, 201);
1055    }
1056
1057    #[test]
1058    fn test_request_body_requirement_creation() {
1059        let body = RequestBodyRequirement {
1060            schema: Some(json!({"type": "object", "properties": {"name": {"type": "string"}}})),
1061            required: vec!["name".to_string()],
1062        };
1063
1064        assert!(body.schema.is_some());
1065        assert_eq!(body.required.len(), 1);
1066    }
1067
1068    #[test]
1069    fn test_response_requirement_creation() {
1070        let response = ResponseRequirement {
1071            status: 200,
1072            schema: Some(json!({"type": "array", "items": {"type": "object"}})),
1073            is_array: true,
1074            count: Some(10),
1075        };
1076
1077        assert_eq!(response.status, 200);
1078        assert!(response.is_array);
1079        assert_eq!(response.count, Some(10));
1080    }
1081
1082    #[test]
1083    fn test_response_requirement_default_status() {
1084        let response = ResponseRequirement {
1085            status: default_status(),
1086            schema: None,
1087            is_array: false,
1088            count: None,
1089        };
1090
1091        assert_eq!(response.status, 200);
1092    }
1093
1094    #[test]
1095    fn test_model_requirement_creation() {
1096        let field = FieldRequirement {
1097            name: "id".to_string(),
1098            r#type: "integer".to_string(),
1099            description: "Product ID".to_string(),
1100            required: true,
1101        };
1102
1103        let model = ModelRequirement {
1104            name: "Product".to_string(),
1105            fields: vec![field],
1106        };
1107
1108        assert_eq!(model.name, "Product");
1109        assert_eq!(model.fields.len(), 1);
1110        assert_eq!(model.fields[0].name, "id");
1111    }
1112
1113    #[test]
1114    fn test_field_requirement_creation() {
1115        let field = FieldRequirement {
1116            name: "name".to_string(),
1117            r#type: "string".to_string(),
1118            description: "Product name".to_string(),
1119            required: true,
1120        };
1121
1122        assert_eq!(field.name, "name");
1123        assert_eq!(field.r#type, "string");
1124        assert!(field.required);
1125    }
1126
1127    #[test]
1128    fn test_field_requirement_default_required() {
1129        let field = FieldRequirement {
1130            name: "optional_field".to_string(),
1131            r#type: "string".to_string(),
1132            description: "".to_string(),
1133            required: default_true(),
1134        };
1135
1136        assert!(field.required);
1137    }
1138
1139    #[test]
1140    fn test_relationship_requirement_creation() {
1141        let relationship = RelationshipRequirement {
1142            from: "Product".to_string(),
1143            to: "Category".to_string(),
1144            r#type: "many-to-one".to_string(),
1145        };
1146
1147        assert_eq!(relationship.from, "Product");
1148        assert_eq!(relationship.to, "Category");
1149        assert_eq!(relationship.r#type, "many-to-one");
1150    }
1151
1152    #[test]
1153    fn test_flow_requirement_creation() {
1154        let flow = FlowRequirement {
1155            name: "checkout".to_string(),
1156            description: "Checkout flow".to_string(),
1157            steps: vec!["Add to cart".to_string(), "Payment".to_string()],
1158        };
1159
1160        assert_eq!(flow.name, "checkout");
1161        assert_eq!(flow.steps.len(), 2);
1162    }
1163
1164    #[test]
1165    fn test_parsed_workspace_scenario_creation() {
1166        let scenario = ParsedWorkspaceScenario {
1167            domain: "e-commerce".to_string(),
1168            title: "Shop Workspace".to_string(),
1169            description: "E-commerce workspace".to_string(),
1170            chaos_characteristics: vec![],
1171            initial_data: InitialDataRequirements::default(),
1172            api_requirements: ApiRequirements::default(),
1173            behavioral_rules: vec![],
1174        };
1175
1176        assert_eq!(scenario.domain, "e-commerce");
1177        assert_eq!(scenario.title, "Shop Workspace");
1178    }
1179
1180    #[test]
1181    fn test_chaos_characteristic_creation() {
1182        let chaos = ChaosCharacteristic {
1183            r#type: "latency".to_string(),
1184            description: "High latency on checkout".to_string(),
1185            config: json!({"delay_ms": 1000}),
1186        };
1187
1188        assert_eq!(chaos.r#type, "latency");
1189        assert_eq!(chaos.description, "High latency on checkout");
1190    }
1191
1192    #[test]
1193    fn test_initial_data_requirements_creation() {
1194        let mut custom = HashMap::new();
1195        custom.insert("products".to_string(), 50);
1196
1197        let data = InitialDataRequirements {
1198            users: Some(100),
1199            disputes: Some(5),
1200            orders: Some(200),
1201            custom,
1202        };
1203
1204        assert_eq!(data.users, Some(100));
1205        assert_eq!(data.disputes, Some(5));
1206        assert_eq!(data.orders, Some(200));
1207        assert_eq!(data.custom.get("products"), Some(&50));
1208    }
1209
1210    #[test]
1211    fn test_initial_data_requirements_default() {
1212        let data = InitialDataRequirements::default();
1213        assert!(data.users.is_none());
1214        assert!(data.disputes.is_none());
1215        assert!(data.orders.is_none());
1216        assert!(data.custom.is_empty());
1217    }
1218
1219    #[test]
1220    fn test_api_requirements_creation() {
1221        let endpoint = EndpointRequirement {
1222            path: "/api/products".to_string(),
1223            method: "GET".to_string(),
1224            description: "Get products".to_string(),
1225            request_body: None,
1226            response: None,
1227        };
1228
1229        let model = ModelRequirement {
1230            name: "Product".to_string(),
1231            fields: vec![],
1232        };
1233
1234        let api_req = ApiRequirements {
1235            endpoints: vec![endpoint],
1236            models: vec![model],
1237        };
1238
1239        assert_eq!(api_req.endpoints.len(), 1);
1240        assert_eq!(api_req.models.len(), 1);
1241    }
1242
1243    #[test]
1244    fn test_api_requirements_default() {
1245        let api_req = ApiRequirements::default();
1246        assert!(api_req.endpoints.is_empty());
1247        assert!(api_req.models.is_empty());
1248    }
1249
1250    #[test]
1251    fn test_behavioral_rule_creation() {
1252        let rule = BehavioralRule {
1253            description: "Slow response on checkout".to_string(),
1254            r#type: "latency".to_string(),
1255            config: json!({"delay_ms": 2000}),
1256        };
1257
1258        assert_eq!(rule.description, "Slow response on checkout");
1259        assert_eq!(rule.r#type, "latency");
1260    }
1261
1262    #[test]
1263    fn test_parsed_workspace_creation_creation() {
1264        let creation = ParsedWorkspaceCreation {
1265            workspace_name: "New Workspace".to_string(),
1266            workspace_description: "A new workspace".to_string(),
1267            entities: vec![],
1268            personas: vec![],
1269            scenarios: vec![],
1270            reality_continuum: None,
1271            drift_budget: None,
1272        };
1273
1274        assert_eq!(creation.workspace_name, "New Workspace");
1275        assert_eq!(creation.workspace_description, "A new workspace");
1276        assert!(creation.entities.is_empty());
1277    }
1278
1279    #[test]
1280    fn test_entity_requirement_creation() {
1281        let entity = EntityRequirement {
1282            name: "Product".to_string(),
1283            description: "Product entity".to_string(),
1284            endpoints: vec![],
1285            fields: vec![],
1286        };
1287
1288        assert_eq!(entity.name, "Product");
1289        assert_eq!(entity.description, "Product entity");
1290        assert!(entity.fields.is_empty());
1291    }
1292
1293    #[test]
1294    fn test_entity_endpoint_requirement_creation() {
1295        let endpoint = EntityEndpointRequirement {
1296            path: "/api/products".to_string(),
1297            method: "GET".to_string(),
1298            description: "Get products".to_string(),
1299        };
1300
1301        assert_eq!(endpoint.path, "/api/products");
1302        assert_eq!(endpoint.method, "GET");
1303    }
1304
1305    #[test]
1306    fn test_persona_requirement_creation() {
1307        let persona = PersonaRequirement {
1308            name: "Customer".to_string(),
1309            description: "Regular customer".to_string(),
1310            traits: HashMap::new(),
1311            relationships: vec![],
1312        };
1313
1314        assert_eq!(persona.name, "Customer");
1315        assert_eq!(persona.description, "Regular customer");
1316        assert!(persona.traits.is_empty());
1317    }
1318
1319    #[test]
1320    fn test_persona_relationship_creation() {
1321        let relationship = PersonaRelationship {
1322            r#type: "one-to-many".to_string(),
1323            target_entity: "Order".to_string(),
1324        };
1325
1326        assert_eq!(relationship.r#type, "one-to-many");
1327        assert_eq!(relationship.target_entity, "Order");
1328    }
1329
1330    #[test]
1331    fn test_parsed_reality_continuum_creation() {
1332        let continuum = ParsedRealityContinuum {
1333            default_ratio: 0.2,
1334            enabled: true,
1335            route_rules: vec![],
1336            transition_mode: "manual".to_string(),
1337            merge_strategy: "field_level".to_string(),
1338        };
1339
1340        assert_eq!(continuum.default_ratio, 0.2);
1341        assert!(continuum.enabled);
1342        assert_eq!(continuum.transition_mode, "manual");
1343        assert_eq!(continuum.merge_strategy, "field_level");
1344    }
1345
1346    #[test]
1347    fn test_parsed_continuum_rule_creation() {
1348        let rule = ParsedContinuumRule {
1349            pattern: "/api/catalog/*".to_string(),
1350            ratio: 0.5,
1351            description: "Catalog route".to_string(),
1352        };
1353
1354        assert_eq!(rule.pattern, "/api/catalog/*");
1355        assert_eq!(rule.ratio, 0.5);
1356    }
1357
1358    #[test]
1359    fn test_parsed_drift_budget_creation() {
1360        let mut per_service_budgets = HashMap::new();
1361        per_service_budgets.insert(
1362            "catalog".to_string(),
1363            ParsedServiceBudget {
1364                max_breaking_changes: 5,
1365                max_non_breaking_changes: 20,
1366            },
1367        );
1368
1369        let budget = ParsedDriftBudget {
1370            strictness: "moderate".to_string(),
1371            enabled: true,
1372            max_breaking_changes: 10,
1373            max_non_breaking_changes: 50,
1374            max_field_churn_percent: Some(5.0),
1375            time_window_days: Some(30),
1376            per_service_budgets,
1377            description: "Drift budget config".to_string(),
1378        };
1379
1380        assert_eq!(budget.strictness, "moderate");
1381        assert!(budget.enabled);
1382        assert_eq!(budget.max_breaking_changes, 10);
1383        assert_eq!(budget.max_non_breaking_changes, 50);
1384        assert_eq!(budget.per_service_budgets.len(), 1);
1385    }
1386
1387    #[test]
1388    fn test_parsed_service_budget_creation() {
1389        let budget = ParsedServiceBudget {
1390            max_breaking_changes: 3,
1391            max_non_breaking_changes: 15,
1392        };
1393
1394        assert_eq!(budget.max_breaking_changes, 3);
1395        assert_eq!(budget.max_non_breaking_changes, 15);
1396    }
1397
1398    #[test]
1399    fn test_parsed_command_clone() {
1400        let command1 = ParsedCommand {
1401            api_type: "test".to_string(),
1402            title: "Test API".to_string(),
1403            description: "Test".to_string(),
1404            endpoints: vec![],
1405            models: vec![],
1406            relationships: vec![],
1407            sample_counts: HashMap::new(),
1408            flows: vec![],
1409        };
1410        let command2 = command1.clone();
1411        assert_eq!(command1.api_type, command2.api_type);
1412    }
1413
1414    #[test]
1415    fn test_parsed_command_debug() {
1416        let command = ParsedCommand {
1417            api_type: "test".to_string(),
1418            title: "Test".to_string(),
1419            description: "Test".to_string(),
1420            endpoints: vec![],
1421            models: vec![],
1422            relationships: vec![],
1423            sample_counts: HashMap::new(),
1424            flows: vec![],
1425        };
1426        let debug_str = format!("{:?}", command);
1427        assert!(debug_str.contains("ParsedCommand"));
1428    }
1429
1430    #[test]
1431    fn test_endpoint_requirement_clone() {
1432        let endpoint1 = EndpointRequirement {
1433            path: "/test".to_string(),
1434            method: "GET".to_string(),
1435            description: "Test".to_string(),
1436            request_body: None,
1437            response: None,
1438        };
1439        let endpoint2 = endpoint1.clone();
1440        assert_eq!(endpoint1.path, endpoint2.path);
1441    }
1442
1443    #[test]
1444    fn test_endpoint_requirement_debug() {
1445        let endpoint = EndpointRequirement {
1446            path: "/test".to_string(),
1447            method: "POST".to_string(),
1448            description: "Test".to_string(),
1449            request_body: None,
1450            response: None,
1451        };
1452        let debug_str = format!("{:?}", endpoint);
1453        assert!(debug_str.contains("EndpointRequirement"));
1454    }
1455
1456    #[test]
1457    fn test_request_body_requirement_clone() {
1458        let body1 = RequestBodyRequirement {
1459            schema: None,
1460            required: vec!["field".to_string()],
1461        };
1462        let body2 = body1.clone();
1463        assert_eq!(body1.required, body2.required);
1464    }
1465
1466    #[test]
1467    fn test_request_body_requirement_debug() {
1468        let body = RequestBodyRequirement {
1469            schema: Some(json!({})),
1470            required: vec![],
1471        };
1472        let debug_str = format!("{:?}", body);
1473        assert!(debug_str.contains("RequestBodyRequirement"));
1474    }
1475
1476    #[test]
1477    fn test_response_requirement_clone() {
1478        let response1 = ResponseRequirement {
1479            status: 200,
1480            schema: None,
1481            is_array: false,
1482            count: None,
1483        };
1484        let response2 = response1.clone();
1485        assert_eq!(response1.status, response2.status);
1486    }
1487
1488    #[test]
1489    fn test_response_requirement_debug() {
1490        let response = ResponseRequirement {
1491            status: 201,
1492            schema: Some(json!({})),
1493            is_array: true,
1494            count: Some(10),
1495        };
1496        let debug_str = format!("{:?}", response);
1497        assert!(debug_str.contains("ResponseRequirement"));
1498    }
1499
1500    #[test]
1501    fn test_model_requirement_clone() {
1502        let model1 = ModelRequirement {
1503            name: "User".to_string(),
1504            fields: vec![],
1505        };
1506        let model2 = model1.clone();
1507        assert_eq!(model1.name, model2.name);
1508    }
1509
1510    #[test]
1511    fn test_model_requirement_debug() {
1512        let model = ModelRequirement {
1513            name: "Product".to_string(),
1514            fields: vec![],
1515        };
1516        let debug_str = format!("{:?}", model);
1517        assert!(debug_str.contains("ModelRequirement"));
1518    }
1519
1520    #[test]
1521    fn test_field_requirement_clone() {
1522        let field1 = FieldRequirement {
1523            name: "id".to_string(),
1524            r#type: "integer".to_string(),
1525            description: "ID".to_string(),
1526            required: true,
1527        };
1528        let field2 = field1.clone();
1529        assert_eq!(field1.name, field2.name);
1530    }
1531
1532    #[test]
1533    fn test_field_requirement_debug() {
1534        let field = FieldRequirement {
1535            name: "name".to_string(),
1536            r#type: "string".to_string(),
1537            description: "Name".to_string(),
1538            required: false,
1539        };
1540        let debug_str = format!("{:?}", field);
1541        assert!(debug_str.contains("FieldRequirement"));
1542    }
1543
1544    #[test]
1545    fn test_relationship_requirement_clone() {
1546        let rel1 = RelationshipRequirement {
1547            from: "User".to_string(),
1548            to: "Order".to_string(),
1549            r#type: "one-to-many".to_string(),
1550        };
1551        let rel2 = rel1.clone();
1552        assert_eq!(rel1.from, rel2.from);
1553    }
1554
1555    #[test]
1556    fn test_relationship_requirement_debug() {
1557        let rel = RelationshipRequirement {
1558            from: "Product".to_string(),
1559            to: "Category".to_string(),
1560            r#type: "many-to-one".to_string(),
1561        };
1562        let debug_str = format!("{:?}", rel);
1563        assert!(debug_str.contains("RelationshipRequirement"));
1564    }
1565
1566    #[test]
1567    fn test_flow_requirement_clone() {
1568        let flow1 = FlowRequirement {
1569            name: "checkout".to_string(),
1570            description: "Checkout".to_string(),
1571            steps: vec![],
1572        };
1573        let flow2 = flow1.clone();
1574        assert_eq!(flow1.name, flow2.name);
1575    }
1576
1577    #[test]
1578    fn test_flow_requirement_debug() {
1579        let flow = FlowRequirement {
1580            name: "auth".to_string(),
1581            description: "Auth flow".to_string(),
1582            steps: vec!["step1".to_string()],
1583        };
1584        let debug_str = format!("{:?}", flow);
1585        assert!(debug_str.contains("FlowRequirement"));
1586    }
1587
1588    #[test]
1589    fn test_parsed_workspace_scenario_clone() {
1590        let scenario1 = ParsedWorkspaceScenario {
1591            domain: "e-commerce".to_string(),
1592            title: "Shop".to_string(),
1593            description: "Shop".to_string(),
1594            chaos_characteristics: vec![],
1595            initial_data: InitialDataRequirements::default(),
1596            api_requirements: ApiRequirements::default(),
1597            behavioral_rules: vec![],
1598        };
1599        let scenario2 = scenario1.clone();
1600        assert_eq!(scenario1.domain, scenario2.domain);
1601    }
1602
1603    #[test]
1604    fn test_parsed_workspace_scenario_debug() {
1605        let scenario = ParsedWorkspaceScenario {
1606            domain: "social".to_string(),
1607            title: "Social".to_string(),
1608            description: "Social".to_string(),
1609            chaos_characteristics: vec![],
1610            initial_data: InitialDataRequirements::default(),
1611            api_requirements: ApiRequirements::default(),
1612            behavioral_rules: vec![],
1613        };
1614        let debug_str = format!("{:?}", scenario);
1615        assert!(debug_str.contains("ParsedWorkspaceScenario"));
1616    }
1617
1618    #[test]
1619    fn test_chaos_characteristic_clone() {
1620        let chaos1 = ChaosCharacteristic {
1621            r#type: "latency".to_string(),
1622            description: "High latency".to_string(),
1623            config: json!({}),
1624        };
1625        let chaos2 = chaos1.clone();
1626        assert_eq!(chaos1.r#type, chaos2.r#type);
1627    }
1628
1629    #[test]
1630    fn test_chaos_characteristic_debug() {
1631        let chaos = ChaosCharacteristic {
1632            r#type: "failure".to_string(),
1633            description: "Failures".to_string(),
1634            config: json!({"rate": 0.1}),
1635        };
1636        let debug_str = format!("{:?}", chaos);
1637        assert!(debug_str.contains("ChaosCharacteristic"));
1638    }
1639
1640    #[test]
1641    fn test_initial_data_requirements_clone() {
1642        let data1 = InitialDataRequirements::default();
1643        let data2 = data1.clone();
1644        // Just verify it doesn't panic
1645        assert_eq!(data1.users, data2.users);
1646    }
1647
1648    #[test]
1649    fn test_initial_data_requirements_debug() {
1650        let data = InitialDataRequirements::default();
1651        let debug_str = format!("{:?}", data);
1652        assert!(debug_str.contains("InitialDataRequirements"));
1653    }
1654
1655    #[test]
1656    fn test_api_requirements_clone() {
1657        let api1 = ApiRequirements::default();
1658        let api2 = api1.clone();
1659        assert_eq!(api1.endpoints.len(), api2.endpoints.len());
1660    }
1661
1662    #[test]
1663    fn test_api_requirements_debug() {
1664        let api = ApiRequirements::default();
1665        let debug_str = format!("{:?}", api);
1666        assert!(debug_str.contains("ApiRequirements"));
1667    }
1668
1669    #[test]
1670    fn test_behavioral_rule_clone() {
1671        let rule1 = BehavioralRule {
1672            description: "Rule".to_string(),
1673            r#type: "failure".to_string(),
1674            config: json!({}),
1675        };
1676        let rule2 = rule1.clone();
1677        assert_eq!(rule1.description, rule2.description);
1678    }
1679
1680    #[test]
1681    fn test_behavioral_rule_debug() {
1682        let rule = BehavioralRule {
1683            description: "Test rule".to_string(),
1684            r#type: "latency".to_string(),
1685            config: json!({"delay": 100}),
1686        };
1687        let debug_str = format!("{:?}", rule);
1688        assert!(debug_str.contains("BehavioralRule"));
1689    }
1690
1691    #[test]
1692    fn test_parsed_workspace_creation_clone() {
1693        let creation1 = ParsedWorkspaceCreation {
1694            workspace_name: "Test".to_string(),
1695            workspace_description: "Test".to_string(),
1696            entities: vec![],
1697            personas: vec![],
1698            scenarios: vec![],
1699            reality_continuum: None,
1700            drift_budget: None,
1701        };
1702        let creation2 = creation1.clone();
1703        assert_eq!(creation1.workspace_name, creation2.workspace_name);
1704    }
1705
1706    #[test]
1707    fn test_parsed_workspace_creation_debug() {
1708        let creation = ParsedWorkspaceCreation {
1709            workspace_name: "Workspace".to_string(),
1710            workspace_description: "Description".to_string(),
1711            entities: vec![],
1712            personas: vec![],
1713            scenarios: vec![],
1714            reality_continuum: None,
1715            drift_budget: None,
1716        };
1717        let debug_str = format!("{:?}", creation);
1718        assert!(debug_str.contains("ParsedWorkspaceCreation"));
1719    }
1720
1721    #[test]
1722    fn test_parsed_reality_continuum_clone() {
1723        let continuum1 = ParsedRealityContinuum {
1724            default_ratio: 0.5,
1725            enabled: true,
1726            route_rules: vec![],
1727            transition_mode: "manual".to_string(),
1728            merge_strategy: "field_level".to_string(),
1729        };
1730        let continuum2 = continuum1.clone();
1731        assert_eq!(continuum1.default_ratio, continuum2.default_ratio);
1732    }
1733
1734    #[test]
1735    fn test_parsed_reality_continuum_debug() {
1736        let continuum = ParsedRealityContinuum {
1737            default_ratio: 0.2,
1738            enabled: true,
1739            route_rules: vec![],
1740            transition_mode: "time_based".to_string(),
1741            merge_strategy: "weighted".to_string(),
1742        };
1743        let debug_str = format!("{:?}", continuum);
1744        assert!(debug_str.contains("ParsedRealityContinuum"));
1745    }
1746
1747    #[test]
1748    fn test_parsed_continuum_rule_clone() {
1749        let rule1 = ParsedContinuumRule {
1750            pattern: "/api/*".to_string(),
1751            ratio: 0.3,
1752            description: "Test".to_string(),
1753        };
1754        let rule2 = rule1.clone();
1755        assert_eq!(rule1.pattern, rule2.pattern);
1756    }
1757
1758    #[test]
1759    fn test_parsed_continuum_rule_debug() {
1760        let rule = ParsedContinuumRule {
1761            pattern: "/catalog/*".to_string(),
1762            ratio: 0.5,
1763            description: "Catalog".to_string(),
1764        };
1765        let debug_str = format!("{:?}", rule);
1766        assert!(debug_str.contains("ParsedContinuumRule"));
1767    }
1768
1769    #[test]
1770    fn test_parsed_drift_budget_clone() {
1771        let budget1 = ParsedDriftBudget {
1772            strictness: "moderate".to_string(),
1773            enabled: true,
1774            max_breaking_changes: 10,
1775            max_non_breaking_changes: 50,
1776            max_field_churn_percent: None,
1777            time_window_days: None,
1778            per_service_budgets: HashMap::new(),
1779            description: "Budget".to_string(),
1780        };
1781        let budget2 = budget1.clone();
1782        assert_eq!(budget1.strictness, budget2.strictness);
1783    }
1784
1785    #[test]
1786    fn test_parsed_drift_budget_debug() {
1787        let budget = ParsedDriftBudget {
1788            strictness: "strict".to_string(),
1789            enabled: true,
1790            max_breaking_changes: 5,
1791            max_non_breaking_changes: 20,
1792            max_field_churn_percent: Some(3.0),
1793            time_window_days: Some(7),
1794            per_service_budgets: HashMap::new(),
1795            description: "Strict budget".to_string(),
1796        };
1797        let debug_str = format!("{:?}", budget);
1798        assert!(debug_str.contains("ParsedDriftBudget"));
1799    }
1800
1801    #[test]
1802    fn test_parsed_service_budget_clone() {
1803        let budget1 = ParsedServiceBudget {
1804            max_breaking_changes: 3,
1805            max_non_breaking_changes: 15,
1806        };
1807        let budget2 = budget1.clone();
1808        assert_eq!(budget1.max_breaking_changes, budget2.max_breaking_changes);
1809    }
1810
1811    #[test]
1812    fn test_parsed_service_budget_debug() {
1813        let budget = ParsedServiceBudget {
1814            max_breaking_changes: 5,
1815            max_non_breaking_changes: 25,
1816        };
1817        let debug_str = format!("{:?}", budget);
1818        assert!(debug_str.contains("ParsedServiceBudget"));
1819    }
1820}