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(_)) => Value::Number(
837                serde_json::Number::from_f64(std::f64::consts::PI)
838                    .expect("PI is a valid f64 value"),
839            ),
840            openapiv3::SchemaKind::Type(openapiv3::Type::Boolean(_)) => Value::Bool(true),
841            openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) => {
842                // First pass: Scan for pagination metadata (total, page, limit)
843                // This helps us generate the correct number of array items
844                let mut pagination_metadata: Option<(u64, u64, u64)> = None; // (total, page, limit)
845
846                // Check if this looks like a paginated response by scanning properties
847                // Look for "items" array property and pagination fields
848                let has_items =
849                    obj.properties.iter().any(|(name, _)| name.to_lowercase() == "items");
850
851                if has_items {
852                    // Try to extract pagination metadata from schema properties
853                    let mut total_opt = None;
854                    let mut page_opt = None;
855                    let mut limit_opt = None;
856
857                    for (prop_name, prop_schema) in &obj.properties {
858                        let prop_lower = prop_name.to_lowercase();
859                        // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema> for extraction
860                        let schema_ref: ReferenceOr<Schema> = match prop_schema {
861                            ReferenceOr::Item(boxed) => ReferenceOr::Item(boxed.as_ref().clone()),
862                            ReferenceOr::Reference { reference } => ReferenceOr::Reference {
863                                reference: reference.clone(),
864                            },
865                        };
866                        if prop_lower == "total" || prop_lower == "count" || prop_lower == "size" {
867                            total_opt = Self::extract_numeric_value_from_schema(&schema_ref);
868                        } else if prop_lower == "page" {
869                            page_opt = Self::extract_numeric_value_from_schema(&schema_ref);
870                        } else if prop_lower == "limit" || prop_lower == "per_page" {
871                            limit_opt = Self::extract_numeric_value_from_schema(&schema_ref);
872                        }
873                    }
874
875                    // If we found a total, use it (with defaults for page/limit)
876                    if let Some(total) = total_opt {
877                        let page = page_opt.unwrap_or(1);
878                        let limit = limit_opt.unwrap_or(20);
879                        pagination_metadata = Some((total, page, limit));
880                        tracing::debug!(
881                            "Detected pagination metadata: total={}, page={}, limit={}",
882                            total,
883                            page,
884                            limit
885                        );
886                    } else {
887                        // Phase 3: If no total found in schema, try to infer from parent entity
888                        // Look for "items" array to determine child entity name
889                        if obj.properties.contains_key("items") {
890                            // Try to infer parent/child relationship from schema names
891                            // This is a heuristic: if we're generating a paginated response,
892                            // check if we can find a parent entity schema with a count field
893                            if let Some(inferred_total) =
894                                Self::try_infer_total_from_context(spec, obj)
895                            {
896                                let page = page_opt.unwrap_or(1);
897                                let limit = limit_opt.unwrap_or(20);
898                                pagination_metadata = Some((inferred_total, page, limit));
899                                tracing::debug!(
900                                    "Inferred pagination metadata from parent entity: total={}, page={}, limit={}",
901                                    inferred_total, page, limit
902                                );
903                            } else {
904                                // Phase 4: Try to use persona traits if available
905                                if let Some(persona) = persona {
906                                    // Look for count-related traits (e.g., "hive_count", "apiary_count")
907                                    // Try common patterns
908                                    let count_keys =
909                                        ["hive_count", "apiary_count", "item_count", "total_count"];
910                                    for key in &count_keys {
911                                        if let Some(count) = persona.get_numeric_trait(key) {
912                                            let page = page_opt.unwrap_or(1);
913                                            let limit = limit_opt.unwrap_or(20);
914                                            pagination_metadata = Some((count, page, limit));
915                                            tracing::debug!(
916                                                "Using persona trait '{}' for pagination: total={}, page={}, limit={}",
917                                                key, count, page, limit
918                                            );
919                                            break;
920                                        }
921                                    }
922                                }
923                            }
924                        }
925                    }
926                }
927
928                let mut map = serde_json::Map::new();
929                for (prop_name, prop_schema) in &obj.properties {
930                    let prop_lower = prop_name.to_lowercase();
931
932                    // Check if this is an array property that should use pagination metadata
933                    let is_items_array = prop_lower == "items" && pagination_metadata.is_some();
934
935                    let value = match prop_schema {
936                        ReferenceOr::Item(prop_schema) => {
937                            // If this is an items array with pagination metadata, always use generate_array_with_count
938                            // (it will use the example as a template if one exists)
939                            if is_items_array {
940                                // Generate array with count based on pagination metadata
941                                Self::generate_array_with_count(
942                                    spec,
943                                    prop_schema.as_ref(),
944                                    pagination_metadata.unwrap(),
945                                    persona,
946                                )
947                            } else if let Some(prop_example) =
948                                prop_schema.schema_data.example.as_ref()
949                            {
950                                // Check for property-level example (only if not items array)
951                                tracing::debug!(
952                                    "Using example for property '{}': {:?}",
953                                    prop_name,
954                                    prop_example
955                                );
956                                prop_example.clone()
957                            } else {
958                                Self::generate_example_from_schema(
959                                    spec,
960                                    prop_schema.as_ref(),
961                                    persona,
962                                )
963                            }
964                        }
965                        ReferenceOr::Reference { reference } => {
966                            // Try to resolve reference
967                            if let Some(resolved_schema) = spec.get_schema(reference) {
968                                // If this is an items array with pagination metadata, always use generate_array_with_count
969                                if is_items_array {
970                                    // Generate array with count based on pagination metadata
971                                    Self::generate_array_with_count(
972                                        spec,
973                                        &resolved_schema.schema,
974                                        pagination_metadata.unwrap(),
975                                        persona,
976                                    )
977                                } else if let Some(ref_example) =
978                                    resolved_schema.schema.schema_data.example.as_ref()
979                                {
980                                    // Check for example from referenced schema (only if not items array)
981                                    tracing::debug!(
982                                        "Using example from referenced schema '{}': {:?}",
983                                        reference,
984                                        ref_example
985                                    );
986                                    ref_example.clone()
987                                } else {
988                                    Self::generate_example_from_schema(
989                                        spec,
990                                        &resolved_schema.schema,
991                                        persona,
992                                    )
993                                }
994                            } else {
995                                Self::generate_example_for_property(prop_name)
996                            }
997                        }
998                    };
999                    let value = match value {
1000                        Value::Null => Self::generate_example_for_property(prop_name),
1001                        Value::Object(ref obj) if obj.is_empty() => {
1002                            Self::generate_example_for_property(prop_name)
1003                        }
1004                        _ => value,
1005                    };
1006                    map.insert(prop_name.clone(), value);
1007                }
1008
1009                // Ensure pagination metadata is set if we detected it
1010                if let Some((total, page, limit)) = pagination_metadata {
1011                    map.insert("total".to_string(), Value::Number(total.into()));
1012                    map.insert("page".to_string(), Value::Number(page.into()));
1013                    map.insert("limit".to_string(), Value::Number(limit.into()));
1014                }
1015
1016                Value::Object(map)
1017            }
1018            openapiv3::SchemaKind::Type(openapiv3::Type::Array(arr)) => {
1019                // Check for array-level example (schema.schema_data.example contains the full array)
1020                // Note: This check is actually redundant since we check at the top,
1021                // but keeping it here for clarity and defensive programming
1022                // If the array schema itself has an example, it's already handled at the top
1023
1024                match &arr.items {
1025                    Some(item_schema) => {
1026                        let example_item = match item_schema {
1027                            ReferenceOr::Item(item_schema) => {
1028                                // Recursively generate example for array item
1029                                // This will check for item-level examples
1030                                Self::generate_example_from_schema(
1031                                    spec,
1032                                    item_schema.as_ref(),
1033                                    persona,
1034                                )
1035                            }
1036                            ReferenceOr::Reference { reference } => {
1037                                // Try to resolve reference and generate example
1038                                // This will check for examples in referenced schema
1039                                if let Some(resolved_schema) = spec.get_schema(reference) {
1040                                    Self::generate_example_from_schema(
1041                                        spec,
1042                                        &resolved_schema.schema,
1043                                        persona,
1044                                    )
1045                                } else {
1046                                    Value::Object(serde_json::Map::new())
1047                                }
1048                            }
1049                        };
1050                        Value::Array(vec![example_item])
1051                    }
1052                    None => Value::Array(vec![Value::String("item".to_string())]),
1053                }
1054            }
1055            _ => Value::Object(serde_json::Map::new()),
1056        }
1057    }
1058
1059    /// Extract numeric value from a schema (from example or default)
1060    /// Returns None if no numeric value can be extracted
1061    fn extract_numeric_value_from_schema(schema_ref: &ReferenceOr<Schema>) -> Option<u64> {
1062        match schema_ref {
1063            ReferenceOr::Item(schema) => {
1064                // Check for example value first
1065                if let Some(example) = schema.schema_data.example.as_ref() {
1066                    if let Some(num) = example.as_u64() {
1067                        return Some(num);
1068                    } else if let Some(num) = example.as_f64() {
1069                        return Some(num as u64);
1070                    }
1071                }
1072                // Check for default value
1073                if let Some(default) = schema.schema_data.default.as_ref() {
1074                    if let Some(num) = default.as_u64() {
1075                        return Some(num);
1076                    } else if let Some(num) = default.as_f64() {
1077                        return Some(num as u64);
1078                    }
1079                }
1080                // For integer types, try to extract from schema constraints
1081                // Note: IntegerType doesn't have a default field in openapiv3
1082                // Defaults are stored in schema_data.default instead
1083                None
1084            }
1085            ReferenceOr::Reference { reference: _ } => {
1086                // For references, we'd need to resolve them, but for now return None
1087                // This can be enhanced later if needed
1088                None
1089            }
1090        }
1091    }
1092
1093    /// Generate an array with a specific count based on pagination metadata
1094    /// Respects the limit (e.g., if total=50 and limit=20, generates 20 items)
1095    fn generate_array_with_count(
1096        spec: &OpenApiSpec,
1097        array_schema: &Schema,
1098        pagination: (u64, u64, u64), // (total, page, limit)
1099        persona: Option<&Persona>,
1100    ) -> Value {
1101        let (total, _page, limit) = pagination;
1102
1103        // Determine how many items to generate
1104        // Respect pagination: generate min(total, limit) items
1105        let count = std::cmp::min(total, limit);
1106
1107        // Cap at reasonable maximum to avoid performance issues
1108        let max_items = 100;
1109        let count = std::cmp::min(count, max_items);
1110
1111        tracing::debug!("Generating array with count={} (total={}, limit={})", count, total, limit);
1112
1113        // Check if array schema has an example with items
1114        if let Some(example) = array_schema.schema_data.example.as_ref() {
1115            if let Some(example_array) = example.as_array() {
1116                if !example_array.is_empty() {
1117                    // Use first example item as template
1118                    let template_item = &example_array[0];
1119                    let items: Vec<Value> = (0..count)
1120                        .map(|i| {
1121                            // Clone template and add variation
1122                            let mut item = template_item.clone();
1123                            Self::add_item_variation(&mut item, i + 1);
1124                            item
1125                        })
1126                        .collect();
1127                    return Value::Array(items);
1128                }
1129            }
1130        }
1131
1132        // Generate items from schema
1133        if let openapiv3::SchemaKind::Type(openapiv3::Type::Array(arr)) = &array_schema.schema_kind
1134        {
1135            if let Some(item_schema) = &arr.items {
1136                let items: Vec<Value> = match item_schema {
1137                    ReferenceOr::Item(item_schema) => {
1138                        (0..count)
1139                            .map(|i| {
1140                                let mut item = Self::generate_example_from_schema(
1141                                    spec,
1142                                    item_schema.as_ref(),
1143                                    persona,
1144                                );
1145                                // Add variation to make items unique
1146                                Self::add_item_variation(&mut item, i + 1);
1147                                item
1148                            })
1149                            .collect()
1150                    }
1151                    ReferenceOr::Reference { reference } => {
1152                        if let Some(resolved_schema) = spec.get_schema(reference) {
1153                            (0..count)
1154                                .map(|i| {
1155                                    let mut item = Self::generate_example_from_schema(
1156                                        spec,
1157                                        &resolved_schema.schema,
1158                                        persona,
1159                                    );
1160                                    // Add variation to make items unique
1161                                    Self::add_item_variation(&mut item, i + 1);
1162                                    item
1163                                })
1164                                .collect()
1165                        } else {
1166                            vec![Value::Object(serde_json::Map::new()); count as usize]
1167                        }
1168                    }
1169                };
1170                return Value::Array(items);
1171            }
1172        }
1173
1174        // Fallback: generate simple items
1175        Value::Array((0..count).map(|i| Value::String(format!("item_{}", i + 1))).collect())
1176    }
1177
1178    /// Add variation to an item to make it unique (for array generation)
1179    /// Varies IDs, names, addresses, and coordinates based on item index
1180    fn add_item_variation(item: &mut Value, item_index: u64) {
1181        if let Some(obj) = item.as_object_mut() {
1182            // Update ID fields to be unique
1183            if let Some(id_val) = obj.get_mut("id") {
1184                if let Some(id_str) = id_val.as_str() {
1185                    // Extract base ID (remove any existing suffix)
1186                    let base_id = id_str.split('_').next().unwrap_or(id_str);
1187                    *id_val = Value::String(format!("{}_{:03}", base_id, item_index));
1188                } else if let Some(id_num) = id_val.as_u64() {
1189                    *id_val = Value::Number((id_num + item_index).into());
1190                }
1191            }
1192
1193            // Update name fields - add variation for all names
1194            if let Some(name_val) = obj.get_mut("name") {
1195                if let Some(name_str) = name_val.as_str() {
1196                    if name_str.contains('#') {
1197                        // Pattern like "Hive #1" -> "Hive #2"
1198                        *name_val = Value::String(format!("Hive #{}", item_index));
1199                    } else {
1200                        // Pattern like "Meadow Apiary" -> use rotation of varied names
1201                        // 60+ unique apiary names with geographic diversity for realistic demo
1202                        let apiary_names = [
1203                            // Midwest/Prairie names
1204                            "Meadow Apiary",
1205                            "Prairie Apiary",
1206                            "Sunset Valley Apiary",
1207                            "Golden Fields Apiary",
1208                            "Miller Family Apiary",
1209                            "Heartland Honey Co.",
1210                            "Cornfield Apiary",
1211                            "Harvest Moon Apiary",
1212                            "Prairie Winds Apiary",
1213                            "Amber Fields Apiary",
1214                            // California/Coastal names
1215                            "Coastal Apiary",
1216                            "Sunset Coast Apiary",
1217                            "Pacific Grove Apiary",
1218                            "Golden Gate Apiary",
1219                            "Napa Valley Apiary",
1220                            "Coastal Breeze Apiary",
1221                            "Pacific Heights Apiary",
1222                            "Bay Area Apiary",
1223                            "Sunset Valley Honey Co.",
1224                            "Coastal Harvest Apiary",
1225                            // Texas/Ranch names
1226                            "Lone Star Apiary",
1227                            "Texas Ranch Apiary",
1228                            "Big Sky Apiary",
1229                            "Prairie Rose Apiary",
1230                            "Hill Country Apiary",
1231                            "Lone Star Honey Co.",
1232                            "Texas Pride Apiary",
1233                            "Wildflower Ranch",
1234                            "Desert Bloom Apiary",
1235                            "Cactus Creek Apiary",
1236                            // Florida/Grove names
1237                            "Orange Grove Apiary",
1238                            "Citrus Grove Apiary",
1239                            "Palm Grove Apiary",
1240                            "Tropical Breeze Apiary",
1241                            "Everglades Apiary",
1242                            "Sunshine State Apiary",
1243                            "Florida Keys Apiary",
1244                            "Grove View Apiary",
1245                            "Tropical Harvest Apiary",
1246                            "Palm Coast Apiary",
1247                            // Northeast/Valley names
1248                            "Mountain View Apiary",
1249                            "Valley Apiary",
1250                            "Riverside Apiary",
1251                            "Hilltop Apiary",
1252                            "Forest Apiary",
1253                            "Mountain Apiary",
1254                            "Lakeside Apiary",
1255                            "Ridge Apiary",
1256                            "Brook Apiary",
1257                            "Hillside Apiary",
1258                            // Generic/Professional names
1259                            "Field Apiary",
1260                            "Creek Apiary",
1261                            "Woodland Apiary",
1262                            "Farm Apiary",
1263                            "Orchard Apiary",
1264                            "Pasture Apiary",
1265                            "Green Valley Apiary",
1266                            "Blue Sky Apiary",
1267                            "Sweet Honey Apiary",
1268                            "Nature's Best Apiary",
1269                            // Business/Commercial names
1270                            "Premium Honey Co.",
1271                            "Artisan Apiary",
1272                            "Heritage Apiary",
1273                            "Summit Apiary",
1274                            "Crystal Springs Apiary",
1275                            "Maple Grove Apiary",
1276                            "Wildflower Apiary",
1277                            "Thistle Apiary",
1278                            "Clover Field Apiary",
1279                            "Honeycomb Apiary",
1280                        ];
1281                        let name_index = (item_index - 1) as usize % apiary_names.len();
1282                        *name_val = Value::String(apiary_names[name_index].to_string());
1283                    }
1284                }
1285            }
1286
1287            // Update location/address fields
1288            if let Some(location_val) = obj.get_mut("location") {
1289                if let Some(location_obj) = location_val.as_object_mut() {
1290                    // Update address
1291                    if let Some(address_val) = location_obj.get_mut("address") {
1292                        if let Some(address_str) = address_val.as_str() {
1293                            // Extract street number if present, otherwise add variation
1294                            if let Some(num_str) = address_str.split_whitespace().next() {
1295                                if let Ok(num) = num_str.parse::<u64>() {
1296                                    *address_val =
1297                                        Value::String(format!("{} Farm Road", num + item_index));
1298                                } else {
1299                                    *address_val =
1300                                        Value::String(format!("{} Farm Road", 100 + item_index));
1301                                }
1302                            } else {
1303                                *address_val =
1304                                    Value::String(format!("{} Farm Road", 100 + item_index));
1305                            }
1306                        }
1307                    }
1308
1309                    // Vary coordinates slightly
1310                    if let Some(lat_val) = location_obj.get_mut("latitude") {
1311                        if let Some(lat) = lat_val.as_f64() {
1312                            *lat_val = Value::Number(
1313                                serde_json::Number::from_f64(lat + (item_index as f64 * 0.01))
1314                                    .expect("latitude arithmetic produces valid f64"),
1315                            );
1316                        }
1317                    }
1318                    if let Some(lng_val) = location_obj.get_mut("longitude") {
1319                        if let Some(lng) = lng_val.as_f64() {
1320                            *lng_val = Value::Number(
1321                                serde_json::Number::from_f64(lng + (item_index as f64 * 0.01))
1322                                    .expect("longitude arithmetic produces valid f64"),
1323                            );
1324                        }
1325                    }
1326                } else if let Some(address_str) = location_val.as_str() {
1327                    // Flat address string
1328                    if let Some(num_str) = address_str.split_whitespace().next() {
1329                        if let Ok(num) = num_str.parse::<u64>() {
1330                            *location_val =
1331                                Value::String(format!("{} Farm Road", num + item_index));
1332                        } else {
1333                            *location_val =
1334                                Value::String(format!("{} Farm Road", 100 + item_index));
1335                        }
1336                    }
1337                }
1338            }
1339
1340            // Update address field if it exists at root level
1341            if let Some(address_val) = obj.get_mut("address") {
1342                if let Some(address_str) = address_val.as_str() {
1343                    if let Some(num_str) = address_str.split_whitespace().next() {
1344                        if let Ok(num) = num_str.parse::<u64>() {
1345                            *address_val = Value::String(format!("{} Farm Road", num + item_index));
1346                        } else {
1347                            *address_val = Value::String(format!("{} Farm Road", 100 + item_index));
1348                        }
1349                    }
1350                }
1351            }
1352
1353            // Vary status fields (common enum values)
1354            if let Some(status_val) = obj.get_mut("status") {
1355                if let Some(status_str) = status_val.as_str() {
1356                    let statuses = [
1357                        "healthy",
1358                        "sick",
1359                        "needs_attention",
1360                        "quarantined",
1361                        "active",
1362                        "inactive",
1363                    ];
1364                    let status_index = (item_index - 1) as usize % statuses.len();
1365                    // Bias towards "healthy" and "active" (70% of items)
1366                    let final_status = if (item_index - 1) % 10 < 7 {
1367                        statuses[0] // "healthy" or "active"
1368                    } else {
1369                        statuses[status_index]
1370                    };
1371                    *status_val = Value::String(final_status.to_string());
1372                }
1373            }
1374
1375            // Vary hive_type fields
1376            if let Some(hive_type_val) = obj.get_mut("hive_type") {
1377                if hive_type_val.as_str().is_some() {
1378                    let hive_types = ["langstroth", "top_bar", "warre", "flow_hive", "national"];
1379                    let type_index = (item_index - 1) as usize % hive_types.len();
1380                    *hive_type_val = Value::String(hive_types[type_index].to_string());
1381                }
1382            }
1383
1384            // Vary nested queen breed fields
1385            if let Some(queen_val) = obj.get_mut("queen") {
1386                if let Some(queen_obj) = queen_val.as_object_mut() {
1387                    if let Some(breed_val) = queen_obj.get_mut("breed") {
1388                        if breed_val.as_str().is_some() {
1389                            let breeds =
1390                                ["italian", "carniolan", "russian", "buckfast", "caucasian"];
1391                            let breed_index = (item_index - 1) as usize % breeds.len();
1392                            *breed_val = Value::String(breeds[breed_index].to_string());
1393                        }
1394                    }
1395                    // Vary queen age
1396                    if let Some(age_val) = queen_obj.get_mut("age_days") {
1397                        if let Some(base_age) = age_val.as_u64() {
1398                            *age_val = Value::Number((base_age + (item_index * 10) % 200).into());
1399                        } else if let Some(base_age) = age_val.as_i64() {
1400                            *age_val =
1401                                Value::Number((base_age + (item_index as i64 * 10) % 200).into());
1402                        }
1403                    }
1404                    // Vary queen mark color
1405                    if let Some(color_val) = queen_obj.get_mut("mark_color") {
1406                        if color_val.as_str().is_some() {
1407                            let colors = ["yellow", "white", "red", "green", "blue"];
1408                            let color_index = (item_index - 1) as usize % colors.len();
1409                            *color_val = Value::String(colors[color_index].to_string());
1410                        }
1411                    }
1412                }
1413            }
1414
1415            // Vary description fields if they exist
1416            if let Some(desc_val) = obj.get_mut("description") {
1417                if let Some(desc_str) = desc_val.as_str() {
1418                    let descriptions = [
1419                        "Production apiary",
1420                        "Research apiary",
1421                        "Commercial operation",
1422                        "Backyard apiary",
1423                        "Educational apiary",
1424                    ];
1425                    let desc_index = (item_index - 1) as usize % descriptions.len();
1426                    *desc_val = Value::String(descriptions[desc_index].to_string());
1427                }
1428            }
1429
1430            // Vary timestamp fields (created_at, updated_at, timestamp, date) for realistic time-series data
1431            // Generate timestamps spanning 12-24 months with proper distribution
1432            let timestamp_fields = [
1433                "created_at",
1434                "updated_at",
1435                "timestamp",
1436                "date",
1437                "forecastDate",
1438                "predictedDate",
1439            ];
1440            for field_name in &timestamp_fields {
1441                if let Some(timestamp_val) = obj.get_mut(*field_name) {
1442                    if let Some(_timestamp_str) = timestamp_val.as_str() {
1443                        // Generate realistic timestamp: distribute items over past 12-18 months
1444                        // Use item_index to create variation (not all same date)
1445                        let months_ago = 12 + ((item_index - 1) % 6); // Distribute over 6 months (12-18 months ago)
1446                        let days_offset = (item_index - 1) % 28; // Distribute within month (cap at 28)
1447                        let hours_offset = ((item_index * 7) % 24) as u8; // Distribute throughout day
1448                        let minutes_offset = ((item_index * 11) % 60) as u8; // Vary minutes
1449
1450                        // Calculate timestamp relative to current date (November 2024)
1451                        // Format: ISO 8601 (e.g., "2024-11-12T14:30:00Z")
1452                        let base_year = 2024;
1453                        let base_month = 11;
1454
1455                        // Calculate target month (going back in time)
1456                        let target_year = if months_ago >= base_month as u64 {
1457                            base_year - 1
1458                        } else {
1459                            base_year
1460                        };
1461                        let target_month = if months_ago >= base_month as u64 {
1462                            12 - (months_ago - base_month as u64) as u8
1463                        } else {
1464                            (base_month as u64 - months_ago) as u8
1465                        };
1466                        let target_day = std::cmp::min(28, 1 + days_offset as u8); // Start from day 1, cap at 28
1467
1468                        // Format as ISO 8601
1469                        let timestamp = format!(
1470                            "{:04}-{:02}-{:02}T{:02}:{:02}:00Z",
1471                            target_year, target_month, target_day, hours_offset, minutes_offset
1472                        );
1473                        *timestamp_val = Value::String(timestamp);
1474                    }
1475                }
1476            }
1477        }
1478    }
1479
1480    /// Try to infer total count from context (parent entity schemas)
1481    /// This is a heuristic that looks for common relationship patterns
1482    fn try_infer_total_from_context(
1483        spec: &OpenApiSpec,
1484        obj_type: &openapiv3::ObjectType,
1485    ) -> Option<u64> {
1486        // Look for "items" array to determine what we're generating
1487        if let Some(items_schema_ref) = obj_type.properties.get("items") {
1488            // Try to determine child entity name from items schema
1489            // This is a heuristic: check schema names in the spec
1490            if let Some(components) = &spec.spec.components {
1491                let schemas = &components.schemas;
1492                // Look through all schemas to find potential parent entities
1493                // that might have count fields matching the items type
1494                for (schema_name, schema_ref) in schemas {
1495                    if let ReferenceOr::Item(schema) = schema_ref {
1496                        if let openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) =
1497                            &schema.schema_kind
1498                        {
1499                            // Look for count fields that might match
1500                            for (prop_name, prop_schema) in &obj.properties {
1501                                let prop_lower = prop_name.to_lowercase();
1502                                if prop_lower.ends_with("_count") {
1503                                    // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema>
1504                                    let schema_ref: ReferenceOr<Schema> = match prop_schema {
1505                                        ReferenceOr::Item(boxed) => {
1506                                            ReferenceOr::Item(boxed.as_ref().clone())
1507                                        }
1508                                        ReferenceOr::Reference { reference } => {
1509                                            ReferenceOr::Reference {
1510                                                reference: reference.clone(),
1511                                            }
1512                                        }
1513                                    };
1514                                    // Found a count field, try to extract its value
1515                                    if let Some(count) =
1516                                        Self::extract_numeric_value_from_schema(&schema_ref)
1517                                    {
1518                                        // Use a reasonable default if count is very large
1519                                        if count > 0 && count <= 1000 {
1520                                            tracing::debug!(
1521                                                "Inferred count {} from parent schema {} field {}",
1522                                                count,
1523                                                schema_name,
1524                                                prop_name
1525                                            );
1526                                            return Some(count);
1527                                        }
1528                                    }
1529                                }
1530                            }
1531                        }
1532                    }
1533                }
1534            }
1535        }
1536
1537        None
1538    }
1539
1540    /// Infer relationship count from parent entity schema
1541    /// When generating a child entity list, check if parent entity has a count field
1542    fn infer_count_from_parent_schema(
1543        spec: &OpenApiSpec,
1544        parent_entity_name: &str,
1545        child_entity_name: &str,
1546    ) -> Option<u64> {
1547        // Look for parent entity schema
1548        let parent_schema_name = parent_entity_name.to_string();
1549        let count_field_name = format!("{}_count", child_entity_name);
1550
1551        // Try to find the schema
1552        if let Some(components) = &spec.spec.components {
1553            let schemas = &components.schemas;
1554            // Look for parent schema (case-insensitive)
1555            for (schema_name, schema_ref) in schemas {
1556                let schema_name_lower = schema_name.to_lowercase();
1557                if schema_name_lower.contains(&parent_entity_name.to_lowercase()) {
1558                    if let ReferenceOr::Item(schema) = schema_ref {
1559                        // Check if this schema has the count field
1560                        if let openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) =
1561                            &schema.schema_kind
1562                        {
1563                            for (prop_name, prop_schema) in &obj.properties {
1564                                if prop_name.to_lowercase() == count_field_name.to_lowercase() {
1565                                    // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema>
1566                                    let schema_ref: ReferenceOr<Schema> = match prop_schema {
1567                                        ReferenceOr::Item(boxed) => {
1568                                            ReferenceOr::Item(boxed.as_ref().clone())
1569                                        }
1570                                        ReferenceOr::Reference { reference } => {
1571                                            ReferenceOr::Reference {
1572                                                reference: reference.clone(),
1573                                            }
1574                                        }
1575                                    };
1576                                    // Extract count value from schema
1577                                    return Self::extract_numeric_value_from_schema(&schema_ref);
1578                                }
1579                            }
1580                        }
1581                    }
1582                }
1583            }
1584        }
1585
1586        None
1587    }
1588
1589    /// Generate example value for a property based on its name
1590    fn generate_example_for_property(prop_name: &str) -> Value {
1591        let prop_lower = prop_name.to_lowercase();
1592
1593        // Generate realistic data based on property name patterns
1594        if prop_lower.contains("id") || prop_lower.contains("uuid") {
1595            Value::String(uuid::Uuid::new_v4().to_string())
1596        } else if prop_lower.contains("email") {
1597            Value::String(format!("user{}@example.com", thread_rng().random_range(1000..=9999)))
1598        } else if prop_lower.contains("name") || prop_lower.contains("title") {
1599            let names = ["John Doe", "Jane Smith", "Bob Johnson", "Alice Brown"];
1600            Value::String(names[thread_rng().random_range(0..names.len())].to_string())
1601        } else if prop_lower.contains("phone") || prop_lower.contains("mobile") {
1602            Value::String(format!("+1-555-{:04}", thread_rng().random_range(1000..=9999)))
1603        } else if prop_lower.contains("address") || prop_lower.contains("street") {
1604            let streets = ["123 Main St", "456 Oak Ave", "789 Pine Rd", "321 Elm St"];
1605            Value::String(streets[thread_rng().random_range(0..streets.len())].to_string())
1606        } else if prop_lower.contains("city") {
1607            let cities = ["New York", "London", "Tokyo", "Paris", "Sydney"];
1608            Value::String(cities[thread_rng().random_range(0..cities.len())].to_string())
1609        } else if prop_lower.contains("country") {
1610            let countries = ["USA", "UK", "Japan", "France", "Australia"];
1611            Value::String(countries[thread_rng().random_range(0..countries.len())].to_string())
1612        } else if prop_lower.contains("company") || prop_lower.contains("organization") {
1613            let companies = ["Acme Corp", "Tech Solutions", "Global Inc", "Innovate Ltd"];
1614            Value::String(companies[thread_rng().random_range(0..companies.len())].to_string())
1615        } else if prop_lower.contains("url") || prop_lower.contains("website") {
1616            Value::String("https://example.com".to_string())
1617        } else if prop_lower.contains("age") {
1618            Value::Number((18 + thread_rng().random_range(0..60)).into())
1619        } else if prop_lower.contains("count") || prop_lower.contains("quantity") {
1620            Value::Number((1 + thread_rng().random_range(0..100)).into())
1621        } else if prop_lower.contains("price")
1622            || prop_lower.contains("amount")
1623            || prop_lower.contains("cost")
1624        {
1625            Value::Number(
1626                serde_json::Number::from_f64(
1627                    (thread_rng().random::<f64>() * 1000.0 * 100.0).round() / 100.0,
1628                )
1629                .expect("rounded price calculation produces valid f64"),
1630            )
1631        } else if prop_lower.contains("active")
1632            || prop_lower.contains("enabled")
1633            || prop_lower.contains("is_")
1634        {
1635            Value::Bool(thread_rng().random_bool(0.5))
1636        } else if prop_lower.contains("date") || prop_lower.contains("time") {
1637            Value::String(chrono::Utc::now().to_rfc3339())
1638        } else if prop_lower.contains("description") || prop_lower.contains("comment") {
1639            Value::String("This is a sample description text.".to_string())
1640        } else {
1641            Value::String(format!("example {}", prop_name))
1642        }
1643    }
1644
1645    /// Generate example responses from OpenAPI examples
1646    pub fn generate_from_examples(
1647        response: &Response,
1648        content_type: Option<&str>,
1649    ) -> Result<Option<Value>> {
1650        use openapiv3::ReferenceOr;
1651
1652        // If content_type is specified, look for examples in that media type
1653        if let Some(content_type) = content_type {
1654            if let Some(media_type) = response.content.get(content_type) {
1655                // Check for single example first
1656                if let Some(example) = &media_type.example {
1657                    return Ok(Some(example.clone()));
1658                }
1659
1660                // Check for multiple examples
1661                for (_, example_ref) in &media_type.examples {
1662                    if let ReferenceOr::Item(example) = example_ref {
1663                        if let Some(value) = &example.value {
1664                            return Ok(Some(value.clone()));
1665                        }
1666                    }
1667                    // Reference resolution would require spec parameter to be added to this function
1668                }
1669            }
1670        }
1671
1672        // If no content_type specified or not found, check all media types
1673        for (_, media_type) in &response.content {
1674            // Check for single example first
1675            if let Some(example) = &media_type.example {
1676                return Ok(Some(example.clone()));
1677            }
1678
1679            // Check for multiple examples
1680            for (_, example_ref) in &media_type.examples {
1681                if let ReferenceOr::Item(example) = example_ref {
1682                    if let Some(value) = &example.value {
1683                        return Ok(Some(value.clone()));
1684                    }
1685                }
1686                // Reference resolution would require spec parameter to be added to this function
1687            }
1688        }
1689
1690        Ok(None)
1691    }
1692
1693    /// Expand templates like {{now}} and {{uuid}} in JSON values
1694    fn expand_templates(value: &Value) -> Value {
1695        match value {
1696            Value::String(s) => {
1697                let expanded = s
1698                    .replace("{{now}}", &chrono::Utc::now().to_rfc3339())
1699                    .replace("{{uuid}}", &uuid::Uuid::new_v4().to_string());
1700                Value::String(expanded)
1701            }
1702            Value::Object(map) => {
1703                let mut new_map = serde_json::Map::new();
1704                for (key, val) in map {
1705                    new_map.insert(key.clone(), Self::expand_templates(val));
1706                }
1707                Value::Object(new_map)
1708            }
1709            Value::Array(arr) => {
1710                let new_arr: Vec<Value> = arr.iter().map(Self::expand_templates).collect();
1711                Value::Array(new_arr)
1712            }
1713            _ => value.clone(),
1714        }
1715    }
1716}
1717
1718#[cfg(test)]
1719mod tests {
1720    use super::*;
1721    use openapiv3::ReferenceOr;
1722    use serde_json::json;
1723
1724    // Mock AI generator for testing
1725    struct MockAiGenerator {
1726        response: Value,
1727    }
1728
1729    #[async_trait]
1730    impl AiGenerator for MockAiGenerator {
1731        async fn generate(&self, _prompt: &str, _config: &AiResponseConfig) -> Result<Value> {
1732            Ok(self.response.clone())
1733        }
1734    }
1735
1736    #[test]
1737    fn generates_example_using_referenced_schemas() {
1738        let yaml = r#"
1739openapi: 3.0.3
1740info:
1741  title: Test API
1742  version: "1.0.0"
1743paths:
1744  /apiaries:
1745    get:
1746      responses:
1747        '200':
1748          description: ok
1749          content:
1750            application/json:
1751              schema:
1752                $ref: '#/components/schemas/Apiary'
1753components:
1754  schemas:
1755    Apiary:
1756      type: object
1757      properties:
1758        id:
1759          type: string
1760        hive:
1761          $ref: '#/components/schemas/Hive'
1762    Hive:
1763      type: object
1764      properties:
1765        name:
1766          type: string
1767        active:
1768          type: boolean
1769        "#;
1770
1771        let spec = OpenApiSpec::from_string(yaml, Some("yaml")).expect("load spec");
1772        let path_item = spec
1773            .spec
1774            .paths
1775            .paths
1776            .get("/apiaries")
1777            .and_then(ReferenceOr::as_item)
1778            .expect("path item");
1779        let operation = path_item.get.as_ref().expect("GET operation");
1780
1781        let response =
1782            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1783                .expect("generate response");
1784
1785        let obj = response.as_object().expect("response object");
1786        assert!(obj.contains_key("id"));
1787        let hive = obj.get("hive").and_then(|value| value.as_object()).expect("hive object");
1788        assert!(hive.contains_key("name"));
1789        assert!(hive.contains_key("active"));
1790    }
1791
1792    #[tokio::test]
1793    async fn test_generate_ai_response_with_generator() {
1794        let ai_config = AiResponseConfig {
1795            enabled: true,
1796            mode: crate::ai_response::AiResponseMode::Intelligent,
1797            prompt: Some("Generate a response for {{method}} {{path}}".to_string()),
1798            context: None,
1799            temperature: 0.7,
1800            max_tokens: 1000,
1801            schema: None,
1802            cache_enabled: true,
1803        };
1804        let context = RequestContext {
1805            method: "GET".to_string(),
1806            path: "/api/users".to_string(),
1807            path_params: HashMap::new(),
1808            query_params: HashMap::new(),
1809            headers: HashMap::new(),
1810            body: None,
1811            multipart_fields: HashMap::new(),
1812            multipart_files: HashMap::new(),
1813        };
1814        let mock_generator = MockAiGenerator {
1815            response: json!({"message": "Generated response"}),
1816        };
1817
1818        let result =
1819            ResponseGenerator::generate_ai_response(&ai_config, &context, Some(&mock_generator))
1820                .await;
1821
1822        assert!(result.is_ok());
1823        let value = result.unwrap();
1824        assert_eq!(value["message"], "Generated response");
1825    }
1826
1827    #[tokio::test]
1828    async fn test_generate_ai_response_without_generator() {
1829        let ai_config = AiResponseConfig {
1830            enabled: true,
1831            mode: crate::ai_response::AiResponseMode::Intelligent,
1832            prompt: Some("Generate a response for {{method}} {{path}}".to_string()),
1833            context: None,
1834            temperature: 0.7,
1835            max_tokens: 1000,
1836            schema: None,
1837            cache_enabled: true,
1838        };
1839        let context = RequestContext {
1840            method: "POST".to_string(),
1841            path: "/api/users".to_string(),
1842            path_params: HashMap::new(),
1843            query_params: HashMap::new(),
1844            headers: HashMap::new(),
1845            body: None,
1846            multipart_fields: HashMap::new(),
1847            multipart_files: HashMap::new(),
1848        };
1849
1850        let result = ResponseGenerator::generate_ai_response(&ai_config, &context, None).await;
1851
1852        assert!(result.is_ok());
1853        let value = result.unwrap();
1854        assert_eq!(value["ai_response"], "AI generation placeholder");
1855        assert!(value["expanded_prompt"].as_str().unwrap().contains("POST"));
1856        assert!(value["expanded_prompt"].as_str().unwrap().contains("/api/users"));
1857    }
1858
1859    #[tokio::test]
1860    async fn test_generate_ai_response_no_prompt() {
1861        let ai_config = AiResponseConfig {
1862            enabled: true,
1863            mode: crate::ai_response::AiResponseMode::Intelligent,
1864            prompt: None,
1865            context: None,
1866            temperature: 0.7,
1867            max_tokens: 1000,
1868            schema: None,
1869            cache_enabled: true,
1870        };
1871        let context = RequestContext {
1872            method: "GET".to_string(),
1873            path: "/api/test".to_string(),
1874            path_params: HashMap::new(),
1875            query_params: HashMap::new(),
1876            headers: HashMap::new(),
1877            body: None,
1878            multipart_fields: HashMap::new(),
1879            multipart_files: HashMap::new(),
1880        };
1881
1882        let result = ResponseGenerator::generate_ai_response(&ai_config, &context, None).await;
1883
1884        assert!(result.is_err());
1885    }
1886
1887    #[test]
1888    fn test_generate_response_with_expansion() {
1889        let spec = OpenApiSpec::from_string(
1890            r#"openapi: 3.0.0
1891info:
1892  title: Test API
1893  version: 1.0.0
1894paths:
1895  /users:
1896    get:
1897      responses:
1898        '200':
1899          description: OK
1900          content:
1901            application/json:
1902              schema:
1903                type: object
1904                properties:
1905                  id:
1906                    type: integer
1907                  name:
1908                    type: string
1909"#,
1910            Some("yaml"),
1911        )
1912        .unwrap();
1913
1914        let operation = spec
1915            .spec
1916            .paths
1917            .paths
1918            .get("/users")
1919            .and_then(|p| p.as_item())
1920            .and_then(|p| p.get.as_ref())
1921            .unwrap();
1922
1923        let response = ResponseGenerator::generate_response_with_expansion(
1924            &spec,
1925            operation,
1926            200,
1927            Some("application/json"),
1928            true,
1929        )
1930        .unwrap();
1931
1932        assert!(response.is_object());
1933    }
1934
1935    #[test]
1936    fn test_generate_response_with_scenario() {
1937        let spec = OpenApiSpec::from_string(
1938            r#"openapi: 3.0.0
1939info:
1940  title: Test API
1941  version: 1.0.0
1942paths:
1943  /users:
1944    get:
1945      responses:
1946        '200':
1947          description: OK
1948          content:
1949            application/json:
1950              examples:
1951                happy:
1952                  value:
1953                    id: 1
1954                    name: "Happy User"
1955                sad:
1956                  value:
1957                    id: 2
1958                    name: "Sad User"
1959"#,
1960            Some("yaml"),
1961        )
1962        .unwrap();
1963
1964        let operation = spec
1965            .spec
1966            .paths
1967            .paths
1968            .get("/users")
1969            .and_then(|p| p.as_item())
1970            .and_then(|p| p.get.as_ref())
1971            .unwrap();
1972
1973        let response = ResponseGenerator::generate_response_with_scenario(
1974            &spec,
1975            operation,
1976            200,
1977            Some("application/json"),
1978            false,
1979            Some("happy"),
1980        )
1981        .unwrap();
1982
1983        assert_eq!(response["id"], 1);
1984        assert_eq!(response["name"], "Happy User");
1985    }
1986
1987    #[test]
1988    fn test_generate_response_with_referenced_response() {
1989        let spec = OpenApiSpec::from_string(
1990            r#"openapi: 3.0.0
1991info:
1992  title: Test API
1993  version: 1.0.0
1994paths:
1995  /users:
1996    get:
1997      responses:
1998        '200':
1999          $ref: '#/components/responses/UserResponse'
2000components:
2001  responses:
2002    UserResponse:
2003      description: User response
2004      content:
2005        application/json:
2006          schema:
2007            type: object
2008            properties:
2009              id:
2010                type: integer
2011              name:
2012                type: string
2013"#,
2014            Some("yaml"),
2015        )
2016        .unwrap();
2017
2018        let operation = spec
2019            .spec
2020            .paths
2021            .paths
2022            .get("/users")
2023            .and_then(|p| p.as_item())
2024            .and_then(|p| p.get.as_ref())
2025            .unwrap();
2026
2027        let response =
2028            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2029                .unwrap();
2030
2031        assert!(response.is_object());
2032    }
2033
2034    #[test]
2035    fn test_generate_response_with_default_status() {
2036        let spec = OpenApiSpec::from_string(
2037            r#"openapi: 3.0.0
2038info:
2039  title: Test API
2040  version: 1.0.0
2041paths:
2042  /users:
2043    get:
2044      responses:
2045        '200':
2046          description: OK
2047        default:
2048          description: Error
2049          content:
2050            application/json:
2051              schema:
2052                type: object
2053                properties:
2054                  error:
2055                    type: string
2056"#,
2057            Some("yaml"),
2058        )
2059        .unwrap();
2060
2061        let operation = spec
2062            .spec
2063            .paths
2064            .paths
2065            .get("/users")
2066            .and_then(|p| p.as_item())
2067            .and_then(|p| p.get.as_ref())
2068            .unwrap();
2069
2070        // Use default response for 500 status
2071        let response =
2072            ResponseGenerator::generate_response(&spec, operation, 500, Some("application/json"))
2073                .unwrap();
2074
2075        assert!(response.is_object());
2076    }
2077
2078    #[test]
2079    fn test_generate_response_with_example_in_media_type() {
2080        let spec = OpenApiSpec::from_string(
2081            r#"openapi: 3.0.0
2082info:
2083  title: Test API
2084  version: 1.0.0
2085paths:
2086  /users:
2087    get:
2088      responses:
2089        '200':
2090          description: OK
2091          content:
2092            application/json:
2093              example:
2094                id: 1
2095                name: "Example User"
2096"#,
2097            Some("yaml"),
2098        )
2099        .unwrap();
2100
2101        let operation = spec
2102            .spec
2103            .paths
2104            .paths
2105            .get("/users")
2106            .and_then(|p| p.as_item())
2107            .and_then(|p| p.get.as_ref())
2108            .unwrap();
2109
2110        let response =
2111            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2112                .unwrap();
2113
2114        assert_eq!(response["id"], 1);
2115        assert_eq!(response["name"], "Example User");
2116    }
2117
2118    #[test]
2119    fn test_generate_response_with_schema_example() {
2120        let spec = OpenApiSpec::from_string(
2121            r#"openapi: 3.0.0
2122info:
2123  title: Test API
2124  version: 1.0.0
2125paths:
2126  /users:
2127    get:
2128      responses:
2129        '200':
2130          description: OK
2131          content:
2132            application/json:
2133              schema:
2134                type: object
2135                example:
2136                  id: 42
2137                  name: "Schema Example"
2138                properties:
2139                  id:
2140                    type: integer
2141                  name:
2142                    type: string
2143"#,
2144            Some("yaml"),
2145        )
2146        .unwrap();
2147
2148        let operation = spec
2149            .spec
2150            .paths
2151            .paths
2152            .get("/users")
2153            .and_then(|p| p.as_item())
2154            .and_then(|p| p.get.as_ref())
2155            .unwrap();
2156
2157        let response =
2158            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2159                .unwrap();
2160
2161        // Should use schema example if available
2162        assert!(response.is_object());
2163    }
2164
2165    #[test]
2166    fn test_generate_response_with_referenced_schema() {
2167        let spec = OpenApiSpec::from_string(
2168            r#"openapi: 3.0.0
2169info:
2170  title: Test API
2171  version: 1.0.0
2172paths:
2173  /users:
2174    get:
2175      responses:
2176        '200':
2177          description: OK
2178          content:
2179            application/json:
2180              schema:
2181                $ref: '#/components/schemas/User'
2182components:
2183  schemas:
2184    User:
2185      type: object
2186      properties:
2187        id:
2188          type: integer
2189        name:
2190          type: string
2191"#,
2192            Some("yaml"),
2193        )
2194        .unwrap();
2195
2196        let operation = spec
2197            .spec
2198            .paths
2199            .paths
2200            .get("/users")
2201            .and_then(|p| p.as_item())
2202            .and_then(|p| p.get.as_ref())
2203            .unwrap();
2204
2205        let response =
2206            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2207                .unwrap();
2208
2209        assert!(response.is_object());
2210        assert!(response.get("id").is_some());
2211        assert!(response.get("name").is_some());
2212    }
2213
2214    #[test]
2215    fn test_generate_response_with_array_schema() {
2216        let spec = OpenApiSpec::from_string(
2217            r#"openapi: 3.0.0
2218info:
2219  title: Test API
2220  version: 1.0.0
2221paths:
2222  /users:
2223    get:
2224      responses:
2225        '200':
2226          description: OK
2227          content:
2228            application/json:
2229              schema:
2230                type: array
2231                items:
2232                  type: object
2233                  properties:
2234                    id:
2235                      type: integer
2236                    name:
2237                      type: string
2238"#,
2239            Some("yaml"),
2240        )
2241        .unwrap();
2242
2243        let operation = spec
2244            .spec
2245            .paths
2246            .paths
2247            .get("/users")
2248            .and_then(|p| p.as_item())
2249            .and_then(|p| p.get.as_ref())
2250            .unwrap();
2251
2252        let response =
2253            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2254                .unwrap();
2255
2256        assert!(response.is_array());
2257    }
2258
2259    #[test]
2260    fn test_generate_response_with_different_content_types() {
2261        let spec = OpenApiSpec::from_string(
2262            r#"openapi: 3.0.0
2263info:
2264  title: Test API
2265  version: 1.0.0
2266paths:
2267  /users:
2268    get:
2269      responses:
2270        '200':
2271          description: OK
2272          content:
2273            application/json:
2274              schema:
2275                type: object
2276            text/plain:
2277              schema:
2278                type: string
2279"#,
2280            Some("yaml"),
2281        )
2282        .unwrap();
2283
2284        let operation = spec
2285            .spec
2286            .paths
2287            .paths
2288            .get("/users")
2289            .and_then(|p| p.as_item())
2290            .and_then(|p| p.get.as_ref())
2291            .unwrap();
2292
2293        // Test JSON content type
2294        let json_response =
2295            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2296                .unwrap();
2297        assert!(json_response.is_object());
2298
2299        // Test text/plain content type
2300        let text_response =
2301            ResponseGenerator::generate_response(&spec, operation, 200, Some("text/plain"))
2302                .unwrap();
2303        assert!(text_response.is_string());
2304    }
2305
2306    #[test]
2307    fn test_generate_response_without_content_type() {
2308        let spec = OpenApiSpec::from_string(
2309            r#"openapi: 3.0.0
2310info:
2311  title: Test API
2312  version: 1.0.0
2313paths:
2314  /users:
2315    get:
2316      responses:
2317        '200':
2318          description: OK
2319          content:
2320            application/json:
2321              schema:
2322                type: object
2323                properties:
2324                  id:
2325                    type: integer
2326"#,
2327            Some("yaml"),
2328        )
2329        .unwrap();
2330
2331        let operation = spec
2332            .spec
2333            .paths
2334            .paths
2335            .get("/users")
2336            .and_then(|p| p.as_item())
2337            .and_then(|p| p.get.as_ref())
2338            .unwrap();
2339
2340        // No content type specified - should use first available
2341        let response = ResponseGenerator::generate_response(&spec, operation, 200, None).unwrap();
2342
2343        assert!(response.is_object());
2344    }
2345
2346    #[test]
2347    fn test_generate_response_with_no_content() {
2348        let spec = OpenApiSpec::from_string(
2349            r#"openapi: 3.0.0
2350info:
2351  title: Test API
2352  version: 1.0.0
2353paths:
2354  /users:
2355    delete:
2356      responses:
2357        '204':
2358          description: No Content
2359"#,
2360            Some("yaml"),
2361        )
2362        .unwrap();
2363
2364        let operation = spec
2365            .spec
2366            .paths
2367            .paths
2368            .get("/users")
2369            .and_then(|p| p.as_item())
2370            .and_then(|p| p.delete.as_ref())
2371            .unwrap();
2372
2373        let response = ResponseGenerator::generate_response(&spec, operation, 204, None).unwrap();
2374
2375        // Should return empty object for no content
2376        assert!(response.is_object());
2377        assert!(response.as_object().unwrap().is_empty());
2378    }
2379
2380    #[test]
2381    fn test_generate_response_with_expansion_disabled() {
2382        let spec = OpenApiSpec::from_string(
2383            r#"openapi: 3.0.0
2384info:
2385  title: Test API
2386  version: 1.0.0
2387paths:
2388  /users:
2389    get:
2390      responses:
2391        '200':
2392          description: OK
2393          content:
2394            application/json:
2395              schema:
2396                type: object
2397                properties:
2398                  id:
2399                    type: integer
2400                  name:
2401                    type: string
2402"#,
2403            Some("yaml"),
2404        )
2405        .unwrap();
2406
2407        let operation = spec
2408            .spec
2409            .paths
2410            .paths
2411            .get("/users")
2412            .and_then(|p| p.as_item())
2413            .and_then(|p| p.get.as_ref())
2414            .unwrap();
2415
2416        let response = ResponseGenerator::generate_response_with_expansion(
2417            &spec,
2418            operation,
2419            200,
2420            Some("application/json"),
2421            false, // No expansion
2422        )
2423        .unwrap();
2424
2425        assert!(response.is_object());
2426    }
2427
2428    #[test]
2429    fn test_generate_response_with_array_schema_referenced_items() {
2430        // Test array schema with referenced item schema (lines 1035-1046)
2431        let spec = OpenApiSpec::from_string(
2432            r#"openapi: 3.0.0
2433info:
2434  title: Test API
2435  version: 1.0.0
2436paths:
2437  /items:
2438    get:
2439      responses:
2440        '200':
2441          description: OK
2442          content:
2443            application/json:
2444              schema:
2445                type: array
2446                items:
2447                  $ref: '#/components/schemas/Item'
2448components:
2449  schemas:
2450    Item:
2451      type: object
2452      properties:
2453        id:
2454          type: string
2455        name:
2456          type: string
2457"#,
2458            Some("yaml"),
2459        )
2460        .unwrap();
2461
2462        let operation = spec
2463            .spec
2464            .paths
2465            .paths
2466            .get("/items")
2467            .and_then(|p| p.as_item())
2468            .and_then(|p| p.get.as_ref())
2469            .unwrap();
2470
2471        let response =
2472            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2473                .unwrap();
2474
2475        // Should generate an array with items from referenced schema
2476        let arr = response.as_array().expect("response should be array");
2477        assert!(!arr.is_empty());
2478        if let Some(item) = arr.first() {
2479            let obj = item.as_object().expect("item should be object");
2480            assert!(obj.contains_key("id") || obj.contains_key("name"));
2481        }
2482    }
2483
2484    #[test]
2485    fn test_generate_response_with_array_schema_missing_reference() {
2486        // Test array schema with missing referenced item schema (line 1045)
2487        let spec = OpenApiSpec::from_string(
2488            r#"openapi: 3.0.0
2489info:
2490  title: Test API
2491  version: 1.0.0
2492paths:
2493  /items:
2494    get:
2495      responses:
2496        '200':
2497          description: OK
2498          content:
2499            application/json:
2500              schema:
2501                type: array
2502                items:
2503                  $ref: '#/components/schemas/NonExistentItem'
2504components:
2505  schemas: {}
2506"#,
2507            Some("yaml"),
2508        )
2509        .unwrap();
2510
2511        let operation = spec
2512            .spec
2513            .paths
2514            .paths
2515            .get("/items")
2516            .and_then(|p| p.as_item())
2517            .and_then(|p| p.get.as_ref())
2518            .unwrap();
2519
2520        let response =
2521            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2522                .unwrap();
2523
2524        // Should generate an array with empty objects when reference not found
2525        let arr = response.as_array().expect("response should be array");
2526        assert!(!arr.is_empty());
2527    }
2528
2529    #[test]
2530    fn test_generate_response_with_array_example_and_pagination() {
2531        // Test array generation with pagination using example items (lines 1114-1126)
2532        let spec = OpenApiSpec::from_string(
2533            r#"openapi: 3.0.0
2534info:
2535  title: Test API
2536  version: 1.0.0
2537paths:
2538  /products:
2539    get:
2540      responses:
2541        '200':
2542          description: OK
2543          content:
2544            application/json:
2545              schema:
2546                type: array
2547                example: [{"id": 1, "name": "Product 1"}]
2548                items:
2549                  type: object
2550                  properties:
2551                    id:
2552                      type: integer
2553                    name:
2554                      type: string
2555"#,
2556            Some("yaml"),
2557        )
2558        .unwrap();
2559
2560        let operation = spec
2561            .spec
2562            .paths
2563            .paths
2564            .get("/products")
2565            .and_then(|p| p.as_item())
2566            .and_then(|p| p.get.as_ref())
2567            .unwrap();
2568
2569        let response =
2570            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2571                .unwrap();
2572
2573        // Should generate an array using the example as template
2574        let arr = response.as_array().expect("response should be array");
2575        assert!(!arr.is_empty());
2576        if let Some(item) = arr.first() {
2577            let obj = item.as_object().expect("item should be object");
2578            assert!(obj.contains_key("id") || obj.contains_key("name"));
2579        }
2580    }
2581
2582    #[test]
2583    fn test_generate_response_with_missing_response_reference() {
2584        // Test response generation with missing response reference (lines 294-298)
2585        let spec = OpenApiSpec::from_string(
2586            r#"openapi: 3.0.0
2587info:
2588  title: Test API
2589  version: 1.0.0
2590paths:
2591  /users:
2592    get:
2593      responses:
2594        '200':
2595          $ref: '#/components/responses/NonExistentResponse'
2596components:
2597  responses: {}
2598"#,
2599            Some("yaml"),
2600        )
2601        .unwrap();
2602
2603        let operation = spec
2604            .spec
2605            .paths
2606            .paths
2607            .get("/users")
2608            .and_then(|p| p.as_item())
2609            .and_then(|p| p.get.as_ref())
2610            .unwrap();
2611
2612        let response =
2613            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2614                .unwrap();
2615
2616        // Should return empty object when reference not found
2617        assert!(response.is_object());
2618        assert!(response.as_object().unwrap().is_empty());
2619    }
2620
2621    #[test]
2622    fn test_generate_response_with_no_response_for_status() {
2623        // Test response generation when no response found for status code (lines 302-310)
2624        let spec = OpenApiSpec::from_string(
2625            r#"openapi: 3.0.0
2626info:
2627  title: Test API
2628  version: 1.0.0
2629paths:
2630  /users:
2631    get:
2632      responses:
2633        '404':
2634          description: Not found
2635"#,
2636            Some("yaml"),
2637        )
2638        .unwrap();
2639
2640        let operation = spec
2641            .spec
2642            .paths
2643            .paths
2644            .get("/users")
2645            .and_then(|p| p.as_item())
2646            .and_then(|p| p.get.as_ref())
2647            .unwrap();
2648
2649        // Request status code 200 but only 404 is defined
2650        let response =
2651            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2652                .unwrap();
2653
2654        // Should return empty object when no response found
2655        assert!(response.is_object());
2656        assert!(response.as_object().unwrap().is_empty());
2657    }
2658}
2659
2660/// Mock response data
2661#[derive(Debug, Clone)]
2662pub struct MockResponse {
2663    /// HTTP status code
2664    pub status_code: u16,
2665    /// Response headers
2666    pub headers: HashMap<String, String>,
2667    /// Response body
2668    pub body: Option<Value>,
2669}
2670
2671impl MockResponse {
2672    /// Create a new mock response
2673    pub fn new(status_code: u16) -> Self {
2674        Self {
2675            status_code,
2676            headers: HashMap::new(),
2677            body: None,
2678        }
2679    }
2680
2681    /// Add a header to the response
2682    pub fn with_header(mut self, name: String, value: String) -> Self {
2683        self.headers.insert(name, value);
2684        self
2685    }
2686
2687    /// Set the response body
2688    pub fn with_body(mut self, body: Value) -> Self {
2689        self.body = Some(body);
2690        self
2691    }
2692}
2693
2694/// OpenAPI security requirement wrapper
2695#[derive(Debug, Clone)]
2696pub struct OpenApiSecurityRequirement {
2697    /// The security scheme name
2698    pub scheme: String,
2699    /// Required scopes (for OAuth2)
2700    pub scopes: Vec<String>,
2701}
2702
2703impl OpenApiSecurityRequirement {
2704    /// Create a new security requirement
2705    pub fn new(scheme: String, scopes: Vec<String>) -> Self {
2706        Self { scheme, scopes }
2707    }
2708}
2709
2710/// OpenAPI operation wrapper with path context
2711#[derive(Debug, Clone)]
2712pub struct OpenApiOperation {
2713    /// The HTTP method
2714    pub method: String,
2715    /// The path this operation belongs to
2716    pub path: String,
2717    /// The OpenAPI operation
2718    pub operation: openapiv3::Operation,
2719}
2720
2721impl OpenApiOperation {
2722    /// Create a new OpenApiOperation
2723    pub fn new(method: String, path: String, operation: openapiv3::Operation) -> Self {
2724        Self {
2725            method,
2726            path,
2727            operation,
2728        }
2729    }
2730}