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