Skip to main content

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