mockforge_core/openapi/
response.rs

1//! OpenAPI response generation and mocking
2//!
3//! This module provides functionality for generating mock responses
4//! based on OpenAPI specifications.
5
6use crate::intelligent_behavior::config::Persona;
7use crate::{
8    ai_response::{AiResponseConfig, RequestContext},
9    OpenApiSpec, Result,
10};
11use async_trait::async_trait;
12use chrono;
13use openapiv3::{Operation, ReferenceOr, Response, Responses, Schema};
14use rand::{thread_rng, Rng};
15use serde_json::Value;
16use std::collections::HashMap;
17use uuid;
18
19/// Trait for AI response generation
20///
21/// This trait allows the HTTP layer to provide custom AI generation
22/// implementations without creating circular dependencies between crates.
23#[async_trait]
24pub trait AiGenerator: Send + Sync {
25    /// Generate an AI response from a prompt
26    ///
27    /// # Arguments
28    /// * `prompt` - The expanded prompt to send to the LLM
29    /// * `config` - The AI response configuration with temperature, max_tokens, etc.
30    ///
31    /// # Returns
32    /// A JSON value containing the generated response
33    async fn generate(&self, prompt: &str, config: &AiResponseConfig) -> Result<Value>;
34}
35
36/// Response generator for creating mock responses
37pub struct ResponseGenerator;
38
39impl ResponseGenerator {
40    /// Generate an AI-assisted response using LLM
41    ///
42    /// This method generates a dynamic response based on request context
43    /// using the configured LLM provider (OpenAI, Anthropic, etc.)
44    ///
45    /// # Arguments
46    /// * `ai_config` - The AI response configuration
47    /// * `context` - The request context for prompt expansion
48    /// * `generator` - Optional AI generator implementation (if None, returns placeholder)
49    ///
50    /// # Returns
51    /// A JSON value containing the generated response
52    pub async fn generate_ai_response(
53        ai_config: &AiResponseConfig,
54        context: &RequestContext,
55        generator: Option<&dyn AiGenerator>,
56    ) -> Result<Value> {
57        // Get the prompt template and expand it with request context
58        let prompt_template = ai_config
59            .prompt
60            .as_ref()
61            .ok_or_else(|| crate::Error::generic("AI prompt is required"))?;
62
63        // Note: expand_prompt_template is now in mockforge-template-expansion crate
64        // For now, we'll do a simple string replacement as a fallback
65        // In the future, this should be refactored to use the template expansion crate
66        let expanded_prompt = prompt_template
67            .replace("{{method}}", &context.method)
68            .replace("{{path}}", &context.path);
69
70        tracing::info!("AI response generation requested with prompt: {}", expanded_prompt);
71
72        // Use the provided generator if available
73        if let Some(gen) = generator {
74            tracing::debug!("Using provided AI generator for response");
75            return gen.generate(&expanded_prompt, ai_config).await;
76        }
77
78        // Fallback: return a descriptive placeholder if no generator is provided
79        tracing::warn!("No AI generator provided, returning placeholder response");
80        Ok(serde_json::json!({
81            "ai_response": "AI generation placeholder",
82            "note": "This endpoint is configured for AI-assisted responses, but no AI generator was provided",
83            "expanded_prompt": expanded_prompt,
84            "mode": format!("{:?}", ai_config.mode),
85            "temperature": ai_config.temperature,
86            "implementation_note": "Pass an AiGenerator implementation to ResponseGenerator::generate_ai_response to enable actual AI generation"
87        }))
88    }
89
90    /// Generate a mock response for an operation and status code
91    pub fn generate_response(
92        spec: &OpenApiSpec,
93        operation: &Operation,
94        status_code: u16,
95        content_type: Option<&str>,
96    ) -> Result<Value> {
97        Self::generate_response_with_expansion(spec, operation, status_code, content_type, true)
98    }
99
100    /// Generate a mock response for an operation and status code with token expansion control
101    pub fn generate_response_with_expansion(
102        spec: &OpenApiSpec,
103        operation: &Operation,
104        status_code: u16,
105        content_type: Option<&str>,
106        expand_tokens: bool,
107    ) -> Result<Value> {
108        Self::generate_response_with_expansion_and_mode(
109            spec,
110            operation,
111            status_code,
112            content_type,
113            expand_tokens,
114            None,
115            None,
116        )
117    }
118
119    /// Generate response with token expansion and selection mode
120    pub fn generate_response_with_expansion_and_mode(
121        spec: &OpenApiSpec,
122        operation: &Operation,
123        status_code: u16,
124        content_type: Option<&str>,
125        expand_tokens: bool,
126        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
127        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
128    ) -> Result<Value> {
129        Self::generate_response_with_expansion_and_mode_and_persona(
130            spec,
131            operation,
132            status_code,
133            content_type,
134            expand_tokens,
135            selection_mode,
136            selector,
137            None, // No persona by default
138        )
139    }
140
141    /// Generate response with token expansion, selection mode, and persona
142    pub fn generate_response_with_expansion_and_mode_and_persona(
143        spec: &OpenApiSpec,
144        operation: &Operation,
145        status_code: u16,
146        content_type: Option<&str>,
147        expand_tokens: bool,
148        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
149        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
150        persona: Option<&Persona>,
151    ) -> Result<Value> {
152        Self::generate_response_with_scenario_and_mode_and_persona(
153            spec,
154            operation,
155            status_code,
156            content_type,
157            expand_tokens,
158            None, // No scenario by default
159            selection_mode,
160            selector,
161            persona,
162        )
163    }
164
165    /// Generate a mock response with scenario support
166    ///
167    /// This method allows selection of specific example scenarios from the OpenAPI spec.
168    /// Scenarios are defined using the standard OpenAPI `examples` field (not the singular `example`).
169    ///
170    /// # Arguments
171    /// * `spec` - The OpenAPI specification
172    /// * `operation` - The operation to generate a response for
173    /// * `status_code` - The HTTP status code
174    /// * `content_type` - Optional content type (e.g., "application/json")
175    /// * `expand_tokens` - Whether to expand template tokens like {{now}} and {{uuid}}
176    /// * `scenario` - Optional scenario name to select from the examples map
177    ///
178    /// # Example
179    /// ```yaml
180    /// responses:
181    ///   '200':
182    ///     content:
183    ///       application/json:
184    ///         examples:
185    ///           happy:
186    ///             value: { "status": "success", "message": "All good!" }
187    ///           error:
188    ///             value: { "status": "error", "message": "Something went wrong" }
189    /// ```
190    pub fn generate_response_with_scenario(
191        spec: &OpenApiSpec,
192        operation: &Operation,
193        status_code: u16,
194        content_type: Option<&str>,
195        expand_tokens: bool,
196        scenario: Option<&str>,
197    ) -> Result<Value> {
198        Self::generate_response_with_scenario_and_mode(
199            spec,
200            operation,
201            status_code,
202            content_type,
203            expand_tokens,
204            scenario,
205            None,
206            None,
207        )
208    }
209
210    /// Generate response with scenario support and selection mode
211    pub fn generate_response_with_scenario_and_mode(
212        spec: &OpenApiSpec,
213        operation: &Operation,
214        status_code: u16,
215        content_type: Option<&str>,
216        expand_tokens: bool,
217        scenario: Option<&str>,
218        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
219        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
220    ) -> Result<Value> {
221        Self::generate_response_with_scenario_and_mode_and_persona(
222            spec,
223            operation,
224            status_code,
225            content_type,
226            expand_tokens,
227            scenario,
228            selection_mode,
229            selector,
230            None, // No persona by default
231        )
232    }
233
234    /// Generate response with scenario support, selection mode, and persona
235    pub fn generate_response_with_scenario_and_mode_and_persona(
236        spec: &OpenApiSpec,
237        operation: &Operation,
238        status_code: u16,
239        content_type: Option<&str>,
240        expand_tokens: bool,
241        scenario: Option<&str>,
242        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
243        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
244        persona: Option<&Persona>,
245    ) -> Result<Value> {
246        // Find the response for the status code
247        let response = Self::find_response_for_status(&operation.responses, status_code);
248
249        tracing::debug!(
250            "Finding response for status code {}: {:?}",
251            status_code,
252            if response.is_some() {
253                "found"
254            } else {
255                "not found"
256            }
257        );
258
259        match response {
260            Some(response_ref) => {
261                match response_ref {
262                    ReferenceOr::Item(response) => {
263                        tracing::debug!(
264                            "Using direct response item with {} content types",
265                            response.content.len()
266                        );
267                        Self::generate_from_response_with_scenario_and_mode(
268                            spec,
269                            response,
270                            content_type,
271                            expand_tokens,
272                            scenario,
273                            selection_mode,
274                            selector,
275                        )
276                    }
277                    ReferenceOr::Reference { reference } => {
278                        tracing::debug!("Resolving response reference: {}", reference);
279                        // Resolve the reference
280                        if let Some(resolved_response) = spec.get_response(reference) {
281                            tracing::debug!(
282                                "Resolved response reference with {} content types",
283                                resolved_response.content.len()
284                            );
285                            Self::generate_from_response_with_scenario_and_mode(
286                                spec,
287                                resolved_response,
288                                content_type,
289                                expand_tokens,
290                                scenario,
291                                selection_mode,
292                                selector,
293                            )
294                        } else {
295                            tracing::warn!("Response reference '{}' not found in spec", reference);
296                            // Reference not found, return empty object
297                            Ok(Value::Object(serde_json::Map::new()))
298                        }
299                    }
300                }
301            }
302            None => {
303                tracing::warn!(
304                    "No response found for status code {} in operation. Available status codes: {:?}",
305                    status_code,
306                    operation.responses.responses.keys().collect::<Vec<_>>()
307                );
308                // No response found for this status code
309                Ok(Value::Object(serde_json::Map::new()))
310            }
311        }
312    }
313
314    /// Find response for a given status code
315    fn find_response_for_status(
316        responses: &Responses,
317        status_code: u16,
318    ) -> Option<&ReferenceOr<Response>> {
319        // First try exact match
320        if let Some(response) = responses.responses.get(&openapiv3::StatusCode::Code(status_code)) {
321            return Some(response);
322        }
323
324        // Try default response
325        if let Some(default_response) = &responses.default {
326            return Some(default_response);
327        }
328
329        None
330    }
331
332    /// Generate response from a Response object
333    fn generate_from_response(
334        spec: &OpenApiSpec,
335        response: &Response,
336        content_type: Option<&str>,
337        expand_tokens: bool,
338    ) -> Result<Value> {
339        Self::generate_from_response_with_scenario(
340            spec,
341            response,
342            content_type,
343            expand_tokens,
344            None,
345        )
346    }
347
348    /// Generate response from a Response object with scenario support
349    fn generate_from_response_with_scenario(
350        spec: &OpenApiSpec,
351        response: &Response,
352        content_type: Option<&str>,
353        expand_tokens: bool,
354        scenario: Option<&str>,
355    ) -> Result<Value> {
356        Self::generate_from_response_with_scenario_and_mode(
357            spec,
358            response,
359            content_type,
360            expand_tokens,
361            scenario,
362            None,
363            None,
364        )
365    }
366
367    /// Generate response from a Response object with scenario support and selection mode
368    fn generate_from_response_with_scenario_and_mode(
369        spec: &OpenApiSpec,
370        response: &Response,
371        content_type: Option<&str>,
372        expand_tokens: bool,
373        scenario: Option<&str>,
374        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
375        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
376    ) -> Result<Value> {
377        Self::generate_from_response_with_scenario_and_mode_and_persona(
378            spec,
379            response,
380            content_type,
381            expand_tokens,
382            scenario,
383            selection_mode,
384            selector,
385            None, // No persona by default
386        )
387    }
388
389    /// Generate response from a Response object with scenario support, selection mode, and persona
390    fn generate_from_response_with_scenario_and_mode_and_persona(
391        spec: &OpenApiSpec,
392        response: &Response,
393        content_type: Option<&str>,
394        expand_tokens: bool,
395        scenario: Option<&str>,
396        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
397        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
398        persona: Option<&Persona>,
399    ) -> Result<Value> {
400        // If content_type is specified, look for that media type
401        if let Some(content_type) = content_type {
402            if let Some(media_type) = response.content.get(content_type) {
403                return Self::generate_from_media_type_with_scenario_and_mode_and_persona(
404                    spec,
405                    media_type,
406                    expand_tokens,
407                    scenario,
408                    selection_mode,
409                    selector,
410                    persona,
411                );
412            }
413        }
414
415        // If no content_type specified or not found, try common content types
416        let preferred_types = ["application/json", "application/xml", "text/plain"];
417
418        for content_type in &preferred_types {
419            if let Some(media_type) = response.content.get(*content_type) {
420                return Self::generate_from_media_type_with_scenario_and_mode_and_persona(
421                    spec,
422                    media_type,
423                    expand_tokens,
424                    scenario,
425                    selection_mode,
426                    selector,
427                    persona,
428                );
429            }
430        }
431
432        // If no suitable content type found, return the first available
433        if let Some((_, media_type)) = response.content.iter().next() {
434            return Self::generate_from_media_type_with_scenario_and_mode_and_persona(
435                spec,
436                media_type,
437                expand_tokens,
438                scenario,
439                selection_mode,
440                selector,
441                persona,
442            );
443        }
444
445        // No content found, return empty object
446        Ok(Value::Object(serde_json::Map::new()))
447    }
448
449    /// Generate response from a MediaType with optional scenario selection
450    fn generate_from_media_type(
451        spec: &OpenApiSpec,
452        media_type: &openapiv3::MediaType,
453        expand_tokens: bool,
454    ) -> Result<Value> {
455        Self::generate_from_media_type_with_scenario(spec, media_type, expand_tokens, None)
456    }
457
458    /// Generate response from a MediaType with scenario support and selection mode
459    fn generate_from_media_type_with_scenario(
460        spec: &OpenApiSpec,
461        media_type: &openapiv3::MediaType,
462        expand_tokens: bool,
463        scenario: Option<&str>,
464    ) -> Result<Value> {
465        Self::generate_from_media_type_with_scenario_and_mode(
466            spec,
467            media_type,
468            expand_tokens,
469            scenario,
470            None,
471            None,
472        )
473    }
474
475    /// Generate response from a MediaType with scenario support and selection mode
476    fn generate_from_media_type_with_scenario_and_mode(
477        spec: &OpenApiSpec,
478        media_type: &openapiv3::MediaType,
479        expand_tokens: bool,
480        scenario: Option<&str>,
481        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
482        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
483    ) -> Result<Value> {
484        Self::generate_from_media_type_with_scenario_and_mode_and_persona(
485            spec,
486            media_type,
487            expand_tokens,
488            scenario,
489            selection_mode,
490            selector,
491            None, // No persona by default
492        )
493    }
494
495    /// Generate response from a MediaType with scenario support, selection mode, and persona
496    fn generate_from_media_type_with_scenario_and_mode_and_persona(
497        spec: &OpenApiSpec,
498        media_type: &openapiv3::MediaType,
499        expand_tokens: bool,
500        scenario: Option<&str>,
501        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
502        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
503        persona: Option<&Persona>,
504    ) -> Result<Value> {
505        // First, check if there's an explicit example
506        // CRITICAL: Always check examples first before falling back to schema generation
507        // This ensures GET requests use the correct response format from OpenAPI examples
508        if let Some(example) = &media_type.example {
509            tracing::debug!("Using explicit example from media type: {:?}", example);
510            // Expand templates in the example if enabled
511            if expand_tokens {
512                let expanded_example = Self::expand_templates(example);
513                return Ok(expanded_example);
514            } else {
515                return Ok(example.clone());
516            }
517        }
518
519        // Then check examples map - with scenario support and selection modes
520        // CRITICAL: Always use examples if available, even if query parameters are missing
521        // This fixes the bug where GET requests without query params return POST-style responses
522        if !media_type.examples.is_empty() {
523            use crate::openapi::response_selection::{ResponseSelectionMode, ResponseSelector};
524
525            tracing::debug!(
526                "Found {} examples in media type, available examples: {:?}",
527                media_type.examples.len(),
528                media_type.examples.keys().collect::<Vec<_>>()
529            );
530
531            // If a scenario is specified, try to find it first
532            if let Some(scenario_name) = scenario {
533                if let Some(example_ref) = media_type.examples.get(scenario_name) {
534                    tracing::debug!("Using scenario '{}' from examples map", scenario_name);
535                    match Self::extract_example_value_with_persona(
536                        spec,
537                        example_ref,
538                        expand_tokens,
539                        persona,
540                        media_type.schema.as_ref(),
541                    ) {
542                        Ok(value) => return Ok(value),
543                        Err(e) => {
544                            tracing::warn!(
545                                "Failed to extract example for scenario '{}': {}, falling back",
546                                scenario_name,
547                                e
548                            );
549                        }
550                    }
551                } else {
552                    tracing::warn!(
553                        "Scenario '{}' not found in examples, falling back based on selection mode",
554                        scenario_name
555                    );
556                }
557            }
558
559            // Determine selection mode
560            let mode = selection_mode.unwrap_or(ResponseSelectionMode::First);
561
562            // Get list of example names for selection
563            let example_names: Vec<String> = media_type.examples.keys().cloned().collect();
564
565            if example_names.is_empty() {
566                // No examples available, fall back to schema
567                tracing::warn!("Examples map is empty, falling back to schema generation");
568            } else if mode == ResponseSelectionMode::Scenario && scenario.is_some() {
569                // Scenario mode was requested but scenario not found, fall through to selection mode
570                tracing::debug!("Scenario not found, using selection mode: {:?}", mode);
571            } else {
572                // Use selection mode to choose an example
573                let selected_index = if let Some(sel) = selector {
574                    sel.select(&example_names)
575                } else {
576                    // Create temporary selector for this selection
577                    let temp_selector = ResponseSelector::new(mode);
578                    temp_selector.select(&example_names)
579                };
580
581                if let Some(example_name) = example_names.get(selected_index) {
582                    if let Some(example_ref) = media_type.examples.get(example_name) {
583                        tracing::debug!(
584                            "Using example '{}' from examples map (mode: {:?}, index: {})",
585                            example_name,
586                            mode,
587                            selected_index
588                        );
589                        match Self::extract_example_value_with_persona(
590                            spec,
591                            example_ref,
592                            expand_tokens,
593                            persona,
594                            media_type.schema.as_ref(),
595                        ) {
596                            Ok(value) => return Ok(value),
597                            Err(e) => {
598                                tracing::warn!(
599                                    "Failed to extract example '{}': {}, trying fallback",
600                                    example_name,
601                                    e
602                                );
603                            }
604                        }
605                    }
606                }
607            }
608
609            // Fall back to first example if selection failed
610            // This is critical - always use the first example if available, even if previous attempts failed
611            if let Some((example_name, example_ref)) = media_type.examples.iter().next() {
612                tracing::debug!(
613                    "Using first example '{}' from examples map as fallback",
614                    example_name
615                );
616                match Self::extract_example_value_with_persona(
617                    spec,
618                    example_ref,
619                    expand_tokens,
620                    persona,
621                    media_type.schema.as_ref(),
622                ) {
623                    Ok(value) => {
624                        tracing::debug!(
625                            "Successfully extracted fallback example '{}'",
626                            example_name
627                        );
628                        return Ok(value);
629                    }
630                    Err(e) => {
631                        tracing::error!(
632                            "Failed to extract fallback example '{}': {}, falling back to schema generation",
633                            example_name,
634                            e
635                        );
636                        // Continue to schema generation as last resort
637                    }
638                }
639            }
640        } else {
641            tracing::debug!("No examples found in media type, will use schema generation");
642        }
643
644        // Fall back to schema-based generation
645        // Pass persona through to schema generation for consistent data patterns
646        if let Some(schema_ref) = &media_type.schema {
647            Ok(Self::generate_example_from_schema_ref(spec, schema_ref, persona))
648        } else {
649            Ok(Value::Object(serde_json::Map::new()))
650        }
651    }
652
653    /// Extract value from an example reference
654    /// Optionally expands items arrays based on pagination metadata if persona is provided
655    fn extract_example_value(
656        spec: &OpenApiSpec,
657        example_ref: &ReferenceOr<openapiv3::Example>,
658        expand_tokens: bool,
659    ) -> Result<Value> {
660        Self::extract_example_value_with_persona(spec, example_ref, expand_tokens, None, None)
661    }
662
663    /// Extract value from an example reference with optional persona and schema for pagination expansion
664    fn extract_example_value_with_persona(
665        spec: &OpenApiSpec,
666        example_ref: &ReferenceOr<openapiv3::Example>,
667        expand_tokens: bool,
668        persona: Option<&Persona>,
669        schema_ref: Option<&ReferenceOr<Schema>>,
670    ) -> Result<Value> {
671        let mut value = match example_ref {
672            ReferenceOr::Item(example) => {
673                if let Some(v) = &example.value {
674                    tracing::debug!("Using example from examples map: {:?}", v);
675                    if expand_tokens {
676                        Self::expand_templates(v)
677                    } else {
678                        v.clone()
679                    }
680                } else {
681                    return Ok(Value::Object(serde_json::Map::new()));
682                }
683            }
684            ReferenceOr::Reference { reference } => {
685                // Resolve the example reference
686                if let Some(example) = spec.get_example(reference) {
687                    if let Some(v) = &example.value {
688                        tracing::debug!("Using resolved example reference: {:?}", v);
689                        if expand_tokens {
690                            Self::expand_templates(v)
691                        } else {
692                            v.clone()
693                        }
694                    } else {
695                        return Ok(Value::Object(serde_json::Map::new()));
696                    }
697                } else {
698                    tracing::warn!("Example reference '{}' not found", reference);
699                    return Ok(Value::Object(serde_json::Map::new()));
700                }
701            }
702        };
703
704        // Check for pagination mismatch and expand items array if needed
705        value = Self::expand_example_items_if_needed(spec, value, persona, schema_ref);
706
707        Ok(value)
708    }
709
710    /// Expand items array in example if pagination metadata suggests more items
711    /// Checks for common response structures: { data: { items: [...], total, limit } } or { items: [...], total, limit }
712    fn expand_example_items_if_needed(
713        spec: &OpenApiSpec,
714        mut example: Value,
715        persona: Option<&Persona>,
716        schema_ref: Option<&ReferenceOr<Schema>>,
717    ) -> Value {
718        // Try to find items array and pagination metadata in the example
719        // Support both nested (data.items) and flat (items) structures
720        let has_nested_items = example
721            .get("data")
722            .and_then(|v| v.as_object())
723            .map(|obj| obj.contains_key("items"))
724            .unwrap_or(false);
725
726        let has_flat_items = example.get("items").is_some();
727
728        if !has_nested_items && !has_flat_items {
729            return example; // No items array found
730        }
731
732        // Extract pagination metadata
733        let total = example
734            .get("data")
735            .and_then(|d| d.get("total"))
736            .or_else(|| example.get("total"))
737            .and_then(|v| v.as_u64().or_else(|| v.as_i64().map(|i| i as u64)));
738
739        let limit = example
740            .get("data")
741            .and_then(|d| d.get("limit"))
742            .or_else(|| example.get("limit"))
743            .and_then(|v| v.as_u64().or_else(|| v.as_i64().map(|i| i as u64)));
744
745        // Get current items array
746        let items_array = example
747            .get("data")
748            .and_then(|d| d.get("items"))
749            .or_else(|| example.get("items"))
750            .and_then(|v| v.as_array())
751            .cloned();
752
753        if let (Some(total_val), Some(limit_val), Some(mut items)) = (total, limit, items_array) {
754            let current_count = items.len() as u64;
755            let expected_count = std::cmp::min(total_val, limit_val);
756            let max_items = 100; // Cap at reasonable maximum
757            let expected_count = std::cmp::min(expected_count, max_items);
758
759            // If items array is smaller than expected, expand it
760            if current_count < expected_count && !items.is_empty() {
761                tracing::debug!(
762                    "Expanding example items array: {} -> {} items (total={}, limit={})",
763                    current_count,
764                    expected_count,
765                    total_val,
766                    limit_val
767                );
768
769                // Use first item as template
770                let template = items[0].clone();
771                let additional_count = expected_count - current_count;
772
773                // Generate additional items
774                for i in 0..additional_count {
775                    let mut new_item = template.clone();
776                    // Use the centralized variation function
777                    let item_index = current_count + i + 1;
778                    Self::add_item_variation(&mut new_item, item_index);
779                    items.push(new_item);
780                }
781
782                // Update the items array in the example
783                if let Some(data_obj) = example.get_mut("data").and_then(|v| v.as_object_mut()) {
784                    data_obj.insert("items".to_string(), Value::Array(items));
785                } else if let Some(root_obj) = example.as_object_mut() {
786                    root_obj.insert("items".to_string(), Value::Array(items));
787                }
788            }
789        }
790
791        example
792    }
793
794    fn generate_example_from_schema_ref(
795        spec: &OpenApiSpec,
796        schema_ref: &ReferenceOr<Schema>,
797        persona: Option<&Persona>,
798    ) -> Value {
799        match schema_ref {
800            ReferenceOr::Item(schema) => Self::generate_example_from_schema(spec, schema, persona),
801            ReferenceOr::Reference { reference } => spec
802                .get_schema(reference)
803                .map(|schema| Self::generate_example_from_schema(spec, &schema.schema, persona))
804                .unwrap_or_else(|| Value::Object(serde_json::Map::new())),
805        }
806    }
807
808    /// Generate example data from an OpenAPI schema
809    ///
810    /// Priority order:
811    /// 1. Schema-level example (schema.schema_data.example)
812    /// 2. Property-level examples when generating objects
813    /// 3. Generated values based on schema type
814    /// 4. Persona traits (if persona provided)
815    fn generate_example_from_schema(
816        spec: &OpenApiSpec,
817        schema: &Schema,
818        persona: Option<&Persona>,
819    ) -> Value {
820        // First, check for schema-level example in schema_data
821        // OpenAPI v3 stores examples in schema_data.example
822        if let Some(example) = schema.schema_data.example.as_ref() {
823            tracing::debug!("Using schema-level example: {:?}", example);
824            return example.clone();
825        }
826
827        // Note: schema-level example check happens at the top of the function (line 380-383)
828        // At this point, if we have a schema-level example, we've already returned it
829        // So we only generate defaults when no example exists
830        match &schema.schema_kind {
831            openapiv3::SchemaKind::Type(openapiv3::Type::String(_)) => {
832                // Use faker for string fields based on field name hints
833                Value::String("example string".to_string())
834            }
835            openapiv3::SchemaKind::Type(openapiv3::Type::Integer(_)) => Value::Number(42.into()),
836            openapiv3::SchemaKind::Type(openapiv3::Type::Number(_)) => {
837                Value::Number(serde_json::Number::from_f64(std::f64::consts::PI).unwrap())
838            }
839            openapiv3::SchemaKind::Type(openapiv3::Type::Boolean(_)) => Value::Bool(true),
840            openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) => {
841                // First pass: Scan for pagination metadata (total, page, limit)
842                // This helps us generate the correct number of array items
843                let mut pagination_metadata: Option<(u64, u64, u64)> = None; // (total, page, limit)
844
845                // Check if this looks like a paginated response by scanning properties
846                // Look for "items" array property and pagination fields
847                let has_items =
848                    obj.properties.iter().any(|(name, _)| name.to_lowercase() == "items");
849
850                if has_items {
851                    // Try to extract pagination metadata from schema properties
852                    let mut total_opt = None;
853                    let mut page_opt = None;
854                    let mut limit_opt = None;
855
856                    for (prop_name, prop_schema) in &obj.properties {
857                        let prop_lower = prop_name.to_lowercase();
858                        // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema> for extraction
859                        let schema_ref: ReferenceOr<Schema> = match prop_schema {
860                            ReferenceOr::Item(boxed) => ReferenceOr::Item(boxed.as_ref().clone()),
861                            ReferenceOr::Reference { reference } => ReferenceOr::Reference {
862                                reference: reference.clone(),
863                            },
864                        };
865                        if prop_lower == "total" || prop_lower == "count" || prop_lower == "size" {
866                            total_opt = Self::extract_numeric_value_from_schema(&schema_ref);
867                        } else if prop_lower == "page" {
868                            page_opt = Self::extract_numeric_value_from_schema(&schema_ref);
869                        } else if prop_lower == "limit" || prop_lower == "per_page" {
870                            limit_opt = Self::extract_numeric_value_from_schema(&schema_ref);
871                        }
872                    }
873
874                    // If we found a total, use it (with defaults for page/limit)
875                    if let Some(total) = total_opt {
876                        let page = page_opt.unwrap_or(1);
877                        let limit = limit_opt.unwrap_or(20);
878                        pagination_metadata = Some((total, page, limit));
879                        tracing::debug!(
880                            "Detected pagination metadata: total={}, page={}, limit={}",
881                            total,
882                            page,
883                            limit
884                        );
885                    } else {
886                        // Phase 3: If no total found in schema, try to infer from parent entity
887                        // Look for "items" array to determine child entity name
888                        if obj.properties.contains_key("items") {
889                            // Try to infer parent/child relationship from schema names
890                            // This is a heuristic: if we're generating a paginated response,
891                            // check if we can find a parent entity schema with a count field
892                            if let Some(inferred_total) =
893                                Self::try_infer_total_from_context(spec, obj)
894                            {
895                                let page = page_opt.unwrap_or(1);
896                                let limit = limit_opt.unwrap_or(20);
897                                pagination_metadata = Some((inferred_total, page, limit));
898                                tracing::debug!(
899                                    "Inferred pagination metadata from parent entity: total={}, page={}, limit={}",
900                                    inferred_total, page, limit
901                                );
902                            } else {
903                                // Phase 4: Try to use persona traits if available
904                                if let Some(persona) = persona {
905                                    // Look for count-related traits (e.g., "hive_count", "apiary_count")
906                                    // Try common patterns
907                                    let count_keys =
908                                        ["hive_count", "apiary_count", "item_count", "total_count"];
909                                    for key in &count_keys {
910                                        if let Some(count) = persona.get_numeric_trait(key) {
911                                            let page = page_opt.unwrap_or(1);
912                                            let limit = limit_opt.unwrap_or(20);
913                                            pagination_metadata = Some((count, page, limit));
914                                            tracing::debug!(
915                                                "Using persona trait '{}' for pagination: total={}, page={}, limit={}",
916                                                key, count, page, limit
917                                            );
918                                            break;
919                                        }
920                                    }
921                                }
922                            }
923                        }
924                    }
925                }
926
927                let mut map = serde_json::Map::new();
928                for (prop_name, prop_schema) in &obj.properties {
929                    let prop_lower = prop_name.to_lowercase();
930
931                    // Check if this is an array property that should use pagination metadata
932                    let is_items_array = prop_lower == "items" && pagination_metadata.is_some();
933
934                    let value = match prop_schema {
935                        ReferenceOr::Item(prop_schema) => {
936                            // If this is an items array with pagination metadata, always use generate_array_with_count
937                            // (it will use the example as a template if one exists)
938                            if is_items_array {
939                                // Generate array with count based on pagination metadata
940                                Self::generate_array_with_count(
941                                    spec,
942                                    prop_schema.as_ref(),
943                                    pagination_metadata.unwrap(),
944                                    persona,
945                                )
946                            } else if let Some(prop_example) =
947                                prop_schema.schema_data.example.as_ref()
948                            {
949                                // Check for property-level example (only if not items array)
950                                tracing::debug!(
951                                    "Using example for property '{}': {:?}",
952                                    prop_name,
953                                    prop_example
954                                );
955                                prop_example.clone()
956                            } else {
957                                Self::generate_example_from_schema(
958                                    spec,
959                                    prop_schema.as_ref(),
960                                    persona,
961                                )
962                            }
963                        }
964                        ReferenceOr::Reference { reference } => {
965                            // Try to resolve reference
966                            if let Some(resolved_schema) = spec.get_schema(reference) {
967                                // If this is an items array with pagination metadata, always use generate_array_with_count
968                                if is_items_array {
969                                    // Generate array with count based on pagination metadata
970                                    Self::generate_array_with_count(
971                                        spec,
972                                        &resolved_schema.schema,
973                                        pagination_metadata.unwrap(),
974                                        persona,
975                                    )
976                                } else if let Some(ref_example) =
977                                    resolved_schema.schema.schema_data.example.as_ref()
978                                {
979                                    // Check for example from referenced schema (only if not items array)
980                                    tracing::debug!(
981                                        "Using example from referenced schema '{}': {:?}",
982                                        reference,
983                                        ref_example
984                                    );
985                                    ref_example.clone()
986                                } else {
987                                    Self::generate_example_from_schema(
988                                        spec,
989                                        &resolved_schema.schema,
990                                        persona,
991                                    )
992                                }
993                            } else {
994                                Self::generate_example_for_property(prop_name)
995                            }
996                        }
997                    };
998                    let value = match value {
999                        Value::Null => Self::generate_example_for_property(prop_name),
1000                        Value::Object(ref obj) if obj.is_empty() => {
1001                            Self::generate_example_for_property(prop_name)
1002                        }
1003                        _ => value,
1004                    };
1005                    map.insert(prop_name.clone(), value);
1006                }
1007
1008                // Ensure pagination metadata is set if we detected it
1009                if let Some((total, page, limit)) = pagination_metadata {
1010                    map.insert("total".to_string(), Value::Number(total.into()));
1011                    map.insert("page".to_string(), Value::Number(page.into()));
1012                    map.insert("limit".to_string(), Value::Number(limit.into()));
1013                }
1014
1015                Value::Object(map)
1016            }
1017            openapiv3::SchemaKind::Type(openapiv3::Type::Array(arr)) => {
1018                // Check for array-level example (schema.schema_data.example contains the full array)
1019                // Note: This check is actually redundant since we check at the top,
1020                // but keeping it here for clarity and defensive programming
1021                // If the array schema itself has an example, it's already handled at the top
1022
1023                match &arr.items {
1024                    Some(item_schema) => {
1025                        let example_item = match item_schema {
1026                            ReferenceOr::Item(item_schema) => {
1027                                // Recursively generate example for array item
1028                                // This will check for item-level examples
1029                                Self::generate_example_from_schema(
1030                                    spec,
1031                                    item_schema.as_ref(),
1032                                    persona,
1033                                )
1034                            }
1035                            ReferenceOr::Reference { reference } => {
1036                                // Try to resolve reference and generate example
1037                                // This will check for examples in referenced schema
1038                                if let Some(resolved_schema) = spec.get_schema(reference) {
1039                                    Self::generate_example_from_schema(
1040                                        spec,
1041                                        &resolved_schema.schema,
1042                                        persona,
1043                                    )
1044                                } else {
1045                                    Value::Object(serde_json::Map::new())
1046                                }
1047                            }
1048                        };
1049                        Value::Array(vec![example_item])
1050                    }
1051                    None => Value::Array(vec![Value::String("item".to_string())]),
1052                }
1053            }
1054            _ => Value::Object(serde_json::Map::new()),
1055        }
1056    }
1057
1058    /// Extract numeric value from a schema (from example or default)
1059    /// Returns None if no numeric value can be extracted
1060    fn extract_numeric_value_from_schema(schema_ref: &ReferenceOr<Schema>) -> Option<u64> {
1061        match schema_ref {
1062            ReferenceOr::Item(schema) => {
1063                // Check for example value first
1064                if let Some(example) = schema.schema_data.example.as_ref() {
1065                    if let Some(num) = example.as_u64() {
1066                        return Some(num);
1067                    } else if let Some(num) = example.as_f64() {
1068                        return Some(num as u64);
1069                    }
1070                }
1071                // Check for default value
1072                if let Some(default) = schema.schema_data.default.as_ref() {
1073                    if let Some(num) = default.as_u64() {
1074                        return Some(num);
1075                    } else if let Some(num) = default.as_f64() {
1076                        return Some(num as u64);
1077                    }
1078                }
1079                // For integer types, try to extract from schema constraints
1080                // Note: IntegerType doesn't have a default field in openapiv3
1081                // Defaults are stored in schema_data.default instead
1082                None
1083            }
1084            ReferenceOr::Reference { reference: _ } => {
1085                // For references, we'd need to resolve them, but for now return None
1086                // This can be enhanced later if needed
1087                None
1088            }
1089        }
1090    }
1091
1092    /// Generate an array with a specific count based on pagination metadata
1093    /// Respects the limit (e.g., if total=50 and limit=20, generates 20 items)
1094    fn generate_array_with_count(
1095        spec: &OpenApiSpec,
1096        array_schema: &Schema,
1097        pagination: (u64, u64, u64), // (total, page, limit)
1098        persona: Option<&Persona>,
1099    ) -> Value {
1100        let (total, _page, limit) = pagination;
1101
1102        // Determine how many items to generate
1103        // Respect pagination: generate min(total, limit) items
1104        let count = std::cmp::min(total, limit);
1105
1106        // Cap at reasonable maximum to avoid performance issues
1107        let max_items = 100;
1108        let count = std::cmp::min(count, max_items);
1109
1110        tracing::debug!("Generating array with count={} (total={}, limit={})", count, total, limit);
1111
1112        // Check if array schema has an example with items
1113        if let Some(example) = array_schema.schema_data.example.as_ref() {
1114            if let Some(example_array) = example.as_array() {
1115                if !example_array.is_empty() {
1116                    // Use first example item as template
1117                    let template_item = &example_array[0];
1118                    let items: Vec<Value> = (0..count)
1119                        .map(|i| {
1120                            // Clone template and add variation
1121                            let mut item = template_item.clone();
1122                            Self::add_item_variation(&mut item, i + 1);
1123                            item
1124                        })
1125                        .collect();
1126                    return Value::Array(items);
1127                }
1128            }
1129        }
1130
1131        // Generate items from schema
1132        if let openapiv3::SchemaKind::Type(openapiv3::Type::Array(arr)) = &array_schema.schema_kind
1133        {
1134            if let Some(item_schema) = &arr.items {
1135                let items: Vec<Value> = match item_schema {
1136                    ReferenceOr::Item(item_schema) => {
1137                        (0..count)
1138                            .map(|i| {
1139                                let mut item = Self::generate_example_from_schema(
1140                                    spec,
1141                                    item_schema.as_ref(),
1142                                    persona,
1143                                );
1144                                // Add variation to make items unique
1145                                Self::add_item_variation(&mut item, i + 1);
1146                                item
1147                            })
1148                            .collect()
1149                    }
1150                    ReferenceOr::Reference { reference } => {
1151                        if let Some(resolved_schema) = spec.get_schema(reference) {
1152                            (0..count)
1153                                .map(|i| {
1154                                    let mut item = Self::generate_example_from_schema(
1155                                        spec,
1156                                        &resolved_schema.schema,
1157                                        persona,
1158                                    );
1159                                    // Add variation to make items unique
1160                                    Self::add_item_variation(&mut item, i + 1);
1161                                    item
1162                                })
1163                                .collect()
1164                        } else {
1165                            vec![Value::Object(serde_json::Map::new()); count as usize]
1166                        }
1167                    }
1168                };
1169                return Value::Array(items);
1170            }
1171        }
1172
1173        // Fallback: generate simple items
1174        Value::Array((0..count).map(|i| Value::String(format!("item_{}", i + 1))).collect())
1175    }
1176
1177    /// Add variation to an item to make it unique (for array generation)
1178    /// Varies IDs, names, addresses, and coordinates based on item index
1179    fn add_item_variation(item: &mut Value, item_index: u64) {
1180        if let Some(obj) = item.as_object_mut() {
1181            // Update ID fields to be unique
1182            if let Some(id_val) = obj.get_mut("id") {
1183                if let Some(id_str) = id_val.as_str() {
1184                    // Extract base ID (remove any existing suffix)
1185                    let base_id = id_str.split('_').next().unwrap_or(id_str);
1186                    *id_val = Value::String(format!("{}_{:03}", base_id, item_index));
1187                } else if let Some(id_num) = id_val.as_u64() {
1188                    *id_val = Value::Number((id_num + item_index).into());
1189                }
1190            }
1191
1192            // Update name fields - add variation for all names
1193            if let Some(name_val) = obj.get_mut("name") {
1194                if let Some(name_str) = name_val.as_str() {
1195                    if name_str.contains('#') {
1196                        // Pattern like "Hive #1" -> "Hive #2"
1197                        *name_val = Value::String(format!("Hive #{}", item_index));
1198                    } else {
1199                        // Pattern like "Meadow Apiary" -> use rotation of varied names
1200                        // 60+ unique apiary names with geographic diversity for realistic demo
1201                        let apiary_names = [
1202                            // Midwest/Prairie names
1203                            "Meadow Apiary",
1204                            "Prairie Apiary",
1205                            "Sunset Valley Apiary",
1206                            "Golden Fields Apiary",
1207                            "Miller Family Apiary",
1208                            "Heartland Honey Co.",
1209                            "Cornfield Apiary",
1210                            "Harvest Moon Apiary",
1211                            "Prairie Winds Apiary",
1212                            "Amber Fields Apiary",
1213                            // California/Coastal names
1214                            "Coastal Apiary",
1215                            "Sunset Coast Apiary",
1216                            "Pacific Grove Apiary",
1217                            "Golden Gate Apiary",
1218                            "Napa Valley Apiary",
1219                            "Coastal Breeze Apiary",
1220                            "Pacific Heights Apiary",
1221                            "Bay Area Apiary",
1222                            "Sunset Valley Honey Co.",
1223                            "Coastal Harvest Apiary",
1224                            // Texas/Ranch names
1225                            "Lone Star Apiary",
1226                            "Texas Ranch Apiary",
1227                            "Big Sky Apiary",
1228                            "Prairie Rose Apiary",
1229                            "Hill Country Apiary",
1230                            "Lone Star Honey Co.",
1231                            "Texas Pride Apiary",
1232                            "Wildflower Ranch",
1233                            "Desert Bloom Apiary",
1234                            "Cactus Creek Apiary",
1235                            // Florida/Grove names
1236                            "Orange Grove Apiary",
1237                            "Citrus Grove Apiary",
1238                            "Palm Grove Apiary",
1239                            "Tropical Breeze Apiary",
1240                            "Everglades Apiary",
1241                            "Sunshine State Apiary",
1242                            "Florida Keys Apiary",
1243                            "Grove View Apiary",
1244                            "Tropical Harvest Apiary",
1245                            "Palm Coast Apiary",
1246                            // Northeast/Valley names
1247                            "Mountain View Apiary",
1248                            "Valley Apiary",
1249                            "Riverside Apiary",
1250                            "Hilltop Apiary",
1251                            "Forest Apiary",
1252                            "Mountain Apiary",
1253                            "Lakeside Apiary",
1254                            "Ridge Apiary",
1255                            "Brook Apiary",
1256                            "Hillside Apiary",
1257                            // Generic/Professional names
1258                            "Field Apiary",
1259                            "Creek Apiary",
1260                            "Woodland Apiary",
1261                            "Farm Apiary",
1262                            "Orchard Apiary",
1263                            "Pasture Apiary",
1264                            "Green Valley Apiary",
1265                            "Blue Sky Apiary",
1266                            "Sweet Honey Apiary",
1267                            "Nature's Best Apiary",
1268                            // Business/Commercial names
1269                            "Premium Honey Co.",
1270                            "Artisan Apiary",
1271                            "Heritage Apiary",
1272                            "Summit Apiary",
1273                            "Crystal Springs Apiary",
1274                            "Maple Grove Apiary",
1275                            "Wildflower Apiary",
1276                            "Thistle Apiary",
1277                            "Clover Field Apiary",
1278                            "Honeycomb Apiary",
1279                        ];
1280                        let name_index = (item_index - 1) as usize % apiary_names.len();
1281                        *name_val = Value::String(apiary_names[name_index].to_string());
1282                    }
1283                }
1284            }
1285
1286            // Update location/address fields
1287            if let Some(location_val) = obj.get_mut("location") {
1288                if let Some(location_obj) = location_val.as_object_mut() {
1289                    // Update address
1290                    if let Some(address_val) = location_obj.get_mut("address") {
1291                        if let Some(address_str) = address_val.as_str() {
1292                            // Extract street number if present, otherwise add variation
1293                            if let Some(num_str) = address_str.split_whitespace().next() {
1294                                if let Ok(num) = num_str.parse::<u64>() {
1295                                    *address_val =
1296                                        Value::String(format!("{} Farm Road", num + item_index));
1297                                } else {
1298                                    *address_val =
1299                                        Value::String(format!("{} Farm Road", 100 + item_index));
1300                                }
1301                            } else {
1302                                *address_val =
1303                                    Value::String(format!("{} Farm Road", 100 + item_index));
1304                            }
1305                        }
1306                    }
1307
1308                    // Vary coordinates slightly
1309                    if let Some(lat_val) = location_obj.get_mut("latitude") {
1310                        if let Some(lat) = lat_val.as_f64() {
1311                            *lat_val = Value::Number(
1312                                serde_json::Number::from_f64(lat + (item_index as f64 * 0.01))
1313                                    .unwrap(),
1314                            );
1315                        }
1316                    }
1317                    if let Some(lng_val) = location_obj.get_mut("longitude") {
1318                        if let Some(lng) = lng_val.as_f64() {
1319                            *lng_val = Value::Number(
1320                                serde_json::Number::from_f64(lng + (item_index as f64 * 0.01))
1321                                    .unwrap(),
1322                            );
1323                        }
1324                    }
1325                } else if let Some(address_str) = location_val.as_str() {
1326                    // Flat address string
1327                    if let Some(num_str) = address_str.split_whitespace().next() {
1328                        if let Ok(num) = num_str.parse::<u64>() {
1329                            *location_val =
1330                                Value::String(format!("{} Farm Road", num + item_index));
1331                        } else {
1332                            *location_val =
1333                                Value::String(format!("{} Farm Road", 100 + item_index));
1334                        }
1335                    }
1336                }
1337            }
1338
1339            // Update address field if it exists at root level
1340            if let Some(address_val) = obj.get_mut("address") {
1341                if let Some(address_str) = address_val.as_str() {
1342                    if let Some(num_str) = address_str.split_whitespace().next() {
1343                        if let Ok(num) = num_str.parse::<u64>() {
1344                            *address_val = Value::String(format!("{} Farm Road", num + item_index));
1345                        } else {
1346                            *address_val = Value::String(format!("{} Farm Road", 100 + item_index));
1347                        }
1348                    }
1349                }
1350            }
1351
1352            // Vary status fields (common enum values)
1353            if let Some(status_val) = obj.get_mut("status") {
1354                if let Some(status_str) = status_val.as_str() {
1355                    let statuses = [
1356                        "healthy",
1357                        "sick",
1358                        "needs_attention",
1359                        "quarantined",
1360                        "active",
1361                        "inactive",
1362                    ];
1363                    let status_index = (item_index - 1) as usize % statuses.len();
1364                    // Bias towards "healthy" and "active" (70% of items)
1365                    let final_status = if (item_index - 1) % 10 < 7 {
1366                        statuses[0] // "healthy" or "active"
1367                    } else {
1368                        statuses[status_index]
1369                    };
1370                    *status_val = Value::String(final_status.to_string());
1371                }
1372            }
1373
1374            // Vary hive_type fields
1375            if let Some(hive_type_val) = obj.get_mut("hive_type") {
1376                if hive_type_val.as_str().is_some() {
1377                    let hive_types = ["langstroth", "top_bar", "warre", "flow_hive", "national"];
1378                    let type_index = (item_index - 1) as usize % hive_types.len();
1379                    *hive_type_val = Value::String(hive_types[type_index].to_string());
1380                }
1381            }
1382
1383            // Vary nested queen breed fields
1384            if let Some(queen_val) = obj.get_mut("queen") {
1385                if let Some(queen_obj) = queen_val.as_object_mut() {
1386                    if let Some(breed_val) = queen_obj.get_mut("breed") {
1387                        if breed_val.as_str().is_some() {
1388                            let breeds =
1389                                ["italian", "carniolan", "russian", "buckfast", "caucasian"];
1390                            let breed_index = (item_index - 1) as usize % breeds.len();
1391                            *breed_val = Value::String(breeds[breed_index].to_string());
1392                        }
1393                    }
1394                    // Vary queen age
1395                    if let Some(age_val) = queen_obj.get_mut("age_days") {
1396                        if let Some(base_age) = age_val.as_u64() {
1397                            *age_val = Value::Number((base_age + (item_index * 10) % 200).into());
1398                        } else if let Some(base_age) = age_val.as_i64() {
1399                            *age_val =
1400                                Value::Number((base_age + (item_index as i64 * 10) % 200).into());
1401                        }
1402                    }
1403                    // Vary queen mark color
1404                    if let Some(color_val) = queen_obj.get_mut("mark_color") {
1405                        if color_val.as_str().is_some() {
1406                            let colors = ["yellow", "white", "red", "green", "blue"];
1407                            let color_index = (item_index - 1) as usize % colors.len();
1408                            *color_val = Value::String(colors[color_index].to_string());
1409                        }
1410                    }
1411                }
1412            }
1413
1414            // Vary description fields if they exist
1415            if let Some(desc_val) = obj.get_mut("description") {
1416                if let Some(desc_str) = desc_val.as_str() {
1417                    let descriptions = [
1418                        "Production apiary",
1419                        "Research apiary",
1420                        "Commercial operation",
1421                        "Backyard apiary",
1422                        "Educational apiary",
1423                    ];
1424                    let desc_index = (item_index - 1) as usize % descriptions.len();
1425                    *desc_val = Value::String(descriptions[desc_index].to_string());
1426                }
1427            }
1428
1429            // Vary timestamp fields (created_at, updated_at, timestamp, date) for realistic time-series data
1430            // Generate timestamps spanning 12-24 months with proper distribution
1431            let timestamp_fields = [
1432                "created_at",
1433                "updated_at",
1434                "timestamp",
1435                "date",
1436                "forecastDate",
1437                "predictedDate",
1438            ];
1439            for field_name in &timestamp_fields {
1440                if let Some(timestamp_val) = obj.get_mut(*field_name) {
1441                    if let Some(_timestamp_str) = timestamp_val.as_str() {
1442                        // Generate realistic timestamp: distribute items over past 12-18 months
1443                        // Use item_index to create variation (not all same date)
1444                        let months_ago = 12 + ((item_index - 1) % 6); // Distribute over 6 months (12-18 months ago)
1445                        let days_offset = (item_index - 1) % 28; // Distribute within month (cap at 28)
1446                        let hours_offset = ((item_index * 7) % 24) as u8; // Distribute throughout day
1447                        let minutes_offset = ((item_index * 11) % 60) as u8; // Vary minutes
1448
1449                        // Calculate timestamp relative to current date (November 2024)
1450                        // Format: ISO 8601 (e.g., "2024-11-12T14:30:00Z")
1451                        let base_year = 2024;
1452                        let base_month = 11;
1453
1454                        // Calculate target month (going back in time)
1455                        let target_year = if months_ago >= base_month as u64 {
1456                            base_year - 1
1457                        } else {
1458                            base_year
1459                        };
1460                        let target_month = if months_ago >= base_month as u64 {
1461                            12 - (months_ago - base_month as u64) as u8
1462                        } else {
1463                            (base_month as u64 - months_ago) as u8
1464                        };
1465                        let target_day = std::cmp::min(28, 1 + days_offset as u8); // Start from day 1, cap at 28
1466
1467                        // Format as ISO 8601
1468                        let timestamp = format!(
1469                            "{:04}-{:02}-{:02}T{:02}:{:02}:00Z",
1470                            target_year, target_month, target_day, hours_offset, minutes_offset
1471                        );
1472                        *timestamp_val = Value::String(timestamp);
1473                    }
1474                }
1475            }
1476        }
1477    }
1478
1479    /// Try to infer total count from context (parent entity schemas)
1480    /// This is a heuristic that looks for common relationship patterns
1481    fn try_infer_total_from_context(
1482        spec: &OpenApiSpec,
1483        obj_type: &openapiv3::ObjectType,
1484    ) -> Option<u64> {
1485        // Look for "items" array to determine what we're generating
1486        if let Some(items_schema_ref) = obj_type.properties.get("items") {
1487            // Try to determine child entity name from items schema
1488            // This is a heuristic: check schema names in the spec
1489            if let Some(components) = &spec.spec.components {
1490                let schemas = &components.schemas;
1491                // Look through all schemas to find potential parent entities
1492                // that might have count fields matching the items type
1493                for (schema_name, schema_ref) in schemas {
1494                    if let ReferenceOr::Item(schema) = schema_ref {
1495                        if let openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) =
1496                            &schema.schema_kind
1497                        {
1498                            // Look for count fields that might match
1499                            for (prop_name, prop_schema) in &obj.properties {
1500                                let prop_lower = prop_name.to_lowercase();
1501                                if prop_lower.ends_with("_count") {
1502                                    // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema>
1503                                    let schema_ref: ReferenceOr<Schema> = match prop_schema {
1504                                        ReferenceOr::Item(boxed) => {
1505                                            ReferenceOr::Item(boxed.as_ref().clone())
1506                                        }
1507                                        ReferenceOr::Reference { reference } => {
1508                                            ReferenceOr::Reference {
1509                                                reference: reference.clone(),
1510                                            }
1511                                        }
1512                                    };
1513                                    // Found a count field, try to extract its value
1514                                    if let Some(count) =
1515                                        Self::extract_numeric_value_from_schema(&schema_ref)
1516                                    {
1517                                        // Use a reasonable default if count is very large
1518                                        if count > 0 && count <= 1000 {
1519                                            tracing::debug!(
1520                                                "Inferred count {} from parent schema {} field {}",
1521                                                count,
1522                                                schema_name,
1523                                                prop_name
1524                                            );
1525                                            return Some(count);
1526                                        }
1527                                    }
1528                                }
1529                            }
1530                        }
1531                    }
1532                }
1533            }
1534        }
1535
1536        None
1537    }
1538
1539    /// Infer relationship count from parent entity schema
1540    /// When generating a child entity list, check if parent entity has a count field
1541    fn infer_count_from_parent_schema(
1542        spec: &OpenApiSpec,
1543        parent_entity_name: &str,
1544        child_entity_name: &str,
1545    ) -> Option<u64> {
1546        // Look for parent entity schema
1547        let parent_schema_name = parent_entity_name.to_string();
1548        let count_field_name = format!("{}_count", child_entity_name);
1549
1550        // Try to find the schema
1551        if let Some(components) = &spec.spec.components {
1552            let schemas = &components.schemas;
1553            // Look for parent schema (case-insensitive)
1554            for (schema_name, schema_ref) in schemas {
1555                let schema_name_lower = schema_name.to_lowercase();
1556                if schema_name_lower.contains(&parent_entity_name.to_lowercase()) {
1557                    if let ReferenceOr::Item(schema) = schema_ref {
1558                        // Check if this schema has the count field
1559                        if let openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) =
1560                            &schema.schema_kind
1561                        {
1562                            for (prop_name, prop_schema) in &obj.properties {
1563                                if prop_name.to_lowercase() == count_field_name.to_lowercase() {
1564                                    // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema>
1565                                    let schema_ref: ReferenceOr<Schema> = match prop_schema {
1566                                        ReferenceOr::Item(boxed) => {
1567                                            ReferenceOr::Item(boxed.as_ref().clone())
1568                                        }
1569                                        ReferenceOr::Reference { reference } => {
1570                                            ReferenceOr::Reference {
1571                                                reference: reference.clone(),
1572                                            }
1573                                        }
1574                                    };
1575                                    // Extract count value from schema
1576                                    return Self::extract_numeric_value_from_schema(&schema_ref);
1577                                }
1578                            }
1579                        }
1580                    }
1581                }
1582            }
1583        }
1584
1585        None
1586    }
1587
1588    /// Generate example value for a property based on its name
1589    fn generate_example_for_property(prop_name: &str) -> Value {
1590        let prop_lower = prop_name.to_lowercase();
1591
1592        // Generate realistic data based on property name patterns
1593        if prop_lower.contains("id") || prop_lower.contains("uuid") {
1594            Value::String(uuid::Uuid::new_v4().to_string())
1595        } else if prop_lower.contains("email") {
1596            Value::String(format!("user{}@example.com", thread_rng().random_range(1000..=9999)))
1597        } else if prop_lower.contains("name") || prop_lower.contains("title") {
1598            let names = ["John Doe", "Jane Smith", "Bob Johnson", "Alice Brown"];
1599            Value::String(names[thread_rng().random_range(0..names.len())].to_string())
1600        } else if prop_lower.contains("phone") || prop_lower.contains("mobile") {
1601            Value::String(format!("+1-555-{:04}", thread_rng().random_range(1000..=9999)))
1602        } else if prop_lower.contains("address") || prop_lower.contains("street") {
1603            let streets = ["123 Main St", "456 Oak Ave", "789 Pine Rd", "321 Elm St"];
1604            Value::String(streets[thread_rng().random_range(0..streets.len())].to_string())
1605        } else if prop_lower.contains("city") {
1606            let cities = ["New York", "London", "Tokyo", "Paris", "Sydney"];
1607            Value::String(cities[thread_rng().random_range(0..cities.len())].to_string())
1608        } else if prop_lower.contains("country") {
1609            let countries = ["USA", "UK", "Japan", "France", "Australia"];
1610            Value::String(countries[thread_rng().random_range(0..countries.len())].to_string())
1611        } else if prop_lower.contains("company") || prop_lower.contains("organization") {
1612            let companies = ["Acme Corp", "Tech Solutions", "Global Inc", "Innovate Ltd"];
1613            Value::String(companies[thread_rng().random_range(0..companies.len())].to_string())
1614        } else if prop_lower.contains("url") || prop_lower.contains("website") {
1615            Value::String("https://example.com".to_string())
1616        } else if prop_lower.contains("age") {
1617            Value::Number((18 + thread_rng().random_range(0..60)).into())
1618        } else if prop_lower.contains("count") || prop_lower.contains("quantity") {
1619            Value::Number((1 + thread_rng().random_range(0..100)).into())
1620        } else if prop_lower.contains("price")
1621            || prop_lower.contains("amount")
1622            || prop_lower.contains("cost")
1623        {
1624            Value::Number(
1625                serde_json::Number::from_f64(
1626                    (thread_rng().random::<f64>() * 1000.0 * 100.0).round() / 100.0,
1627                )
1628                .unwrap(),
1629            )
1630        } else if prop_lower.contains("active")
1631            || prop_lower.contains("enabled")
1632            || prop_lower.contains("is_")
1633        {
1634            Value::Bool(thread_rng().random_bool(0.5))
1635        } else if prop_lower.contains("date") || prop_lower.contains("time") {
1636            Value::String(chrono::Utc::now().to_rfc3339())
1637        } else if prop_lower.contains("description") || prop_lower.contains("comment") {
1638            Value::String("This is a sample description text.".to_string())
1639        } else {
1640            Value::String(format!("example {}", prop_name))
1641        }
1642    }
1643
1644    /// Generate example responses from OpenAPI examples
1645    pub fn generate_from_examples(
1646        response: &Response,
1647        content_type: Option<&str>,
1648    ) -> Result<Option<Value>> {
1649        use openapiv3::ReferenceOr;
1650
1651        // If content_type is specified, look for examples in that media type
1652        if let Some(content_type) = content_type {
1653            if let Some(media_type) = response.content.get(content_type) {
1654                // Check for single example first
1655                if let Some(example) = &media_type.example {
1656                    return Ok(Some(example.clone()));
1657                }
1658
1659                // Check for multiple examples
1660                for (_, example_ref) in &media_type.examples {
1661                    if let ReferenceOr::Item(example) = example_ref {
1662                        if let Some(value) = &example.value {
1663                            return Ok(Some(value.clone()));
1664                        }
1665                    }
1666                    // Reference resolution would require spec parameter to be added to this function
1667                }
1668            }
1669        }
1670
1671        // If no content_type specified or not found, check all media types
1672        for (_, media_type) in &response.content {
1673            // Check for single example first
1674            if let Some(example) = &media_type.example {
1675                return Ok(Some(example.clone()));
1676            }
1677
1678            // Check for multiple examples
1679            for (_, example_ref) in &media_type.examples {
1680                if let ReferenceOr::Item(example) = example_ref {
1681                    if let Some(value) = &example.value {
1682                        return Ok(Some(value.clone()));
1683                    }
1684                }
1685                // Reference resolution would require spec parameter to be added to this function
1686            }
1687        }
1688
1689        Ok(None)
1690    }
1691
1692    /// Expand templates like {{now}} and {{uuid}} in JSON values
1693    fn expand_templates(value: &Value) -> Value {
1694        match value {
1695            Value::String(s) => {
1696                let expanded = s
1697                    .replace("{{now}}", &chrono::Utc::now().to_rfc3339())
1698                    .replace("{{uuid}}", &uuid::Uuid::new_v4().to_string());
1699                Value::String(expanded)
1700            }
1701            Value::Object(map) => {
1702                let mut new_map = serde_json::Map::new();
1703                for (key, val) in map {
1704                    new_map.insert(key.clone(), Self::expand_templates(val));
1705                }
1706                Value::Object(new_map)
1707            }
1708            Value::Array(arr) => {
1709                let new_arr: Vec<Value> = arr.iter().map(Self::expand_templates).collect();
1710                Value::Array(new_arr)
1711            }
1712            _ => value.clone(),
1713        }
1714    }
1715}
1716
1717#[cfg(test)]
1718mod tests {
1719    use super::*;
1720    use openapiv3::ReferenceOr;
1721    use serde_json::json;
1722
1723    // Mock AI generator for testing
1724    struct MockAiGenerator {
1725        response: Value,
1726    }
1727
1728    #[async_trait]
1729    impl AiGenerator for MockAiGenerator {
1730        async fn generate(&self, _prompt: &str, _config: &AiResponseConfig) -> Result<Value> {
1731            Ok(self.response.clone())
1732        }
1733    }
1734
1735    #[test]
1736    fn generates_example_using_referenced_schemas() {
1737        let yaml = r#"
1738openapi: 3.0.3
1739info:
1740  title: Test API
1741  version: "1.0.0"
1742paths:
1743  /apiaries:
1744    get:
1745      responses:
1746        '200':
1747          description: ok
1748          content:
1749            application/json:
1750              schema:
1751                $ref: '#/components/schemas/Apiary'
1752components:
1753  schemas:
1754    Apiary:
1755      type: object
1756      properties:
1757        id:
1758          type: string
1759        hive:
1760          $ref: '#/components/schemas/Hive'
1761    Hive:
1762      type: object
1763      properties:
1764        name:
1765          type: string
1766        active:
1767          type: boolean
1768        "#;
1769
1770        let spec = OpenApiSpec::from_string(yaml, Some("yaml")).expect("load spec");
1771        let path_item = spec
1772            .spec
1773            .paths
1774            .paths
1775            .get("/apiaries")
1776            .and_then(ReferenceOr::as_item)
1777            .expect("path item");
1778        let operation = path_item.get.as_ref().expect("GET operation");
1779
1780        let response =
1781            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1782                .expect("generate response");
1783
1784        let obj = response.as_object().expect("response object");
1785        assert!(obj.contains_key("id"));
1786        let hive = obj.get("hive").and_then(|value| value.as_object()).expect("hive object");
1787        assert!(hive.contains_key("name"));
1788        assert!(hive.contains_key("active"));
1789    }
1790
1791    #[tokio::test]
1792    async fn test_generate_ai_response_with_generator() {
1793        let ai_config = AiResponseConfig {
1794            enabled: true,
1795            mode: crate::ai_response::AiResponseMode::Intelligent,
1796            prompt: Some("Generate a response for {{method}} {{path}}".to_string()),
1797            context: None,
1798            temperature: 0.7,
1799            max_tokens: 1000,
1800            schema: None,
1801            cache_enabled: true,
1802        };
1803        let context = RequestContext {
1804            method: "GET".to_string(),
1805            path: "/api/users".to_string(),
1806            path_params: HashMap::new(),
1807            query_params: HashMap::new(),
1808            headers: HashMap::new(),
1809            body: None,
1810            multipart_fields: HashMap::new(),
1811            multipart_files: HashMap::new(),
1812        };
1813        let mock_generator = MockAiGenerator {
1814            response: json!({"message": "Generated response"}),
1815        };
1816
1817        let result =
1818            ResponseGenerator::generate_ai_response(&ai_config, &context, Some(&mock_generator))
1819                .await;
1820
1821        assert!(result.is_ok());
1822        let value = result.unwrap();
1823        assert_eq!(value["message"], "Generated response");
1824    }
1825
1826    #[tokio::test]
1827    async fn test_generate_ai_response_without_generator() {
1828        let ai_config = AiResponseConfig {
1829            enabled: true,
1830            mode: crate::ai_response::AiResponseMode::Intelligent,
1831            prompt: Some("Generate a response for {{method}} {{path}}".to_string()),
1832            context: None,
1833            temperature: 0.7,
1834            max_tokens: 1000,
1835            schema: None,
1836            cache_enabled: true,
1837        };
1838        let context = RequestContext {
1839            method: "POST".to_string(),
1840            path: "/api/users".to_string(),
1841            path_params: HashMap::new(),
1842            query_params: HashMap::new(),
1843            headers: HashMap::new(),
1844            body: None,
1845            multipart_fields: HashMap::new(),
1846            multipart_files: HashMap::new(),
1847        };
1848
1849        let result = ResponseGenerator::generate_ai_response(&ai_config, &context, None).await;
1850
1851        assert!(result.is_ok());
1852        let value = result.unwrap();
1853        assert_eq!(value["ai_response"], "AI generation placeholder");
1854        assert!(value["expanded_prompt"].as_str().unwrap().contains("POST"));
1855        assert!(value["expanded_prompt"].as_str().unwrap().contains("/api/users"));
1856    }
1857
1858    #[tokio::test]
1859    async fn test_generate_ai_response_no_prompt() {
1860        let ai_config = AiResponseConfig {
1861            enabled: true,
1862            mode: crate::ai_response::AiResponseMode::Intelligent,
1863            prompt: None,
1864            context: None,
1865            temperature: 0.7,
1866            max_tokens: 1000,
1867            schema: None,
1868            cache_enabled: true,
1869        };
1870        let context = RequestContext {
1871            method: "GET".to_string(),
1872            path: "/api/test".to_string(),
1873            path_params: HashMap::new(),
1874            query_params: HashMap::new(),
1875            headers: HashMap::new(),
1876            body: None,
1877            multipart_fields: HashMap::new(),
1878            multipart_files: HashMap::new(),
1879        };
1880
1881        let result = ResponseGenerator::generate_ai_response(&ai_config, &context, None).await;
1882
1883        assert!(result.is_err());
1884    }
1885
1886    #[test]
1887    fn test_generate_response_with_expansion() {
1888        let spec = OpenApiSpec::from_string(
1889            r#"openapi: 3.0.0
1890info:
1891  title: Test API
1892  version: 1.0.0
1893paths:
1894  /users:
1895    get:
1896      responses:
1897        '200':
1898          description: OK
1899          content:
1900            application/json:
1901              schema:
1902                type: object
1903                properties:
1904                  id:
1905                    type: integer
1906                  name:
1907                    type: string
1908"#,
1909            Some("yaml"),
1910        )
1911        .unwrap();
1912
1913        let operation = spec
1914            .spec
1915            .paths
1916            .paths
1917            .get("/users")
1918            .and_then(|p| p.as_item())
1919            .and_then(|p| p.get.as_ref())
1920            .unwrap();
1921
1922        let response = ResponseGenerator::generate_response_with_expansion(
1923            &spec,
1924            operation,
1925            200,
1926            Some("application/json"),
1927            true,
1928        )
1929        .unwrap();
1930
1931        assert!(response.is_object());
1932    }
1933
1934    #[test]
1935    fn test_generate_response_with_scenario() {
1936        let spec = OpenApiSpec::from_string(
1937            r#"openapi: 3.0.0
1938info:
1939  title: Test API
1940  version: 1.0.0
1941paths:
1942  /users:
1943    get:
1944      responses:
1945        '200':
1946          description: OK
1947          content:
1948            application/json:
1949              examples:
1950                happy:
1951                  value:
1952                    id: 1
1953                    name: "Happy User"
1954                sad:
1955                  value:
1956                    id: 2
1957                    name: "Sad User"
1958"#,
1959            Some("yaml"),
1960        )
1961        .unwrap();
1962
1963        let operation = spec
1964            .spec
1965            .paths
1966            .paths
1967            .get("/users")
1968            .and_then(|p| p.as_item())
1969            .and_then(|p| p.get.as_ref())
1970            .unwrap();
1971
1972        let response = ResponseGenerator::generate_response_with_scenario(
1973            &spec,
1974            operation,
1975            200,
1976            Some("application/json"),
1977            false,
1978            Some("happy"),
1979        )
1980        .unwrap();
1981
1982        assert_eq!(response["id"], 1);
1983        assert_eq!(response["name"], "Happy User");
1984    }
1985
1986    #[test]
1987    fn test_generate_response_with_referenced_response() {
1988        let spec = OpenApiSpec::from_string(
1989            r#"openapi: 3.0.0
1990info:
1991  title: Test API
1992  version: 1.0.0
1993paths:
1994  /users:
1995    get:
1996      responses:
1997        '200':
1998          $ref: '#/components/responses/UserResponse'
1999components:
2000  responses:
2001    UserResponse:
2002      description: User response
2003      content:
2004        application/json:
2005          schema:
2006            type: object
2007            properties:
2008              id:
2009                type: integer
2010              name:
2011                type: string
2012"#,
2013            Some("yaml"),
2014        )
2015        .unwrap();
2016
2017        let operation = spec
2018            .spec
2019            .paths
2020            .paths
2021            .get("/users")
2022            .and_then(|p| p.as_item())
2023            .and_then(|p| p.get.as_ref())
2024            .unwrap();
2025
2026        let response =
2027            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2028                .unwrap();
2029
2030        assert!(response.is_object());
2031    }
2032
2033    #[test]
2034    fn test_generate_response_with_default_status() {
2035        let spec = OpenApiSpec::from_string(
2036            r#"openapi: 3.0.0
2037info:
2038  title: Test API
2039  version: 1.0.0
2040paths:
2041  /users:
2042    get:
2043      responses:
2044        '200':
2045          description: OK
2046        default:
2047          description: Error
2048          content:
2049            application/json:
2050              schema:
2051                type: object
2052                properties:
2053                  error:
2054                    type: string
2055"#,
2056            Some("yaml"),
2057        )
2058        .unwrap();
2059
2060        let operation = spec
2061            .spec
2062            .paths
2063            .paths
2064            .get("/users")
2065            .and_then(|p| p.as_item())
2066            .and_then(|p| p.get.as_ref())
2067            .unwrap();
2068
2069        // Use default response for 500 status
2070        let response =
2071            ResponseGenerator::generate_response(&spec, operation, 500, Some("application/json"))
2072                .unwrap();
2073
2074        assert!(response.is_object());
2075    }
2076
2077    #[test]
2078    fn test_generate_response_with_example_in_media_type() {
2079        let spec = OpenApiSpec::from_string(
2080            r#"openapi: 3.0.0
2081info:
2082  title: Test API
2083  version: 1.0.0
2084paths:
2085  /users:
2086    get:
2087      responses:
2088        '200':
2089          description: OK
2090          content:
2091            application/json:
2092              example:
2093                id: 1
2094                name: "Example User"
2095"#,
2096            Some("yaml"),
2097        )
2098        .unwrap();
2099
2100        let operation = spec
2101            .spec
2102            .paths
2103            .paths
2104            .get("/users")
2105            .and_then(|p| p.as_item())
2106            .and_then(|p| p.get.as_ref())
2107            .unwrap();
2108
2109        let response =
2110            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2111                .unwrap();
2112
2113        assert_eq!(response["id"], 1);
2114        assert_eq!(response["name"], "Example User");
2115    }
2116
2117    #[test]
2118    fn test_generate_response_with_schema_example() {
2119        let spec = OpenApiSpec::from_string(
2120            r#"openapi: 3.0.0
2121info:
2122  title: Test API
2123  version: 1.0.0
2124paths:
2125  /users:
2126    get:
2127      responses:
2128        '200':
2129          description: OK
2130          content:
2131            application/json:
2132              schema:
2133                type: object
2134                example:
2135                  id: 42
2136                  name: "Schema Example"
2137                properties:
2138                  id:
2139                    type: integer
2140                  name:
2141                    type: string
2142"#,
2143            Some("yaml"),
2144        )
2145        .unwrap();
2146
2147        let operation = spec
2148            .spec
2149            .paths
2150            .paths
2151            .get("/users")
2152            .and_then(|p| p.as_item())
2153            .and_then(|p| p.get.as_ref())
2154            .unwrap();
2155
2156        let response =
2157            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2158                .unwrap();
2159
2160        // Should use schema example if available
2161        assert!(response.is_object());
2162    }
2163
2164    #[test]
2165    fn test_generate_response_with_referenced_schema() {
2166        let spec = OpenApiSpec::from_string(
2167            r#"openapi: 3.0.0
2168info:
2169  title: Test API
2170  version: 1.0.0
2171paths:
2172  /users:
2173    get:
2174      responses:
2175        '200':
2176          description: OK
2177          content:
2178            application/json:
2179              schema:
2180                $ref: '#/components/schemas/User'
2181components:
2182  schemas:
2183    User:
2184      type: object
2185      properties:
2186        id:
2187          type: integer
2188        name:
2189          type: string
2190"#,
2191            Some("yaml"),
2192        )
2193        .unwrap();
2194
2195        let operation = spec
2196            .spec
2197            .paths
2198            .paths
2199            .get("/users")
2200            .and_then(|p| p.as_item())
2201            .and_then(|p| p.get.as_ref())
2202            .unwrap();
2203
2204        let response =
2205            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2206                .unwrap();
2207
2208        assert!(response.is_object());
2209        assert!(response.get("id").is_some());
2210        assert!(response.get("name").is_some());
2211    }
2212
2213    #[test]
2214    fn test_generate_response_with_array_schema() {
2215        let spec = OpenApiSpec::from_string(
2216            r#"openapi: 3.0.0
2217info:
2218  title: Test API
2219  version: 1.0.0
2220paths:
2221  /users:
2222    get:
2223      responses:
2224        '200':
2225          description: OK
2226          content:
2227            application/json:
2228              schema:
2229                type: array
2230                items:
2231                  type: object
2232                  properties:
2233                    id:
2234                      type: integer
2235                    name:
2236                      type: string
2237"#,
2238            Some("yaml"),
2239        )
2240        .unwrap();
2241
2242        let operation = spec
2243            .spec
2244            .paths
2245            .paths
2246            .get("/users")
2247            .and_then(|p| p.as_item())
2248            .and_then(|p| p.get.as_ref())
2249            .unwrap();
2250
2251        let response =
2252            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2253                .unwrap();
2254
2255        assert!(response.is_array());
2256    }
2257
2258    #[test]
2259    fn test_generate_response_with_different_content_types() {
2260        let spec = OpenApiSpec::from_string(
2261            r#"openapi: 3.0.0
2262info:
2263  title: Test API
2264  version: 1.0.0
2265paths:
2266  /users:
2267    get:
2268      responses:
2269        '200':
2270          description: OK
2271          content:
2272            application/json:
2273              schema:
2274                type: object
2275            text/plain:
2276              schema:
2277                type: string
2278"#,
2279            Some("yaml"),
2280        )
2281        .unwrap();
2282
2283        let operation = spec
2284            .spec
2285            .paths
2286            .paths
2287            .get("/users")
2288            .and_then(|p| p.as_item())
2289            .and_then(|p| p.get.as_ref())
2290            .unwrap();
2291
2292        // Test JSON content type
2293        let json_response =
2294            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2295                .unwrap();
2296        assert!(json_response.is_object());
2297
2298        // Test text/plain content type
2299        let text_response =
2300            ResponseGenerator::generate_response(&spec, operation, 200, Some("text/plain"))
2301                .unwrap();
2302        assert!(text_response.is_string());
2303    }
2304
2305    #[test]
2306    fn test_generate_response_without_content_type() {
2307        let spec = OpenApiSpec::from_string(
2308            r#"openapi: 3.0.0
2309info:
2310  title: Test API
2311  version: 1.0.0
2312paths:
2313  /users:
2314    get:
2315      responses:
2316        '200':
2317          description: OK
2318          content:
2319            application/json:
2320              schema:
2321                type: object
2322                properties:
2323                  id:
2324                    type: integer
2325"#,
2326            Some("yaml"),
2327        )
2328        .unwrap();
2329
2330        let operation = spec
2331            .spec
2332            .paths
2333            .paths
2334            .get("/users")
2335            .and_then(|p| p.as_item())
2336            .and_then(|p| p.get.as_ref())
2337            .unwrap();
2338
2339        // No content type specified - should use first available
2340        let response = ResponseGenerator::generate_response(&spec, operation, 200, None).unwrap();
2341
2342        assert!(response.is_object());
2343    }
2344
2345    #[test]
2346    fn test_generate_response_with_no_content() {
2347        let spec = OpenApiSpec::from_string(
2348            r#"openapi: 3.0.0
2349info:
2350  title: Test API
2351  version: 1.0.0
2352paths:
2353  /users:
2354    delete:
2355      responses:
2356        '204':
2357          description: No Content
2358"#,
2359            Some("yaml"),
2360        )
2361        .unwrap();
2362
2363        let operation = spec
2364            .spec
2365            .paths
2366            .paths
2367            .get("/users")
2368            .and_then(|p| p.as_item())
2369            .and_then(|p| p.delete.as_ref())
2370            .unwrap();
2371
2372        let response = ResponseGenerator::generate_response(&spec, operation, 204, None).unwrap();
2373
2374        // Should return empty object for no content
2375        assert!(response.is_object());
2376        assert!(response.as_object().unwrap().is_empty());
2377    }
2378
2379    #[test]
2380    fn test_generate_response_with_expansion_disabled() {
2381        let spec = OpenApiSpec::from_string(
2382            r#"openapi: 3.0.0
2383info:
2384  title: Test API
2385  version: 1.0.0
2386paths:
2387  /users:
2388    get:
2389      responses:
2390        '200':
2391          description: OK
2392          content:
2393            application/json:
2394              schema:
2395                type: object
2396                properties:
2397                  id:
2398                    type: integer
2399                  name:
2400                    type: string
2401"#,
2402            Some("yaml"),
2403        )
2404        .unwrap();
2405
2406        let operation = spec
2407            .spec
2408            .paths
2409            .paths
2410            .get("/users")
2411            .and_then(|p| p.as_item())
2412            .and_then(|p| p.get.as_ref())
2413            .unwrap();
2414
2415        let response = ResponseGenerator::generate_response_with_expansion(
2416            &spec,
2417            operation,
2418            200,
2419            Some("application/json"),
2420            false, // No expansion
2421        )
2422        .unwrap();
2423
2424        assert!(response.is_object());
2425    }
2426
2427    #[test]
2428    fn test_generate_response_with_array_schema_referenced_items() {
2429        // Test array schema with referenced item schema (lines 1035-1046)
2430        let spec = OpenApiSpec::from_string(
2431            r#"openapi: 3.0.0
2432info:
2433  title: Test API
2434  version: 1.0.0
2435paths:
2436  /items:
2437    get:
2438      responses:
2439        '200':
2440          description: OK
2441          content:
2442            application/json:
2443              schema:
2444                type: array
2445                items:
2446                  $ref: '#/components/schemas/Item'
2447components:
2448  schemas:
2449    Item:
2450      type: object
2451      properties:
2452        id:
2453          type: string
2454        name:
2455          type: string
2456"#,
2457            Some("yaml"),
2458        )
2459        .unwrap();
2460
2461        let operation = spec
2462            .spec
2463            .paths
2464            .paths
2465            .get("/items")
2466            .and_then(|p| p.as_item())
2467            .and_then(|p| p.get.as_ref())
2468            .unwrap();
2469
2470        let response =
2471            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2472                .unwrap();
2473
2474        // Should generate an array with items from referenced schema
2475        let arr = response.as_array().expect("response should be array");
2476        assert!(!arr.is_empty());
2477        if let Some(item) = arr.first() {
2478            let obj = item.as_object().expect("item should be object");
2479            assert!(obj.contains_key("id") || obj.contains_key("name"));
2480        }
2481    }
2482
2483    #[test]
2484    fn test_generate_response_with_array_schema_missing_reference() {
2485        // Test array schema with missing referenced item schema (line 1045)
2486        let spec = OpenApiSpec::from_string(
2487            r#"openapi: 3.0.0
2488info:
2489  title: Test API
2490  version: 1.0.0
2491paths:
2492  /items:
2493    get:
2494      responses:
2495        '200':
2496          description: OK
2497          content:
2498            application/json:
2499              schema:
2500                type: array
2501                items:
2502                  $ref: '#/components/schemas/NonExistentItem'
2503components:
2504  schemas: {}
2505"#,
2506            Some("yaml"),
2507        )
2508        .unwrap();
2509
2510        let operation = spec
2511            .spec
2512            .paths
2513            .paths
2514            .get("/items")
2515            .and_then(|p| p.as_item())
2516            .and_then(|p| p.get.as_ref())
2517            .unwrap();
2518
2519        let response =
2520            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2521                .unwrap();
2522
2523        // Should generate an array with empty objects when reference not found
2524        let arr = response.as_array().expect("response should be array");
2525        assert!(!arr.is_empty());
2526    }
2527
2528    #[test]
2529    fn test_generate_response_with_array_example_and_pagination() {
2530        // Test array generation with pagination using example items (lines 1114-1126)
2531        let spec = OpenApiSpec::from_string(
2532            r#"openapi: 3.0.0
2533info:
2534  title: Test API
2535  version: 1.0.0
2536paths:
2537  /products:
2538    get:
2539      responses:
2540        '200':
2541          description: OK
2542          content:
2543            application/json:
2544              schema:
2545                type: array
2546                example: [{"id": 1, "name": "Product 1"}]
2547                items:
2548                  type: object
2549                  properties:
2550                    id:
2551                      type: integer
2552                    name:
2553                      type: string
2554"#,
2555            Some("yaml"),
2556        )
2557        .unwrap();
2558
2559        let operation = spec
2560            .spec
2561            .paths
2562            .paths
2563            .get("/products")
2564            .and_then(|p| p.as_item())
2565            .and_then(|p| p.get.as_ref())
2566            .unwrap();
2567
2568        let response =
2569            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2570                .unwrap();
2571
2572        // Should generate an array using the example as template
2573        let arr = response.as_array().expect("response should be array");
2574        assert!(!arr.is_empty());
2575        if let Some(item) = arr.first() {
2576            let obj = item.as_object().expect("item should be object");
2577            assert!(obj.contains_key("id") || obj.contains_key("name"));
2578        }
2579    }
2580
2581    #[test]
2582    fn test_generate_response_with_missing_response_reference() {
2583        // Test response generation with missing response reference (lines 294-298)
2584        let spec = OpenApiSpec::from_string(
2585            r#"openapi: 3.0.0
2586info:
2587  title: Test API
2588  version: 1.0.0
2589paths:
2590  /users:
2591    get:
2592      responses:
2593        '200':
2594          $ref: '#/components/responses/NonExistentResponse'
2595components:
2596  responses: {}
2597"#,
2598            Some("yaml"),
2599        )
2600        .unwrap();
2601
2602        let operation = spec
2603            .spec
2604            .paths
2605            .paths
2606            .get("/users")
2607            .and_then(|p| p.as_item())
2608            .and_then(|p| p.get.as_ref())
2609            .unwrap();
2610
2611        let response =
2612            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2613                .unwrap();
2614
2615        // Should return empty object when reference not found
2616        assert!(response.is_object());
2617        assert!(response.as_object().unwrap().is_empty());
2618    }
2619
2620    #[test]
2621    fn test_generate_response_with_no_response_for_status() {
2622        // Test response generation when no response found for status code (lines 302-310)
2623        let spec = OpenApiSpec::from_string(
2624            r#"openapi: 3.0.0
2625info:
2626  title: Test API
2627  version: 1.0.0
2628paths:
2629  /users:
2630    get:
2631      responses:
2632        '404':
2633          description: Not found
2634"#,
2635            Some("yaml"),
2636        )
2637        .unwrap();
2638
2639        let operation = spec
2640            .spec
2641            .paths
2642            .paths
2643            .get("/users")
2644            .and_then(|p| p.as_item())
2645            .and_then(|p| p.get.as_ref())
2646            .unwrap();
2647
2648        // Request status code 200 but only 404 is defined
2649        let response =
2650            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2651                .unwrap();
2652
2653        // Should return empty object when no response found
2654        assert!(response.is_object());
2655        assert!(response.as_object().unwrap().is_empty());
2656    }
2657}
2658
2659/// Mock response data
2660#[derive(Debug, Clone)]
2661pub struct MockResponse {
2662    /// HTTP status code
2663    pub status_code: u16,
2664    /// Response headers
2665    pub headers: HashMap<String, String>,
2666    /// Response body
2667    pub body: Option<Value>,
2668}
2669
2670impl MockResponse {
2671    /// Create a new mock response
2672    pub fn new(status_code: u16) -> Self {
2673        Self {
2674            status_code,
2675            headers: HashMap::new(),
2676            body: None,
2677        }
2678    }
2679
2680    /// Add a header to the response
2681    pub fn with_header(mut self, name: String, value: String) -> Self {
2682        self.headers.insert(name, value);
2683        self
2684    }
2685
2686    /// Set the response body
2687    pub fn with_body(mut self, body: Value) -> Self {
2688        self.body = Some(body);
2689        self
2690    }
2691}
2692
2693/// OpenAPI security requirement wrapper
2694#[derive(Debug, Clone)]
2695pub struct OpenApiSecurityRequirement {
2696    /// The security scheme name
2697    pub scheme: String,
2698    /// Required scopes (for OAuth2)
2699    pub scopes: Vec<String>,
2700}
2701
2702impl OpenApiSecurityRequirement {
2703    /// Create a new security requirement
2704    pub fn new(scheme: String, scopes: Vec<String>) -> Self {
2705        Self { scheme, scopes }
2706    }
2707}
2708
2709/// OpenAPI operation wrapper with path context
2710#[derive(Debug, Clone)]
2711pub struct OpenApiOperation {
2712    /// The HTTP method
2713    pub method: String,
2714    /// The path this operation belongs to
2715    pub path: String,
2716    /// The OpenAPI operation
2717    pub operation: openapiv3::Operation,
2718}
2719
2720impl OpenApiOperation {
2721    /// Create a new OpenApiOperation
2722    pub fn new(method: String, path: String, operation: openapiv3::Operation) -> Self {
2723        Self {
2724            method,
2725            path,
2726            operation,
2727        }
2728    }
2729}