mockforge_core/openapi/
response.rs

1//! OpenAPI response generation and mocking
2//!
3//! This module provides functionality for generating mock responses
4//! based on OpenAPI specifications.
5
6use crate::intelligent_behavior::config::Persona;
7use crate::{
8    ai_response::{AiResponseConfig, RequestContext},
9    OpenApiSpec, Result,
10};
11use async_trait::async_trait;
12use chrono;
13use openapiv3::{Operation, ReferenceOr, Response, Responses, Schema};
14use rand::{thread_rng, Rng};
15use serde_json::Value;
16use std::collections::HashMap;
17use uuid;
18
19/// Trait for AI response generation
20///
21/// This trait allows the HTTP layer to provide custom AI generation
22/// implementations without creating circular dependencies between crates.
23#[async_trait]
24pub trait AiGenerator: Send + Sync {
25    /// Generate an AI response from a prompt
26    ///
27    /// # Arguments
28    /// * `prompt` - The expanded prompt to send to the LLM
29    /// * `config` - The AI response configuration with temperature, max_tokens, etc.
30    ///
31    /// # Returns
32    /// A JSON value containing the generated response
33    async fn generate(&self, prompt: &str, config: &AiResponseConfig) -> Result<Value>;
34}
35
36/// Response generator for creating mock responses
37pub struct ResponseGenerator;
38
39impl ResponseGenerator {
40    /// Generate an AI-assisted response using LLM
41    ///
42    /// This method generates a dynamic response based on request context
43    /// using the configured LLM provider (OpenAI, Anthropic, etc.)
44    ///
45    /// # Arguments
46    /// * `ai_config` - The AI response configuration
47    /// * `context` - The request context for prompt expansion
48    /// * `generator` - Optional AI generator implementation (if None, returns placeholder)
49    ///
50    /// # Returns
51    /// A JSON value containing the generated response
52    pub async fn generate_ai_response(
53        ai_config: &AiResponseConfig,
54        context: &RequestContext,
55        generator: Option<&dyn AiGenerator>,
56    ) -> Result<Value> {
57        // Get the prompt template and expand it with request context
58        let prompt_template = ai_config
59            .prompt
60            .as_ref()
61            .ok_or_else(|| crate::Error::generic("AI prompt is required"))?;
62
63        // Note: expand_prompt_template is now in mockforge-template-expansion crate
64        // For now, we'll do a simple string replacement as a fallback
65        // In the future, this should be refactored to use the template expansion crate
66        let expanded_prompt = prompt_template
67            .replace("{{method}}", &context.method)
68            .replace("{{path}}", &context.path);
69
70        tracing::info!("AI response generation requested with prompt: {}", expanded_prompt);
71
72        // Use the provided generator if available
73        if let Some(gen) = generator {
74            tracing::debug!("Using provided AI generator for response");
75            return gen.generate(&expanded_prompt, ai_config).await;
76        }
77
78        // Fallback: return a descriptive placeholder if no generator is provided
79        tracing::warn!("No AI generator provided, returning placeholder response");
80        Ok(serde_json::json!({
81            "ai_response": "AI generation placeholder",
82            "note": "This endpoint is configured for AI-assisted responses, but no AI generator was provided",
83            "expanded_prompt": expanded_prompt,
84            "mode": format!("{:?}", ai_config.mode),
85            "temperature": ai_config.temperature,
86            "implementation_note": "Pass an AiGenerator implementation to ResponseGenerator::generate_ai_response to enable actual AI generation"
87        }))
88    }
89
90    /// Generate a mock response for an operation and status code
91    pub fn generate_response(
92        spec: &OpenApiSpec,
93        operation: &Operation,
94        status_code: u16,
95        content_type: Option<&str>,
96    ) -> Result<Value> {
97        Self::generate_response_with_expansion(spec, operation, status_code, content_type, true)
98    }
99
100    /// Generate a mock response for an operation and status code with token expansion control
101    pub fn generate_response_with_expansion(
102        spec: &OpenApiSpec,
103        operation: &Operation,
104        status_code: u16,
105        content_type: Option<&str>,
106        expand_tokens: bool,
107    ) -> Result<Value> {
108        Self::generate_response_with_expansion_and_mode(
109            spec,
110            operation,
111            status_code,
112            content_type,
113            expand_tokens,
114            None,
115            None,
116        )
117    }
118
119    /// Generate response with token expansion and selection mode
120    pub fn generate_response_with_expansion_and_mode(
121        spec: &OpenApiSpec,
122        operation: &Operation,
123        status_code: u16,
124        content_type: Option<&str>,
125        expand_tokens: bool,
126        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
127        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
128    ) -> Result<Value> {
129        Self::generate_response_with_expansion_and_mode_and_persona(
130            spec,
131            operation,
132            status_code,
133            content_type,
134            expand_tokens,
135            selection_mode,
136            selector,
137            None, // No persona by default
138        )
139    }
140
141    /// Generate response with token expansion, selection mode, and persona
142    pub fn generate_response_with_expansion_and_mode_and_persona(
143        spec: &OpenApiSpec,
144        operation: &Operation,
145        status_code: u16,
146        content_type: Option<&str>,
147        expand_tokens: bool,
148        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
149        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
150        persona: Option<&Persona>,
151    ) -> Result<Value> {
152        Self::generate_response_with_scenario_and_mode_and_persona(
153            spec,
154            operation,
155            status_code,
156            content_type,
157            expand_tokens,
158            None, // No scenario by default
159            selection_mode,
160            selector,
161            persona,
162        )
163    }
164
165    /// Generate a mock response with scenario support
166    ///
167    /// This method allows selection of specific example scenarios from the OpenAPI spec.
168    /// Scenarios are defined using the standard OpenAPI `examples` field (not the singular `example`).
169    ///
170    /// # Arguments
171    /// * `spec` - The OpenAPI specification
172    /// * `operation` - The operation to generate a response for
173    /// * `status_code` - The HTTP status code
174    /// * `content_type` - Optional content type (e.g., "application/json")
175    /// * `expand_tokens` - Whether to expand template tokens like {{now}} and {{uuid}}
176    /// * `scenario` - Optional scenario name to select from the examples map
177    ///
178    /// # Example
179    /// ```yaml
180    /// responses:
181    ///   '200':
182    ///     content:
183    ///       application/json:
184    ///         examples:
185    ///           happy:
186    ///             value: { "status": "success", "message": "All good!" }
187    ///           error:
188    ///             value: { "status": "error", "message": "Something went wrong" }
189    /// ```
190    pub fn generate_response_with_scenario(
191        spec: &OpenApiSpec,
192        operation: &Operation,
193        status_code: u16,
194        content_type: Option<&str>,
195        expand_tokens: bool,
196        scenario: Option<&str>,
197    ) -> Result<Value> {
198        Self::generate_response_with_scenario_and_mode(
199            spec,
200            operation,
201            status_code,
202            content_type,
203            expand_tokens,
204            scenario,
205            None,
206            None,
207        )
208    }
209
210    /// Generate response with scenario support and selection mode
211    pub fn generate_response_with_scenario_and_mode(
212        spec: &OpenApiSpec,
213        operation: &Operation,
214        status_code: u16,
215        content_type: Option<&str>,
216        expand_tokens: bool,
217        scenario: Option<&str>,
218        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
219        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
220    ) -> Result<Value> {
221        Self::generate_response_with_scenario_and_mode_and_persona(
222            spec,
223            operation,
224            status_code,
225            content_type,
226            expand_tokens,
227            scenario,
228            selection_mode,
229            selector,
230            None, // No persona by default
231        )
232    }
233
234    /// Generate response with scenario support, selection mode, and persona
235    pub fn generate_response_with_scenario_and_mode_and_persona(
236        spec: &OpenApiSpec,
237        operation: &Operation,
238        status_code: u16,
239        content_type: Option<&str>,
240        expand_tokens: bool,
241        scenario: Option<&str>,
242        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
243        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
244        persona: Option<&Persona>,
245    ) -> Result<Value> {
246        // Find the response for the status code
247        let response = Self::find_response_for_status(&operation.responses, status_code);
248
249        tracing::debug!(
250            "Finding response for status code {}: {:?}",
251            status_code,
252            if response.is_some() {
253                "found"
254            } else {
255                "not found"
256            }
257        );
258
259        match response {
260            Some(response_ref) => {
261                match response_ref {
262                    ReferenceOr::Item(response) => {
263                        tracing::debug!(
264                            "Using direct response item with {} content types",
265                            response.content.len()
266                        );
267                        Self::generate_from_response_with_scenario_and_mode(
268                            spec,
269                            response,
270                            content_type,
271                            expand_tokens,
272                            scenario,
273                            selection_mode,
274                            selector,
275                        )
276                    }
277                    ReferenceOr::Reference { reference } => {
278                        tracing::debug!("Resolving response reference: {}", reference);
279                        // Resolve the reference
280                        if let Some(resolved_response) = spec.get_response(reference) {
281                            tracing::debug!(
282                                "Resolved response reference with {} content types",
283                                resolved_response.content.len()
284                            );
285                            Self::generate_from_response_with_scenario_and_mode(
286                                spec,
287                                resolved_response,
288                                content_type,
289                                expand_tokens,
290                                scenario,
291                                selection_mode,
292                                selector,
293                            )
294                        } else {
295                            tracing::warn!("Response reference '{}' not found in spec", reference);
296                            // Reference not found, return empty object
297                            Ok(Value::Object(serde_json::Map::new()))
298                        }
299                    }
300                }
301            }
302            None => {
303                tracing::warn!(
304                    "No response found for status code {} in operation. Available status codes: {:?}",
305                    status_code,
306                    operation.responses.responses.keys().collect::<Vec<_>>()
307                );
308                // No response found for this status code
309                Ok(Value::Object(serde_json::Map::new()))
310            }
311        }
312    }
313
314    /// Find response for a given status code
315    fn find_response_for_status(
316        responses: &Responses,
317        status_code: u16,
318    ) -> Option<&ReferenceOr<Response>> {
319        // First try exact match
320        if let Some(response) = responses.responses.get(&openapiv3::StatusCode::Code(status_code)) {
321            return Some(response);
322        }
323
324        // Try default response
325        if let Some(default_response) = &responses.default {
326            return Some(default_response);
327        }
328
329        None
330    }
331
332    /// Generate response from a Response object
333    fn generate_from_response(
334        spec: &OpenApiSpec,
335        response: &Response,
336        content_type: Option<&str>,
337        expand_tokens: bool,
338    ) -> Result<Value> {
339        Self::generate_from_response_with_scenario(
340            spec,
341            response,
342            content_type,
343            expand_tokens,
344            None,
345        )
346    }
347
348    /// Generate response from a Response object with scenario support
349    fn generate_from_response_with_scenario(
350        spec: &OpenApiSpec,
351        response: &Response,
352        content_type: Option<&str>,
353        expand_tokens: bool,
354        scenario: Option<&str>,
355    ) -> Result<Value> {
356        Self::generate_from_response_with_scenario_and_mode(
357            spec,
358            response,
359            content_type,
360            expand_tokens,
361            scenario,
362            None,
363            None,
364        )
365    }
366
367    /// Generate response from a Response object with scenario support and selection mode
368    fn generate_from_response_with_scenario_and_mode(
369        spec: &OpenApiSpec,
370        response: &Response,
371        content_type: Option<&str>,
372        expand_tokens: bool,
373        scenario: Option<&str>,
374        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
375        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
376    ) -> Result<Value> {
377        Self::generate_from_response_with_scenario_and_mode_and_persona(
378            spec,
379            response,
380            content_type,
381            expand_tokens,
382            scenario,
383            selection_mode,
384            selector,
385            None, // No persona by default
386        )
387    }
388
389    /// Generate response from a Response object with scenario support, selection mode, and persona
390    fn generate_from_response_with_scenario_and_mode_and_persona(
391        spec: &OpenApiSpec,
392        response: &Response,
393        content_type: Option<&str>,
394        expand_tokens: bool,
395        scenario: Option<&str>,
396        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
397        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
398        persona: Option<&Persona>,
399    ) -> Result<Value> {
400        // If content_type is specified, look for that media type
401        if let Some(content_type) = content_type {
402            if let Some(media_type) = response.content.get(content_type) {
403                return Self::generate_from_media_type_with_scenario_and_mode_and_persona(
404                    spec,
405                    media_type,
406                    expand_tokens,
407                    scenario,
408                    selection_mode,
409                    selector,
410                    persona,
411                );
412            }
413        }
414
415        // If no content_type specified or not found, try common content types
416        let preferred_types = ["application/json", "application/xml", "text/plain"];
417
418        for content_type in &preferred_types {
419            if let Some(media_type) = response.content.get(*content_type) {
420                return Self::generate_from_media_type_with_scenario_and_mode_and_persona(
421                    spec,
422                    media_type,
423                    expand_tokens,
424                    scenario,
425                    selection_mode,
426                    selector,
427                    persona,
428                );
429            }
430        }
431
432        // If no suitable content type found, return the first available
433        if let Some((_, media_type)) = response.content.iter().next() {
434            return Self::generate_from_media_type_with_scenario_and_mode_and_persona(
435                spec,
436                media_type,
437                expand_tokens,
438                scenario,
439                selection_mode,
440                selector,
441                persona,
442            );
443        }
444
445        // No content found, return empty object
446        Ok(Value::Object(serde_json::Map::new()))
447    }
448
449    /// Generate response from a MediaType with optional scenario selection
450    fn generate_from_media_type(
451        spec: &OpenApiSpec,
452        media_type: &openapiv3::MediaType,
453        expand_tokens: bool,
454    ) -> Result<Value> {
455        Self::generate_from_media_type_with_scenario(spec, media_type, expand_tokens, None)
456    }
457
458    /// Generate response from a MediaType with scenario support and selection mode
459    fn generate_from_media_type_with_scenario(
460        spec: &OpenApiSpec,
461        media_type: &openapiv3::MediaType,
462        expand_tokens: bool,
463        scenario: Option<&str>,
464    ) -> Result<Value> {
465        Self::generate_from_media_type_with_scenario_and_mode(
466            spec,
467            media_type,
468            expand_tokens,
469            scenario,
470            None,
471            None,
472        )
473    }
474
475    /// Generate response from a MediaType with scenario support and selection mode
476    fn generate_from_media_type_with_scenario_and_mode(
477        spec: &OpenApiSpec,
478        media_type: &openapiv3::MediaType,
479        expand_tokens: bool,
480        scenario: Option<&str>,
481        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
482        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
483    ) -> Result<Value> {
484        Self::generate_from_media_type_with_scenario_and_mode_and_persona(
485            spec,
486            media_type,
487            expand_tokens,
488            scenario,
489            selection_mode,
490            selector,
491            None, // No persona by default
492        )
493    }
494
495    /// Generate response from a MediaType with scenario support, selection mode, and persona
496    fn generate_from_media_type_with_scenario_and_mode_and_persona(
497        spec: &OpenApiSpec,
498        media_type: &openapiv3::MediaType,
499        expand_tokens: bool,
500        scenario: Option<&str>,
501        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
502        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
503        persona: Option<&Persona>,
504    ) -> Result<Value> {
505        // First, check if there's an explicit example
506        // CRITICAL: Always check examples first before falling back to schema generation
507        // This ensures GET requests use the correct response format from OpenAPI examples
508        if let Some(example) = &media_type.example {
509            tracing::debug!("Using explicit example from media type: {:?}", example);
510            // Expand templates in the example if enabled
511            if expand_tokens {
512                let expanded_example = Self::expand_templates(example);
513                return Ok(expanded_example);
514            } else {
515                return Ok(example.clone());
516            }
517        }
518
519        // Then check examples map - with scenario support and selection modes
520        // CRITICAL: Always use examples if available, even if query parameters are missing
521        // This fixes the bug where GET requests without query params return POST-style responses
522        if !media_type.examples.is_empty() {
523            use crate::openapi::response_selection::{ResponseSelectionMode, ResponseSelector};
524
525            tracing::debug!(
526                "Found {} examples in media type, available examples: {:?}",
527                media_type.examples.len(),
528                media_type.examples.keys().collect::<Vec<_>>()
529            );
530
531            // If a scenario is specified, try to find it first
532            if let Some(scenario_name) = scenario {
533                if let Some(example_ref) = media_type.examples.get(scenario_name) {
534                    tracing::debug!("Using scenario '{}' from examples map", scenario_name);
535                    match Self::extract_example_value_with_persona(
536                        spec,
537                        example_ref,
538                        expand_tokens,
539                        persona,
540                        media_type.schema.as_ref(),
541                    ) {
542                        Ok(value) => return Ok(value),
543                        Err(e) => {
544                            tracing::warn!(
545                                "Failed to extract example for scenario '{}': {}, falling back",
546                                scenario_name,
547                                e
548                            );
549                        }
550                    }
551                } else {
552                    tracing::warn!(
553                        "Scenario '{}' not found in examples, falling back based on selection mode",
554                        scenario_name
555                    );
556                }
557            }
558
559            // Determine selection mode
560            let mode = selection_mode.unwrap_or(ResponseSelectionMode::First);
561
562            // Get list of example names for selection
563            let example_names: Vec<String> = media_type.examples.keys().cloned().collect();
564
565            if example_names.is_empty() {
566                // No examples available, fall back to schema
567                tracing::warn!("Examples map is empty, falling back to schema generation");
568            } else if mode == ResponseSelectionMode::Scenario && scenario.is_some() {
569                // Scenario mode was requested but scenario not found, fall through to selection mode
570                tracing::debug!("Scenario not found, using selection mode: {:?}", mode);
571            } else {
572                // Use selection mode to choose an example
573                let selected_index = if let Some(sel) = selector {
574                    sel.select(&example_names)
575                } else {
576                    // Create temporary selector for this selection
577                    let temp_selector = ResponseSelector::new(mode);
578                    temp_selector.select(&example_names)
579                };
580
581                if let Some(example_name) = example_names.get(selected_index) {
582                    if let Some(example_ref) = media_type.examples.get(example_name) {
583                        tracing::debug!(
584                            "Using example '{}' from examples map (mode: {:?}, index: {})",
585                            example_name,
586                            mode,
587                            selected_index
588                        );
589                        match Self::extract_example_value_with_persona(
590                            spec,
591                            example_ref,
592                            expand_tokens,
593                            persona,
594                            media_type.schema.as_ref(),
595                        ) {
596                            Ok(value) => return Ok(value),
597                            Err(e) => {
598                                tracing::warn!(
599                                    "Failed to extract example '{}': {}, trying fallback",
600                                    example_name,
601                                    e
602                                );
603                            }
604                        }
605                    }
606                }
607            }
608
609            // Fall back to first example if selection failed
610            // This is critical - always use the first example if available, even if previous attempts failed
611            if let Some((example_name, example_ref)) = media_type.examples.iter().next() {
612                tracing::debug!(
613                    "Using first example '{}' from examples map as fallback",
614                    example_name
615                );
616                match Self::extract_example_value_with_persona(
617                    spec,
618                    example_ref,
619                    expand_tokens,
620                    persona,
621                    media_type.schema.as_ref(),
622                ) {
623                    Ok(value) => {
624                        tracing::debug!(
625                            "Successfully extracted fallback example '{}'",
626                            example_name
627                        );
628                        return Ok(value);
629                    }
630                    Err(e) => {
631                        tracing::error!(
632                            "Failed to extract fallback example '{}': {}, falling back to schema generation",
633                            example_name,
634                            e
635                        );
636                        // Continue to schema generation as last resort
637                    }
638                }
639            }
640        } else {
641            tracing::debug!("No examples found in media type, will use schema generation");
642        }
643
644        // Fall back to schema-based generation
645        // Pass persona through to schema generation for consistent data patterns
646        if let Some(schema_ref) = &media_type.schema {
647            Ok(Self::generate_example_from_schema_ref(spec, schema_ref, persona))
648        } else {
649            Ok(Value::Object(serde_json::Map::new()))
650        }
651    }
652
653    /// Extract value from an example reference
654    /// Optionally expands items arrays based on pagination metadata if persona is provided
655    fn extract_example_value(
656        spec: &OpenApiSpec,
657        example_ref: &ReferenceOr<openapiv3::Example>,
658        expand_tokens: bool,
659    ) -> Result<Value> {
660        Self::extract_example_value_with_persona(spec, example_ref, expand_tokens, None, None)
661    }
662
663    /// Extract value from an example reference with optional persona and schema for pagination expansion
664    fn extract_example_value_with_persona(
665        spec: &OpenApiSpec,
666        example_ref: &ReferenceOr<openapiv3::Example>,
667        expand_tokens: bool,
668        persona: Option<&Persona>,
669        schema_ref: Option<&ReferenceOr<Schema>>,
670    ) -> Result<Value> {
671        let mut value = match example_ref {
672            ReferenceOr::Item(example) => {
673                if let Some(v) = &example.value {
674                    tracing::debug!("Using example from examples map: {:?}", v);
675                    if expand_tokens {
676                        Self::expand_templates(v)
677                    } else {
678                        v.clone()
679                    }
680                } else {
681                    return Ok(Value::Object(serde_json::Map::new()));
682                }
683            }
684            ReferenceOr::Reference { reference } => {
685                // Resolve the example reference
686                if let Some(example) = spec.get_example(reference) {
687                    if let Some(v) = &example.value {
688                        tracing::debug!("Using resolved example reference: {:?}", v);
689                        if expand_tokens {
690                            Self::expand_templates(v)
691                        } else {
692                            v.clone()
693                        }
694                    } else {
695                        return Ok(Value::Object(serde_json::Map::new()));
696                    }
697                } else {
698                    tracing::warn!("Example reference '{}' not found", reference);
699                    return Ok(Value::Object(serde_json::Map::new()));
700                }
701            }
702        };
703
704        // Check for pagination mismatch and expand items array if needed
705        value = Self::expand_example_items_if_needed(spec, value, persona, schema_ref);
706
707        Ok(value)
708    }
709
710    /// Expand items array in example if pagination metadata suggests more items
711    /// Checks for common response structures: { data: { items: [...], total, limit } } or { items: [...], total, limit }
712    fn expand_example_items_if_needed(
713        spec: &OpenApiSpec,
714        mut example: Value,
715        persona: Option<&Persona>,
716        schema_ref: Option<&ReferenceOr<Schema>>,
717    ) -> Value {
718        // Try to find items array and pagination metadata in the example
719        // Support both nested (data.items) and flat (items) structures
720        let has_nested_items = example
721            .get("data")
722            .and_then(|v| v.as_object())
723            .map(|obj| obj.contains_key("items"))
724            .unwrap_or(false);
725
726        let has_flat_items = example.get("items").is_some();
727
728        if !has_nested_items && !has_flat_items {
729            return example; // No items array found
730        }
731
732        // Extract pagination metadata
733        let total = example
734            .get("data")
735            .and_then(|d| d.get("total"))
736            .or_else(|| example.get("total"))
737            .and_then(|v| v.as_u64().or_else(|| v.as_i64().map(|i| i as u64)));
738
739        let limit = example
740            .get("data")
741            .and_then(|d| d.get("limit"))
742            .or_else(|| example.get("limit"))
743            .and_then(|v| v.as_u64().or_else(|| v.as_i64().map(|i| i as u64)));
744
745        // Get current items array
746        let items_array = example
747            .get("data")
748            .and_then(|d| d.get("items"))
749            .or_else(|| example.get("items"))
750            .and_then(|v| v.as_array())
751            .cloned();
752
753        if let (Some(total_val), Some(limit_val), Some(mut items)) = (total, limit, items_array) {
754            let current_count = items.len() as u64;
755            let expected_count = std::cmp::min(total_val, limit_val);
756            let max_items = 100; // Cap at reasonable maximum
757            let expected_count = std::cmp::min(expected_count, max_items);
758
759            // If items array is smaller than expected, expand it
760            if current_count < expected_count && !items.is_empty() {
761                tracing::debug!(
762                    "Expanding example items array: {} -> {} items (total={}, limit={})",
763                    current_count,
764                    expected_count,
765                    total_val,
766                    limit_val
767                );
768
769                // Use first item as template
770                let template = items[0].clone();
771                let additional_count = expected_count - current_count;
772
773                // Generate additional items
774                for i in 0..additional_count {
775                    let mut new_item = template.clone();
776                    // Use the centralized variation function
777                    let item_index = current_count + i + 1;
778                    Self::add_item_variation(&mut new_item, item_index);
779                    items.push(new_item);
780                }
781
782                // Update the items array in the example
783                if let Some(data_obj) = example.get_mut("data").and_then(|v| v.as_object_mut()) {
784                    data_obj.insert("items".to_string(), Value::Array(items));
785                } else if let Some(root_obj) = example.as_object_mut() {
786                    root_obj.insert("items".to_string(), Value::Array(items));
787                }
788            }
789        }
790
791        example
792    }
793
794    fn generate_example_from_schema_ref(
795        spec: &OpenApiSpec,
796        schema_ref: &ReferenceOr<Schema>,
797        persona: Option<&Persona>,
798    ) -> Value {
799        match schema_ref {
800            ReferenceOr::Item(schema) => Self::generate_example_from_schema(spec, schema, persona),
801            ReferenceOr::Reference { reference } => spec
802                .get_schema(reference)
803                .map(|schema| Self::generate_example_from_schema(spec, &schema.schema, persona))
804                .unwrap_or_else(|| Value::Object(serde_json::Map::new())),
805        }
806    }
807
808    /// Generate example data from an OpenAPI schema
809    ///
810    /// Priority order:
811    /// 1. Schema-level example (schema.schema_data.example)
812    /// 2. Property-level examples when generating objects
813    /// 3. Generated values based on schema type
814    /// 4. Persona traits (if persona provided)
815    fn generate_example_from_schema(
816        spec: &OpenApiSpec,
817        schema: &Schema,
818        persona: Option<&Persona>,
819    ) -> Value {
820        // First, check for schema-level example in schema_data
821        // OpenAPI v3 stores examples in schema_data.example
822        if let Some(example) = schema.schema_data.example.as_ref() {
823            tracing::debug!("Using schema-level example: {:?}", example);
824            return example.clone();
825        }
826
827        // Note: schema-level example check happens at the top of the function (line 380-383)
828        // At this point, if we have a schema-level example, we've already returned it
829        // So we only generate defaults when no example exists
830        match &schema.schema_kind {
831            openapiv3::SchemaKind::Type(openapiv3::Type::String(_)) => {
832                // Use faker for string fields based on field name hints
833                Value::String("example string".to_string())
834            }
835            openapiv3::SchemaKind::Type(openapiv3::Type::Integer(_)) => Value::Number(42.into()),
836            openapiv3::SchemaKind::Type(openapiv3::Type::Number(_)) => {
837                Value::Number(serde_json::Number::from_f64(std::f64::consts::PI).unwrap())
838            }
839            openapiv3::SchemaKind::Type(openapiv3::Type::Boolean(_)) => Value::Bool(true),
840            openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) => {
841                // First pass: Scan for pagination metadata (total, page, limit)
842                // This helps us generate the correct number of array items
843                let mut pagination_metadata: Option<(u64, u64, u64)> = None; // (total, page, limit)
844
845                // Check if this looks like a paginated response by scanning properties
846                // Look for "items" array property and pagination fields
847                let has_items =
848                    obj.properties.iter().any(|(name, _)| name.to_lowercase() == "items");
849
850                if has_items {
851                    // Try to extract pagination metadata from schema properties
852                    let mut total_opt = None;
853                    let mut page_opt = None;
854                    let mut limit_opt = None;
855
856                    for (prop_name, prop_schema) in &obj.properties {
857                        let prop_lower = prop_name.to_lowercase();
858                        // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema> for extraction
859                        let schema_ref: ReferenceOr<Schema> = match prop_schema {
860                            ReferenceOr::Item(boxed) => ReferenceOr::Item(boxed.as_ref().clone()),
861                            ReferenceOr::Reference { reference } => ReferenceOr::Reference {
862                                reference: reference.clone(),
863                            },
864                        };
865                        if prop_lower == "total" || prop_lower == "count" || prop_lower == "size" {
866                            total_opt = Self::extract_numeric_value_from_schema(&schema_ref);
867                        } else if prop_lower == "page" {
868                            page_opt = Self::extract_numeric_value_from_schema(&schema_ref);
869                        } else if prop_lower == "limit" || prop_lower == "per_page" {
870                            limit_opt = Self::extract_numeric_value_from_schema(&schema_ref);
871                        }
872                    }
873
874                    // If we found a total, use it (with defaults for page/limit)
875                    if let Some(total) = total_opt {
876                        let page = page_opt.unwrap_or(1);
877                        let limit = limit_opt.unwrap_or(20);
878                        pagination_metadata = Some((total, page, limit));
879                        tracing::debug!(
880                            "Detected pagination metadata: total={}, page={}, limit={}",
881                            total,
882                            page,
883                            limit
884                        );
885                    } else {
886                        // Phase 3: If no total found in schema, try to infer from parent entity
887                        // Look for "items" array to determine child entity name
888                        if obj.properties.contains_key("items") {
889                            // Try to infer parent/child relationship from schema names
890                            // This is a heuristic: if we're generating a paginated response,
891                            // check if we can find a parent entity schema with a count field
892                            if let Some(inferred_total) =
893                                Self::try_infer_total_from_context(spec, obj)
894                            {
895                                let page = page_opt.unwrap_or(1);
896                                let limit = limit_opt.unwrap_or(20);
897                                pagination_metadata = Some((inferred_total, page, limit));
898                                tracing::debug!(
899                                    "Inferred pagination metadata from parent entity: total={}, page={}, limit={}",
900                                    inferred_total, page, limit
901                                );
902                            } else {
903                                // Phase 4: Try to use persona traits if available
904                                if let Some(persona) = persona {
905                                    // Look for count-related traits (e.g., "hive_count", "apiary_count")
906                                    // Try common patterns
907                                    let count_keys =
908                                        ["hive_count", "apiary_count", "item_count", "total_count"];
909                                    for key in &count_keys {
910                                        if let Some(count) = persona.get_numeric_trait(key) {
911                                            let page = page_opt.unwrap_or(1);
912                                            let limit = limit_opt.unwrap_or(20);
913                                            pagination_metadata = Some((count, page, limit));
914                                            tracing::debug!(
915                                                "Using persona trait '{}' for pagination: total={}, page={}, limit={}",
916                                                key, count, page, limit
917                                            );
918                                            break;
919                                        }
920                                    }
921                                }
922                            }
923                        }
924                    }
925                }
926
927                let mut map = serde_json::Map::new();
928                for (prop_name, prop_schema) in &obj.properties {
929                    let prop_lower = prop_name.to_lowercase();
930
931                    // Check if this is an array property that should use pagination metadata
932                    let is_items_array = prop_lower == "items" && pagination_metadata.is_some();
933
934                    let value = match prop_schema {
935                        ReferenceOr::Item(prop_schema) => {
936                            // If this is an items array with pagination metadata, always use generate_array_with_count
937                            // (it will use the example as a template if one exists)
938                            if is_items_array {
939                                // Generate array with count based on pagination metadata
940                                Self::generate_array_with_count(
941                                    spec,
942                                    prop_schema.as_ref(),
943                                    pagination_metadata.unwrap(),
944                                    persona,
945                                )
946                            } else if let Some(prop_example) =
947                                prop_schema.schema_data.example.as_ref()
948                            {
949                                // Check for property-level example (only if not items array)
950                                tracing::debug!(
951                                    "Using example for property '{}': {:?}",
952                                    prop_name,
953                                    prop_example
954                                );
955                                prop_example.clone()
956                            } else {
957                                Self::generate_example_from_schema(
958                                    spec,
959                                    prop_schema.as_ref(),
960                                    persona,
961                                )
962                            }
963                        }
964                        ReferenceOr::Reference { reference } => {
965                            // Try to resolve reference
966                            if let Some(resolved_schema) = spec.get_schema(reference) {
967                                // If this is an items array with pagination metadata, always use generate_array_with_count
968                                if is_items_array {
969                                    // Generate array with count based on pagination metadata
970                                    Self::generate_array_with_count(
971                                        spec,
972                                        &resolved_schema.schema,
973                                        pagination_metadata.unwrap(),
974                                        persona,
975                                    )
976                                } else if let Some(ref_example) =
977                                    resolved_schema.schema.schema_data.example.as_ref()
978                                {
979                                    // Check for example from referenced schema (only if not items array)
980                                    tracing::debug!(
981                                        "Using example from referenced schema '{}': {:?}",
982                                        reference,
983                                        ref_example
984                                    );
985                                    ref_example.clone()
986                                } else {
987                                    Self::generate_example_from_schema(
988                                        spec,
989                                        &resolved_schema.schema,
990                                        persona,
991                                    )
992                                }
993                            } else {
994                                Self::generate_example_for_property(prop_name)
995                            }
996                        }
997                    };
998                    let value = match value {
999                        Value::Null => Self::generate_example_for_property(prop_name),
1000                        Value::Object(ref obj) if obj.is_empty() => {
1001                            Self::generate_example_for_property(prop_name)
1002                        }
1003                        _ => value,
1004                    };
1005                    map.insert(prop_name.clone(), value);
1006                }
1007
1008                // Ensure pagination metadata is set if we detected it
1009                if let Some((total, page, limit)) = pagination_metadata {
1010                    map.insert("total".to_string(), Value::Number(total.into()));
1011                    map.insert("page".to_string(), Value::Number(page.into()));
1012                    map.insert("limit".to_string(), Value::Number(limit.into()));
1013                }
1014
1015                Value::Object(map)
1016            }
1017            openapiv3::SchemaKind::Type(openapiv3::Type::Array(arr)) => {
1018                // Check for array-level example (schema.schema_data.example contains the full array)
1019                // Note: This check is actually redundant since we check at the top,
1020                // but keeping it here for clarity and defensive programming
1021                // If the array schema itself has an example, it's already handled at the top
1022
1023                match &arr.items {
1024                    Some(item_schema) => {
1025                        let example_item = match item_schema {
1026                            ReferenceOr::Item(item_schema) => {
1027                                // Recursively generate example for array item
1028                                // This will check for item-level examples
1029                                Self::generate_example_from_schema(
1030                                    spec,
1031                                    item_schema.as_ref(),
1032                                    persona,
1033                                )
1034                            }
1035                            ReferenceOr::Reference { reference } => {
1036                                // Try to resolve reference and generate example
1037                                // This will check for examples in referenced schema
1038                                if let Some(resolved_schema) = spec.get_schema(reference) {
1039                                    Self::generate_example_from_schema(
1040                                        spec,
1041                                        &resolved_schema.schema,
1042                                        persona,
1043                                    )
1044                                } else {
1045                                    Value::Object(serde_json::Map::new())
1046                                }
1047                            }
1048                        };
1049                        Value::Array(vec![example_item])
1050                    }
1051                    None => Value::Array(vec![Value::String("item".to_string())]),
1052                }
1053            }
1054            _ => Value::Object(serde_json::Map::new()),
1055        }
1056    }
1057
1058    /// Extract numeric value from a schema (from example or default)
1059    /// Returns None if no numeric value can be extracted
1060    fn extract_numeric_value_from_schema(schema_ref: &ReferenceOr<Schema>) -> Option<u64> {
1061        match schema_ref {
1062            ReferenceOr::Item(schema) => {
1063                // Check for example value first
1064                if let Some(example) = schema.schema_data.example.as_ref() {
1065                    if let Some(num) = example.as_u64() {
1066                        return Some(num);
1067                    } else if let Some(num) = example.as_f64() {
1068                        return Some(num as u64);
1069                    }
1070                }
1071                // Check for default value
1072                if let Some(default) = schema.schema_data.default.as_ref() {
1073                    if let Some(num) = default.as_u64() {
1074                        return Some(num);
1075                    } else if let Some(num) = default.as_f64() {
1076                        return Some(num as u64);
1077                    }
1078                }
1079                // For integer types, try to extract from schema constraints
1080                // Note: IntegerType doesn't have a default field in openapiv3
1081                // Defaults are stored in schema_data.default instead
1082                None
1083            }
1084            ReferenceOr::Reference { reference: _ } => {
1085                // For references, we'd need to resolve them, but for now return None
1086                // This can be enhanced later if needed
1087                None
1088            }
1089        }
1090    }
1091
1092    /// Generate an array with a specific count based on pagination metadata
1093    /// Respects the limit (e.g., if total=50 and limit=20, generates 20 items)
1094    fn generate_array_with_count(
1095        spec: &OpenApiSpec,
1096        array_schema: &Schema,
1097        pagination: (u64, u64, u64), // (total, page, limit)
1098        persona: Option<&Persona>,
1099    ) -> Value {
1100        let (total, _page, limit) = pagination;
1101
1102        // Determine how many items to generate
1103        // Respect pagination: generate min(total, limit) items
1104        let count = std::cmp::min(total, limit);
1105
1106        // Cap at reasonable maximum to avoid performance issues
1107        let max_items = 100;
1108        let count = std::cmp::min(count, max_items);
1109
1110        tracing::debug!("Generating array with count={} (total={}, limit={})", count, total, limit);
1111
1112        // Check if array schema has an example with items
1113        if let Some(example) = array_schema.schema_data.example.as_ref() {
1114            if let Some(example_array) = example.as_array() {
1115                if !example_array.is_empty() {
1116                    // Use first example item as template
1117                    let template_item = &example_array[0];
1118                    let items: Vec<Value> = (0..count)
1119                        .map(|i| {
1120                            // Clone template and add variation
1121                            let mut item = template_item.clone();
1122                            Self::add_item_variation(&mut item, i + 1);
1123                            item
1124                        })
1125                        .collect();
1126                    return Value::Array(items);
1127                }
1128            }
1129        }
1130
1131        // Generate items from schema
1132        if let openapiv3::SchemaKind::Type(openapiv3::Type::Array(arr)) = &array_schema.schema_kind
1133        {
1134            if let Some(item_schema) = &arr.items {
1135                let items: Vec<Value> = match item_schema {
1136                    ReferenceOr::Item(item_schema) => {
1137                        (0..count)
1138                            .map(|i| {
1139                                let mut item = Self::generate_example_from_schema(
1140                                    spec,
1141                                    item_schema.as_ref(),
1142                                    persona,
1143                                );
1144                                // Add variation to make items unique
1145                                Self::add_item_variation(&mut item, i + 1);
1146                                item
1147                            })
1148                            .collect()
1149                    }
1150                    ReferenceOr::Reference { reference } => {
1151                        if let Some(resolved_schema) = spec.get_schema(reference) {
1152                            (0..count)
1153                                .map(|i| {
1154                                    let mut item = Self::generate_example_from_schema(
1155                                        spec,
1156                                        &resolved_schema.schema,
1157                                        persona,
1158                                    );
1159                                    // Add variation to make items unique
1160                                    Self::add_item_variation(&mut item, i + 1);
1161                                    item
1162                                })
1163                                .collect()
1164                        } else {
1165                            vec![Value::Object(serde_json::Map::new()); count as usize]
1166                        }
1167                    }
1168                };
1169                return Value::Array(items);
1170            }
1171        }
1172
1173        // Fallback: generate simple items
1174        Value::Array((0..count).map(|i| Value::String(format!("item_{}", i + 1))).collect())
1175    }
1176
1177    /// Add variation to an item to make it unique (for array generation)
1178    /// Varies IDs, names, addresses, and coordinates based on item index
1179    fn add_item_variation(item: &mut Value, item_index: u64) {
1180        if let Some(obj) = item.as_object_mut() {
1181            // Update ID fields to be unique
1182            if let Some(id_val) = obj.get_mut("id") {
1183                if let Some(id_str) = id_val.as_str() {
1184                    // Extract base ID (remove any existing suffix)
1185                    let base_id = id_str.split('_').next().unwrap_or(id_str);
1186                    *id_val = Value::String(format!("{}_{:03}", base_id, item_index));
1187                } else if let Some(id_num) = id_val.as_u64() {
1188                    *id_val = Value::Number((id_num + item_index).into());
1189                }
1190            }
1191
1192            // Update name fields - add variation for all names
1193            if let Some(name_val) = obj.get_mut("name") {
1194                if let Some(name_str) = name_val.as_str() {
1195                    if name_str.contains('#') {
1196                        // Pattern like "Hive #1" -> "Hive #2"
1197                        *name_val = Value::String(format!("Hive #{}", item_index));
1198                    } else {
1199                        // Pattern like "Meadow Apiary" -> use rotation of varied names
1200                        // 60+ unique apiary names with geographic diversity for realistic demo
1201                        let apiary_names = [
1202                            // Midwest/Prairie names
1203                            "Meadow Apiary",
1204                            "Prairie Apiary",
1205                            "Sunset Valley Apiary",
1206                            "Golden Fields Apiary",
1207                            "Miller Family Apiary",
1208                            "Heartland Honey Co.",
1209                            "Cornfield Apiary",
1210                            "Harvest Moon Apiary",
1211                            "Prairie Winds Apiary",
1212                            "Amber Fields Apiary",
1213                            // California/Coastal names
1214                            "Coastal Apiary",
1215                            "Sunset Coast Apiary",
1216                            "Pacific Grove Apiary",
1217                            "Golden Gate Apiary",
1218                            "Napa Valley Apiary",
1219                            "Coastal Breeze Apiary",
1220                            "Pacific Heights Apiary",
1221                            "Bay Area Apiary",
1222                            "Sunset Valley Honey Co.",
1223                            "Coastal Harvest Apiary",
1224                            // Texas/Ranch names
1225                            "Lone Star Apiary",
1226                            "Texas Ranch Apiary",
1227                            "Big Sky Apiary",
1228                            "Prairie Rose Apiary",
1229                            "Hill Country Apiary",
1230                            "Lone Star Honey Co.",
1231                            "Texas Pride Apiary",
1232                            "Wildflower Ranch",
1233                            "Desert Bloom Apiary",
1234                            "Cactus Creek Apiary",
1235                            // Florida/Grove names
1236                            "Orange Grove Apiary",
1237                            "Citrus Grove Apiary",
1238                            "Palm Grove Apiary",
1239                            "Tropical Breeze Apiary",
1240                            "Everglades Apiary",
1241                            "Sunshine State Apiary",
1242                            "Florida Keys Apiary",
1243                            "Grove View Apiary",
1244                            "Tropical Harvest Apiary",
1245                            "Palm Coast Apiary",
1246                            // Northeast/Valley names
1247                            "Mountain View Apiary",
1248                            "Valley Apiary",
1249                            "Riverside Apiary",
1250                            "Hilltop Apiary",
1251                            "Forest Apiary",
1252                            "Mountain Apiary",
1253                            "Lakeside Apiary",
1254                            "Ridge Apiary",
1255                            "Brook Apiary",
1256                            "Hillside Apiary",
1257                            // Generic/Professional names
1258                            "Field Apiary",
1259                            "Creek Apiary",
1260                            "Woodland Apiary",
1261                            "Farm Apiary",
1262                            "Orchard Apiary",
1263                            "Pasture Apiary",
1264                            "Green Valley Apiary",
1265                            "Blue Sky Apiary",
1266                            "Sweet Honey Apiary",
1267                            "Nature's Best Apiary",
1268                            // Business/Commercial names
1269                            "Premium Honey Co.",
1270                            "Artisan Apiary",
1271                            "Heritage Apiary",
1272                            "Summit Apiary",
1273                            "Crystal Springs Apiary",
1274                            "Maple Grove Apiary",
1275                            "Wildflower Apiary",
1276                            "Thistle Apiary",
1277                            "Clover Field Apiary",
1278                            "Honeycomb Apiary",
1279                        ];
1280                        let name_index = (item_index - 1) as usize % apiary_names.len();
1281                        *name_val = Value::String(apiary_names[name_index].to_string());
1282                    }
1283                }
1284            }
1285
1286            // Update location/address fields
1287            if let Some(location_val) = obj.get_mut("location") {
1288                if let Some(location_obj) = location_val.as_object_mut() {
1289                    // Update address
1290                    if let Some(address_val) = location_obj.get_mut("address") {
1291                        if let Some(address_str) = address_val.as_str() {
1292                            // Extract street number if present, otherwise add variation
1293                            if let Some(num_str) = address_str.split_whitespace().next() {
1294                                if let Ok(num) = num_str.parse::<u64>() {
1295                                    *address_val =
1296                                        Value::String(format!("{} Farm Road", num + item_index));
1297                                } else {
1298                                    *address_val =
1299                                        Value::String(format!("{} Farm Road", 100 + item_index));
1300                                }
1301                            } else {
1302                                *address_val =
1303                                    Value::String(format!("{} Farm Road", 100 + item_index));
1304                            }
1305                        }
1306                    }
1307
1308                    // Vary coordinates slightly
1309                    if let Some(lat_val) = location_obj.get_mut("latitude") {
1310                        if let Some(lat) = lat_val.as_f64() {
1311                            *lat_val = Value::Number(
1312                                serde_json::Number::from_f64(lat + (item_index as f64 * 0.01))
1313                                    .unwrap(),
1314                            );
1315                        }
1316                    }
1317                    if let Some(lng_val) = location_obj.get_mut("longitude") {
1318                        if let Some(lng) = lng_val.as_f64() {
1319                            *lng_val = Value::Number(
1320                                serde_json::Number::from_f64(lng + (item_index as f64 * 0.01))
1321                                    .unwrap(),
1322                            );
1323                        }
1324                    }
1325                } else if let Some(address_str) = location_val.as_str() {
1326                    // Flat address string
1327                    if let Some(num_str) = address_str.split_whitespace().next() {
1328                        if let Ok(num) = num_str.parse::<u64>() {
1329                            *location_val =
1330                                Value::String(format!("{} Farm Road", num + item_index));
1331                        } else {
1332                            *location_val =
1333                                Value::String(format!("{} Farm Road", 100 + item_index));
1334                        }
1335                    }
1336                }
1337            }
1338
1339            // Update address field if it exists at root level
1340            if let Some(address_val) = obj.get_mut("address") {
1341                if let Some(address_str) = address_val.as_str() {
1342                    if let Some(num_str) = address_str.split_whitespace().next() {
1343                        if let Ok(num) = num_str.parse::<u64>() {
1344                            *address_val = Value::String(format!("{} Farm Road", num + item_index));
1345                        } else {
1346                            *address_val = Value::String(format!("{} Farm Road", 100 + item_index));
1347                        }
1348                    }
1349                }
1350            }
1351
1352            // Vary status fields (common enum values)
1353            if let Some(status_val) = obj.get_mut("status") {
1354                if let Some(status_str) = status_val.as_str() {
1355                    let statuses = [
1356                        "healthy",
1357                        "sick",
1358                        "needs_attention",
1359                        "quarantined",
1360                        "active",
1361                        "inactive",
1362                    ];
1363                    let status_index = (item_index - 1) as usize % statuses.len();
1364                    // Bias towards "healthy" and "active" (70% of items)
1365                    let final_status = if (item_index - 1) % 10 < 7 {
1366                        statuses[0] // "healthy" or "active"
1367                    } else {
1368                        statuses[status_index]
1369                    };
1370                    *status_val = Value::String(final_status.to_string());
1371                }
1372            }
1373
1374            // Vary hive_type fields
1375            if let Some(hive_type_val) = obj.get_mut("hive_type") {
1376                if hive_type_val.as_str().is_some() {
1377                    let hive_types = ["langstroth", "top_bar", "warre", "flow_hive", "national"];
1378                    let type_index = (item_index - 1) as usize % hive_types.len();
1379                    *hive_type_val = Value::String(hive_types[type_index].to_string());
1380                }
1381            }
1382
1383            // Vary nested queen breed fields
1384            if let Some(queen_val) = obj.get_mut("queen") {
1385                if let Some(queen_obj) = queen_val.as_object_mut() {
1386                    if let Some(breed_val) = queen_obj.get_mut("breed") {
1387                        if breed_val.as_str().is_some() {
1388                            let breeds =
1389                                ["italian", "carniolan", "russian", "buckfast", "caucasian"];
1390                            let breed_index = (item_index - 1) as usize % breeds.len();
1391                            *breed_val = Value::String(breeds[breed_index].to_string());
1392                        }
1393                    }
1394                    // Vary queen age
1395                    if let Some(age_val) = queen_obj.get_mut("age_days") {
1396                        if let Some(base_age) = age_val.as_u64() {
1397                            *age_val = Value::Number((base_age + (item_index * 10) % 200).into());
1398                        } else if let Some(base_age) = age_val.as_i64() {
1399                            *age_val =
1400                                Value::Number((base_age + (item_index as i64 * 10) % 200).into());
1401                        }
1402                    }
1403                    // Vary queen mark color
1404                    if let Some(color_val) = queen_obj.get_mut("mark_color") {
1405                        if color_val.as_str().is_some() {
1406                            let colors = ["yellow", "white", "red", "green", "blue"];
1407                            let color_index = (item_index - 1) as usize % colors.len();
1408                            *color_val = Value::String(colors[color_index].to_string());
1409                        }
1410                    }
1411                }
1412            }
1413
1414            // Vary description fields if they exist
1415            if let Some(desc_val) = obj.get_mut("description") {
1416                if let Some(desc_str) = desc_val.as_str() {
1417                    let descriptions = [
1418                        "Production apiary",
1419                        "Research apiary",
1420                        "Commercial operation",
1421                        "Backyard apiary",
1422                        "Educational apiary",
1423                    ];
1424                    let desc_index = (item_index - 1) as usize % descriptions.len();
1425                    *desc_val = Value::String(descriptions[desc_index].to_string());
1426                }
1427            }
1428
1429            // Vary timestamp fields (created_at, updated_at, timestamp, date) for realistic time-series data
1430            // Generate timestamps spanning 12-24 months with proper distribution
1431            let timestamp_fields = [
1432                "created_at",
1433                "updated_at",
1434                "timestamp",
1435                "date",
1436                "forecastDate",
1437                "predictedDate",
1438            ];
1439            for field_name in &timestamp_fields {
1440                if let Some(timestamp_val) = obj.get_mut(*field_name) {
1441                    if let Some(_timestamp_str) = timestamp_val.as_str() {
1442                        // Generate realistic timestamp: distribute items over past 12-18 months
1443                        // Use item_index to create variation (not all same date)
1444                        let months_ago = 12 + ((item_index - 1) % 6); // Distribute over 6 months (12-18 months ago)
1445                        let days_offset = (item_index - 1) % 28; // Distribute within month (cap at 28)
1446                        let hours_offset = ((item_index * 7) % 24) as u8; // Distribute throughout day
1447                        let minutes_offset = ((item_index * 11) % 60) as u8; // Vary minutes
1448
1449                        // Calculate timestamp relative to current date (November 2024)
1450                        // Format: ISO 8601 (e.g., "2024-11-12T14:30:00Z")
1451                        let base_year = 2024;
1452                        let base_month = 11;
1453
1454                        // Calculate target month (going back in time)
1455                        let target_year = if months_ago >= base_month as u64 {
1456                            base_year - 1
1457                        } else {
1458                            base_year
1459                        };
1460                        let target_month = if months_ago >= base_month as u64 {
1461                            12 - (months_ago - base_month as u64) as u8
1462                        } else {
1463                            (base_month as u64 - months_ago) as u8
1464                        };
1465                        let target_day = std::cmp::min(28, 1 + days_offset as u8); // Start from day 1, cap at 28
1466
1467                        // Format as ISO 8601
1468                        let timestamp = format!(
1469                            "{:04}-{:02}-{:02}T{:02}:{:02}:00Z",
1470                            target_year, target_month, target_day, hours_offset, minutes_offset
1471                        );
1472                        *timestamp_val = Value::String(timestamp);
1473                    }
1474                }
1475            }
1476        }
1477    }
1478
1479    /// Try to infer total count from context (parent entity schemas)
1480    /// This is a heuristic that looks for common relationship patterns
1481    fn try_infer_total_from_context(
1482        spec: &OpenApiSpec,
1483        obj_type: &openapiv3::ObjectType,
1484    ) -> Option<u64> {
1485        // Look for "items" array to determine what we're generating
1486        if let Some(items_schema_ref) = obj_type.properties.get("items") {
1487            // Try to determine child entity name from items schema
1488            // This is a heuristic: check schema names in the spec
1489            if let Some(components) = &spec.spec.components {
1490                let schemas = &components.schemas;
1491                // Look through all schemas to find potential parent entities
1492                // that might have count fields matching the items type
1493                for (schema_name, schema_ref) in schemas {
1494                    if let ReferenceOr::Item(schema) = schema_ref {
1495                        if let openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) =
1496                            &schema.schema_kind
1497                        {
1498                            // Look for count fields that might match
1499                            for (prop_name, prop_schema) in &obj.properties {
1500                                let prop_lower = prop_name.to_lowercase();
1501                                if prop_lower.ends_with("_count") {
1502                                    // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema>
1503                                    let schema_ref: ReferenceOr<Schema> = match prop_schema {
1504                                        ReferenceOr::Item(boxed) => {
1505                                            ReferenceOr::Item(boxed.as_ref().clone())
1506                                        }
1507                                        ReferenceOr::Reference { reference } => {
1508                                            ReferenceOr::Reference {
1509                                                reference: reference.clone(),
1510                                            }
1511                                        }
1512                                    };
1513                                    // Found a count field, try to extract its value
1514                                    if let Some(count) =
1515                                        Self::extract_numeric_value_from_schema(&schema_ref)
1516                                    {
1517                                        // Use a reasonable default if count is very large
1518                                        if count > 0 && count <= 1000 {
1519                                            tracing::debug!(
1520                                                "Inferred count {} from parent schema {} field {}",
1521                                                count,
1522                                                schema_name,
1523                                                prop_name
1524                                            );
1525                                            return Some(count);
1526                                        }
1527                                    }
1528                                }
1529                            }
1530                        }
1531                    }
1532                }
1533            }
1534        }
1535
1536        None
1537    }
1538
1539    /// Infer relationship count from parent entity schema
1540    /// When generating a child entity list, check if parent entity has a count field
1541    fn infer_count_from_parent_schema(
1542        spec: &OpenApiSpec,
1543        parent_entity_name: &str,
1544        child_entity_name: &str,
1545    ) -> Option<u64> {
1546        // Look for parent entity schema
1547        let parent_schema_name = parent_entity_name.to_string();
1548        let count_field_name = format!("{}_count", child_entity_name);
1549
1550        // Try to find the schema
1551        if let Some(components) = &spec.spec.components {
1552            let schemas = &components.schemas;
1553            // Look for parent schema (case-insensitive)
1554            for (schema_name, schema_ref) in schemas {
1555                let schema_name_lower = schema_name.to_lowercase();
1556                if schema_name_lower.contains(&parent_entity_name.to_lowercase()) {
1557                    if let ReferenceOr::Item(schema) = schema_ref {
1558                        // Check if this schema has the count field
1559                        if let openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) =
1560                            &schema.schema_kind
1561                        {
1562                            for (prop_name, prop_schema) in &obj.properties {
1563                                if prop_name.to_lowercase() == count_field_name.to_lowercase() {
1564                                    // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema>
1565                                    let schema_ref: ReferenceOr<Schema> = match prop_schema {
1566                                        ReferenceOr::Item(boxed) => {
1567                                            ReferenceOr::Item(boxed.as_ref().clone())
1568                                        }
1569                                        ReferenceOr::Reference { reference } => {
1570                                            ReferenceOr::Reference {
1571                                                reference: reference.clone(),
1572                                            }
1573                                        }
1574                                    };
1575                                    // Extract count value from schema
1576                                    return Self::extract_numeric_value_from_schema(&schema_ref);
1577                                }
1578                            }
1579                        }
1580                    }
1581                }
1582            }
1583        }
1584
1585        None
1586    }
1587
1588    /// Generate example value for a property based on its name
1589    fn generate_example_for_property(prop_name: &str) -> Value {
1590        let prop_lower = prop_name.to_lowercase();
1591
1592        // Generate realistic data based on property name patterns
1593        if prop_lower.contains("id") || prop_lower.contains("uuid") {
1594            Value::String(uuid::Uuid::new_v4().to_string())
1595        } else if prop_lower.contains("email") {
1596            Value::String(format!("user{}@example.com", thread_rng().random_range(1000..=9999)))
1597        } else if prop_lower.contains("name") || prop_lower.contains("title") {
1598            let names = ["John Doe", "Jane Smith", "Bob Johnson", "Alice Brown"];
1599            Value::String(names[thread_rng().random_range(0..names.len())].to_string())
1600        } else if prop_lower.contains("phone") || prop_lower.contains("mobile") {
1601            Value::String(format!("+1-555-{:04}", thread_rng().random_range(1000..=9999)))
1602        } else if prop_lower.contains("address") || prop_lower.contains("street") {
1603            let streets = ["123 Main St", "456 Oak Ave", "789 Pine Rd", "321 Elm St"];
1604            Value::String(streets[thread_rng().random_range(0..streets.len())].to_string())
1605        } else if prop_lower.contains("city") {
1606            let cities = ["New York", "London", "Tokyo", "Paris", "Sydney"];
1607            Value::String(cities[thread_rng().random_range(0..cities.len())].to_string())
1608        } else if prop_lower.contains("country") {
1609            let countries = ["USA", "UK", "Japan", "France", "Australia"];
1610            Value::String(countries[thread_rng().random_range(0..countries.len())].to_string())
1611        } else if prop_lower.contains("company") || prop_lower.contains("organization") {
1612            let companies = ["Acme Corp", "Tech Solutions", "Global Inc", "Innovate Ltd"];
1613            Value::String(companies[thread_rng().random_range(0..companies.len())].to_string())
1614        } else if prop_lower.contains("url") || prop_lower.contains("website") {
1615            Value::String("https://example.com".to_string())
1616        } else if prop_lower.contains("age") {
1617            Value::Number((18 + thread_rng().random_range(0..60)).into())
1618        } else if prop_lower.contains("count") || prop_lower.contains("quantity") {
1619            Value::Number((1 + thread_rng().random_range(0..100)).into())
1620        } else if prop_lower.contains("price")
1621            || prop_lower.contains("amount")
1622            || prop_lower.contains("cost")
1623        {
1624            Value::Number(
1625                serde_json::Number::from_f64(
1626                    (thread_rng().random::<f64>() * 1000.0 * 100.0).round() / 100.0,
1627                )
1628                .unwrap(),
1629            )
1630        } else if prop_lower.contains("active")
1631            || prop_lower.contains("enabled")
1632            || prop_lower.contains("is_")
1633        {
1634            Value::Bool(thread_rng().random_bool(0.5))
1635        } else if prop_lower.contains("date") || prop_lower.contains("time") {
1636            Value::String(chrono::Utc::now().to_rfc3339())
1637        } else if prop_lower.contains("description") || prop_lower.contains("comment") {
1638            Value::String("This is a sample description text.".to_string())
1639        } else {
1640            Value::String(format!("example {}", prop_name))
1641        }
1642    }
1643
1644    /// Generate example responses from OpenAPI examples
1645    pub fn generate_from_examples(
1646        response: &Response,
1647        content_type: Option<&str>,
1648    ) -> Result<Option<Value>> {
1649        use openapiv3::ReferenceOr;
1650
1651        // If content_type is specified, look for examples in that media type
1652        if let Some(content_type) = content_type {
1653            if let Some(media_type) = response.content.get(content_type) {
1654                // Check for single example first
1655                if let Some(example) = &media_type.example {
1656                    return Ok(Some(example.clone()));
1657                }
1658
1659                // Check for multiple examples
1660                for (_, example_ref) in &media_type.examples {
1661                    if let ReferenceOr::Item(example) = example_ref {
1662                        if let Some(value) = &example.value {
1663                            return Ok(Some(value.clone()));
1664                        }
1665                    }
1666                    // Reference resolution would require spec parameter to be added to this function
1667                }
1668            }
1669        }
1670
1671        // If no content_type specified or not found, check all media types
1672        for (_, media_type) in &response.content {
1673            // Check for single example first
1674            if let Some(example) = &media_type.example {
1675                return Ok(Some(example.clone()));
1676            }
1677
1678            // Check for multiple examples
1679            for (_, example_ref) in &media_type.examples {
1680                if let ReferenceOr::Item(example) = example_ref {
1681                    if let Some(value) = &example.value {
1682                        return Ok(Some(value.clone()));
1683                    }
1684                }
1685                // Reference resolution would require spec parameter to be added to this function
1686            }
1687        }
1688
1689        Ok(None)
1690    }
1691
1692    /// Expand templates like {{now}} and {{uuid}} in JSON values
1693    fn expand_templates(value: &Value) -> Value {
1694        match value {
1695            Value::String(s) => {
1696                let expanded = s
1697                    .replace("{{now}}", &chrono::Utc::now().to_rfc3339())
1698                    .replace("{{uuid}}", &uuid::Uuid::new_v4().to_string());
1699                Value::String(expanded)
1700            }
1701            Value::Object(map) => {
1702                let mut new_map = serde_json::Map::new();
1703                for (key, val) in map {
1704                    new_map.insert(key.clone(), Self::expand_templates(val));
1705                }
1706                Value::Object(new_map)
1707            }
1708            Value::Array(arr) => {
1709                let new_arr: Vec<Value> = arr.iter().map(Self::expand_templates).collect();
1710                Value::Array(new_arr)
1711            }
1712            _ => value.clone(),
1713        }
1714    }
1715}
1716
1717#[cfg(test)]
1718mod tests {
1719    use super::*;
1720    use openapiv3::ReferenceOr;
1721
1722    #[test]
1723    fn generates_example_using_referenced_schemas() {
1724        let yaml = r#"
1725openapi: 3.0.3
1726info:
1727  title: Test API
1728  version: "1.0.0"
1729paths:
1730  /apiaries:
1731    get:
1732      responses:
1733        '200':
1734          description: ok
1735          content:
1736            application/json:
1737              schema:
1738                $ref: '#/components/schemas/Apiary'
1739components:
1740  schemas:
1741    Apiary:
1742      type: object
1743      properties:
1744        id:
1745          type: string
1746        hive:
1747          $ref: '#/components/schemas/Hive'
1748    Hive:
1749      type: object
1750      properties:
1751        name:
1752          type: string
1753        active:
1754          type: boolean
1755        "#;
1756
1757        let spec = OpenApiSpec::from_string(yaml, Some("yaml")).expect("load spec");
1758        let path_item = spec
1759            .spec
1760            .paths
1761            .paths
1762            .get("/apiaries")
1763            .and_then(ReferenceOr::as_item)
1764            .expect("path item");
1765        let operation = path_item.get.as_ref().expect("GET operation");
1766
1767        let response =
1768            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1769                .expect("generate response");
1770
1771        let obj = response.as_object().expect("response object");
1772        assert!(obj.contains_key("id"));
1773        let hive = obj.get("hive").and_then(|value| value.as_object()).expect("hive object");
1774        assert!(hive.contains_key("name"));
1775        assert!(hive.contains_key("active"));
1776    }
1777}
1778
1779/// Mock response data
1780#[derive(Debug, Clone)]
1781pub struct MockResponse {
1782    /// HTTP status code
1783    pub status_code: u16,
1784    /// Response headers
1785    pub headers: HashMap<String, String>,
1786    /// Response body
1787    pub body: Option<Value>,
1788}
1789
1790impl MockResponse {
1791    /// Create a new mock response
1792    pub fn new(status_code: u16) -> Self {
1793        Self {
1794            status_code,
1795            headers: HashMap::new(),
1796            body: None,
1797        }
1798    }
1799
1800    /// Add a header to the response
1801    pub fn with_header(mut self, name: String, value: String) -> Self {
1802        self.headers.insert(name, value);
1803        self
1804    }
1805
1806    /// Set the response body
1807    pub fn with_body(mut self, body: Value) -> Self {
1808        self.body = Some(body);
1809        self
1810    }
1811}
1812
1813/// OpenAPI security requirement wrapper
1814#[derive(Debug, Clone)]
1815pub struct OpenApiSecurityRequirement {
1816    /// The security scheme name
1817    pub scheme: String,
1818    /// Required scopes (for OAuth2)
1819    pub scopes: Vec<String>,
1820}
1821
1822impl OpenApiSecurityRequirement {
1823    /// Create a new security requirement
1824    pub fn new(scheme: String, scopes: Vec<String>) -> Self {
1825        Self { scheme, scopes }
1826    }
1827}
1828
1829/// OpenAPI operation wrapper with path context
1830#[derive(Debug, Clone)]
1831pub struct OpenApiOperation {
1832    /// The HTTP method
1833    pub method: String,
1834    /// The path this operation belongs to
1835    pub path: String,
1836    /// The OpenAPI operation
1837    pub operation: openapiv3::Operation,
1838}
1839
1840impl OpenApiOperation {
1841    /// Create a new OpenApiOperation
1842    pub fn new(method: String, path: String, operation: openapiv3::Operation) -> Self {
1843        Self {
1844            method,
1845            path,
1846            operation,
1847        }
1848    }
1849}