Skip to main content

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