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