1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
//! Natural language mock generator
//!
//! This module provides functionality to generate mocks from natural language descriptions.
//! It integrates with the existing VoiceCommandParser and VoiceSpecGenerator to leverage
//! the proven mock generation infrastructure.
use crate::ai_studio::artifact_freezer::{ArtifactFreezer, FreezeMetadata};
use crate::ai_studio::config::DeterministicModeConfig;
use crate::intelligent_behavior::IntelligentBehaviorConfig;
use crate::voice::{command_parser::VoiceCommandParser, spec_generator::VoiceSpecGenerator};
use mockforge_foundation::Result;
use mockforge_openapi::OpenApiSpec;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
/// Mock generator for creating mocks from natural language
pub struct MockGenerator {
/// Voice command parser for parsing NL descriptions
parser: VoiceCommandParser,
/// Spec generator for creating OpenAPI specs
spec_generator: VoiceSpecGenerator,
/// Configuration (needed for accessing LLM provider/model info)
config: IntelligentBehaviorConfig,
}
impl MockGenerator {
/// Create a new mock generator with default configuration
pub fn new() -> Self {
let config = IntelligentBehaviorConfig::default();
Self {
parser: VoiceCommandParser::new(config.clone()),
spec_generator: VoiceSpecGenerator::new(),
config,
}
}
/// Create a new mock generator with custom configuration
pub fn with_config(config: IntelligentBehaviorConfig) -> Self {
Self {
parser: VoiceCommandParser::new(config.clone()),
spec_generator: VoiceSpecGenerator::new(),
config,
}
}
/// Generate a mock from natural language description
///
/// This method parses the natural language description and generates a complete
/// OpenAPI specification ready for use with MockForge.
///
/// # Example
///
/// ```rust,ignore
/// use mockforge_core::ai_studio::nl_mock_generator::MockGenerator;
///
/// async fn example() -> mockforge_core::Result<()> {
/// let generator = MockGenerator::new();
/// let result = generator.generate(
/// "Create a user API with CRUD operations for managing users",
/// None,
/// None,
/// None,
/// ).await?;
/// Ok(())
/// }
/// ```
pub async fn generate(
&self,
description: &str,
_workspace_id: Option<&str>,
ai_mode: Option<crate::ai_studio::config::AiMode>,
deterministic_config: Option<&DeterministicModeConfig>,
) -> Result<MockGenerationResult> {
// In deterministic mode, check for frozen artifacts first
if ai_mode == Some(crate::ai_studio::config::AiMode::GenerateOnceFreeze) {
let freezer = ArtifactFreezer::new();
// Create identifier from description hash
let mut hasher = DefaultHasher::new();
description.hash(&mut hasher);
let description_hash = format!("{:x}", hasher.finish());
// Try to load frozen artifact
if let Some(frozen) = freezer.load_frozen("mock", Some(&description_hash)).await? {
// Extract spec from frozen content (remove metadata)
let mut spec = frozen.content.clone();
if let Some(obj) = spec.as_object_mut() {
obj.remove("_frozen_metadata");
}
return Ok(MockGenerationResult {
spec: Some(spec),
message: format!(
"Loaded frozen mock artifact from {} (deterministic mode)",
frozen.path
),
parsed_command: None,
frozen_artifact: Some(frozen),
});
}
}
// Parse the natural language command
let parsed = self.parser.parse_command(description).await?;
// Generate OpenAPI spec from parsed command
let spec = self.spec_generator.generate_spec(&parsed).await?;
// Convert spec to JSON for response
let spec_json = serde_json::to_value(&spec.spec)?;
// Auto-freeze if enabled
let frozen_artifact = if let Some(config) = deterministic_config {
if config.enabled && config.is_auto_freeze_enabled() {
let freezer = ArtifactFreezer::new();
// Calculate prompt hash
let mut hasher = Sha256::new();
hasher.update(description.as_bytes());
let prompt_hash = format!("{:x}", hasher.finalize());
// Create metadata
let metadata = if config.track_metadata {
Some(FreezeMetadata {
llm_provider: Some(self.config.behavior_model.llm_provider.clone()),
llm_model: Some(self.config.behavior_model.model.clone()),
llm_version: None, // Would need to be passed in or retrieved
prompt_hash: Some(prompt_hash),
output_hash: None, // Will be calculated by freezer
original_prompt: Some(description.to_string()),
})
} else {
None
};
let freeze_request = crate::ai_studio::artifact_freezer::FreezeRequest {
artifact_type: "mock".to_string(),
content: spec_json.clone(),
format: config.freeze_format.clone(),
path: None,
metadata,
};
freezer.auto_freeze_if_enabled(&freeze_request, config).await?
} else {
None
}
} else {
None
};
// Record AI pillar usage (ai_generation type=mock)
mockforge_foundation::pillar_tracking::record_ai_usage(
_workspace_id.map(String::from),
None,
"ai_generation",
serde_json::json!({
"type": "mock",
"endpoints": parsed.endpoints.len(),
"models": parsed.models.len(),
}),
)
.await;
Ok(MockGenerationResult {
spec: Some(spec_json),
message: format!(
"Successfully generated API '{}' with {} endpoints and {} models{}",
parsed.title,
parsed.endpoints.len(),
parsed.models.len(),
if frozen_artifact.is_some() {
" (auto-frozen)"
} else {
""
}
),
parsed_command: Some(parsed),
frozen_artifact,
})
}
/// Generate a mock with additional context (for conversational mode)
///
/// This method allows generating mocks that extend or modify existing specifications.
pub async fn generate_with_context(
&self,
description: &str,
existing_spec: Option<&OpenApiSpec>,
) -> Result<MockGenerationResult> {
// Parse the natural language command
let parsed = self.parser.parse_command(description).await?;
// Generate or merge spec
let spec = if let Some(existing) = existing_spec {
// Merge with existing spec
self.spec_generator.merge_spec(existing, &parsed).await?
} else {
// Generate new spec
self.spec_generator.generate_spec(&parsed).await?
};
// Convert spec to JSON for response
let spec_json = serde_json::to_value(&spec.spec)?;
// Record AI pillar usage. Merge counts as ai_refinement; new generation as ai_generation.
let metric_name = if existing_spec.is_some() {
"ai_refinement"
} else {
"ai_generation"
};
mockforge_foundation::pillar_tracking::record_ai_usage(
None,
None,
metric_name,
serde_json::json!({
"type": "mock",
"endpoints": parsed.endpoints.len(),
"models": parsed.models.len(),
}),
)
.await;
Ok(MockGenerationResult {
spec: Some(spec_json),
message: format!(
"Successfully {} API '{}' with {} endpoints and {} models",
if existing_spec.is_some() {
"updated"
} else {
"generated"
},
parsed.title,
parsed.endpoints.len(),
parsed.models.len()
),
parsed_command: Some(parsed),
frozen_artifact: None,
})
}
}
impl Default for MockGenerator {
fn default() -> Self {
Self::new()
}
}
/// Result of mock generation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MockGenerationResult {
/// Generated OpenAPI spec (if any)
pub spec: Option<serde_json::Value>,
/// Status message
pub message: String,
/// Parsed command details (for debugging/preview)
#[serde(skip_serializing_if = "Option::is_none")]
pub parsed_command: Option<crate::voice::command_parser::ParsedCommand>,
/// Frozen artifact (if auto-freeze was enabled)
#[serde(skip_serializing_if = "Option::is_none")]
pub frozen_artifact: Option<crate::ai_studio::artifact_freezer::FrozenArtifact>,
}