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        assert!(result.is_ok());
1863        let value = result.unwrap();
1864        assert_eq!(value["ai_response"], "AI generation placeholder");
1865        assert!(value["expanded_prompt"].as_str().unwrap().contains("POST"));
1866        assert!(value["expanded_prompt"].as_str().unwrap().contains("/api/users"));
1867    }
1868
1869    #[tokio::test]
1870    async fn test_generate_ai_response_no_prompt() {
1871        let ai_config = AiResponseConfig {
1872            enabled: true,
1873            mode: crate::ai_response::AiResponseMode::Intelligent,
1874            prompt: None,
1875            context: None,
1876            temperature: 0.7,
1877            max_tokens: 1000,
1878            schema: None,
1879            cache_enabled: true,
1880        };
1881        let context = RequestContext {
1882            method: "GET".to_string(),
1883            path: "/api/test".to_string(),
1884            path_params: HashMap::new(),
1885            query_params: HashMap::new(),
1886            headers: HashMap::new(),
1887            body: None,
1888            multipart_fields: HashMap::new(),
1889            multipart_files: HashMap::new(),
1890        };
1891
1892        let result = ResponseGenerator::generate_ai_response(&ai_config, &context, None).await;
1893
1894        assert!(result.is_err());
1895    }
1896
1897    #[test]
1898    fn test_generate_response_with_expansion() {
1899        let spec = OpenApiSpec::from_string(
1900            r#"openapi: 3.0.0
1901info:
1902  title: Test API
1903  version: 1.0.0
1904paths:
1905  /users:
1906    get:
1907      responses:
1908        '200':
1909          description: OK
1910          content:
1911            application/json:
1912              schema:
1913                type: object
1914                properties:
1915                  id:
1916                    type: integer
1917                  name:
1918                    type: string
1919"#,
1920            Some("yaml"),
1921        )
1922        .unwrap();
1923
1924        let operation = spec
1925            .spec
1926            .paths
1927            .paths
1928            .get("/users")
1929            .and_then(|p| p.as_item())
1930            .and_then(|p| p.get.as_ref())
1931            .unwrap();
1932
1933        let response = ResponseGenerator::generate_response_with_expansion(
1934            &spec,
1935            operation,
1936            200,
1937            Some("application/json"),
1938            true,
1939        )
1940        .unwrap();
1941
1942        assert!(response.is_object());
1943    }
1944
1945    #[test]
1946    fn test_generate_response_with_scenario() {
1947        let spec = OpenApiSpec::from_string(
1948            r#"openapi: 3.0.0
1949info:
1950  title: Test API
1951  version: 1.0.0
1952paths:
1953  /users:
1954    get:
1955      responses:
1956        '200':
1957          description: OK
1958          content:
1959            application/json:
1960              examples:
1961                happy:
1962                  value:
1963                    id: 1
1964                    name: "Happy User"
1965                sad:
1966                  value:
1967                    id: 2
1968                    name: "Sad User"
1969"#,
1970            Some("yaml"),
1971        )
1972        .unwrap();
1973
1974        let operation = spec
1975            .spec
1976            .paths
1977            .paths
1978            .get("/users")
1979            .and_then(|p| p.as_item())
1980            .and_then(|p| p.get.as_ref())
1981            .unwrap();
1982
1983        let response = ResponseGenerator::generate_response_with_scenario(
1984            &spec,
1985            operation,
1986            200,
1987            Some("application/json"),
1988            false,
1989            Some("happy"),
1990        )
1991        .unwrap();
1992
1993        assert_eq!(response["id"], 1);
1994        assert_eq!(response["name"], "Happy User");
1995    }
1996
1997    #[test]
1998    fn test_generate_response_with_referenced_response() {
1999        let spec = OpenApiSpec::from_string(
2000            r#"openapi: 3.0.0
2001info:
2002  title: Test API
2003  version: 1.0.0
2004paths:
2005  /users:
2006    get:
2007      responses:
2008        '200':
2009          $ref: '#/components/responses/UserResponse'
2010components:
2011  responses:
2012    UserResponse:
2013      description: User response
2014      content:
2015        application/json:
2016          schema:
2017            type: object
2018            properties:
2019              id:
2020                type: integer
2021              name:
2022                type: string
2023"#,
2024            Some("yaml"),
2025        )
2026        .unwrap();
2027
2028        let operation = spec
2029            .spec
2030            .paths
2031            .paths
2032            .get("/users")
2033            .and_then(|p| p.as_item())
2034            .and_then(|p| p.get.as_ref())
2035            .unwrap();
2036
2037        let response =
2038            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2039                .unwrap();
2040
2041        assert!(response.is_object());
2042    }
2043
2044    #[test]
2045    fn test_generate_response_with_default_status() {
2046        let spec = OpenApiSpec::from_string(
2047            r#"openapi: 3.0.0
2048info:
2049  title: Test API
2050  version: 1.0.0
2051paths:
2052  /users:
2053    get:
2054      responses:
2055        '200':
2056          description: OK
2057        default:
2058          description: Error
2059          content:
2060            application/json:
2061              schema:
2062                type: object
2063                properties:
2064                  error:
2065                    type: string
2066"#,
2067            Some("yaml"),
2068        )
2069        .unwrap();
2070
2071        let operation = spec
2072            .spec
2073            .paths
2074            .paths
2075            .get("/users")
2076            .and_then(|p| p.as_item())
2077            .and_then(|p| p.get.as_ref())
2078            .unwrap();
2079
2080        // Use default response for 500 status
2081        let response =
2082            ResponseGenerator::generate_response(&spec, operation, 500, Some("application/json"))
2083                .unwrap();
2084
2085        assert!(response.is_object());
2086    }
2087
2088    #[test]
2089    fn test_generate_response_with_example_in_media_type() {
2090        let spec = OpenApiSpec::from_string(
2091            r#"openapi: 3.0.0
2092info:
2093  title: Test API
2094  version: 1.0.0
2095paths:
2096  /users:
2097    get:
2098      responses:
2099        '200':
2100          description: OK
2101          content:
2102            application/json:
2103              example:
2104                id: 1
2105                name: "Example User"
2106"#,
2107            Some("yaml"),
2108        )
2109        .unwrap();
2110
2111        let operation = spec
2112            .spec
2113            .paths
2114            .paths
2115            .get("/users")
2116            .and_then(|p| p.as_item())
2117            .and_then(|p| p.get.as_ref())
2118            .unwrap();
2119
2120        let response =
2121            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2122                .unwrap();
2123
2124        assert_eq!(response["id"], 1);
2125        assert_eq!(response["name"], "Example User");
2126    }
2127
2128    #[test]
2129    fn test_generate_response_with_schema_example() {
2130        let spec = OpenApiSpec::from_string(
2131            r#"openapi: 3.0.0
2132info:
2133  title: Test API
2134  version: 1.0.0
2135paths:
2136  /users:
2137    get:
2138      responses:
2139        '200':
2140          description: OK
2141          content:
2142            application/json:
2143              schema:
2144                type: object
2145                example:
2146                  id: 42
2147                  name: "Schema Example"
2148                properties:
2149                  id:
2150                    type: integer
2151                  name:
2152                    type: string
2153"#,
2154            Some("yaml"),
2155        )
2156        .unwrap();
2157
2158        let operation = spec
2159            .spec
2160            .paths
2161            .paths
2162            .get("/users")
2163            .and_then(|p| p.as_item())
2164            .and_then(|p| p.get.as_ref())
2165            .unwrap();
2166
2167        let response =
2168            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2169                .unwrap();
2170
2171        // Should use schema example if available
2172        assert!(response.is_object());
2173    }
2174
2175    #[test]
2176    fn test_generate_response_with_referenced_schema() {
2177        let spec = OpenApiSpec::from_string(
2178            r#"openapi: 3.0.0
2179info:
2180  title: Test API
2181  version: 1.0.0
2182paths:
2183  /users:
2184    get:
2185      responses:
2186        '200':
2187          description: OK
2188          content:
2189            application/json:
2190              schema:
2191                $ref: '#/components/schemas/User'
2192components:
2193  schemas:
2194    User:
2195      type: object
2196      properties:
2197        id:
2198          type: integer
2199        name:
2200          type: string
2201"#,
2202            Some("yaml"),
2203        )
2204        .unwrap();
2205
2206        let operation = spec
2207            .spec
2208            .paths
2209            .paths
2210            .get("/users")
2211            .and_then(|p| p.as_item())
2212            .and_then(|p| p.get.as_ref())
2213            .unwrap();
2214
2215        let response =
2216            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2217                .unwrap();
2218
2219        assert!(response.is_object());
2220        assert!(response.get("id").is_some());
2221        assert!(response.get("name").is_some());
2222    }
2223
2224    #[test]
2225    fn test_generate_response_with_array_schema() {
2226        let spec = OpenApiSpec::from_string(
2227            r#"openapi: 3.0.0
2228info:
2229  title: Test API
2230  version: 1.0.0
2231paths:
2232  /users:
2233    get:
2234      responses:
2235        '200':
2236          description: OK
2237          content:
2238            application/json:
2239              schema:
2240                type: array
2241                items:
2242                  type: object
2243                  properties:
2244                    id:
2245                      type: integer
2246                    name:
2247                      type: string
2248"#,
2249            Some("yaml"),
2250        )
2251        .unwrap();
2252
2253        let operation = spec
2254            .spec
2255            .paths
2256            .paths
2257            .get("/users")
2258            .and_then(|p| p.as_item())
2259            .and_then(|p| p.get.as_ref())
2260            .unwrap();
2261
2262        let response =
2263            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2264                .unwrap();
2265
2266        assert!(response.is_array());
2267    }
2268
2269    #[test]
2270    fn test_generate_response_with_different_content_types() {
2271        let spec = OpenApiSpec::from_string(
2272            r#"openapi: 3.0.0
2273info:
2274  title: Test API
2275  version: 1.0.0
2276paths:
2277  /users:
2278    get:
2279      responses:
2280        '200':
2281          description: OK
2282          content:
2283            application/json:
2284              schema:
2285                type: object
2286            text/plain:
2287              schema:
2288                type: string
2289"#,
2290            Some("yaml"),
2291        )
2292        .unwrap();
2293
2294        let operation = spec
2295            .spec
2296            .paths
2297            .paths
2298            .get("/users")
2299            .and_then(|p| p.as_item())
2300            .and_then(|p| p.get.as_ref())
2301            .unwrap();
2302
2303        // Test JSON content type
2304        let json_response =
2305            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2306                .unwrap();
2307        assert!(json_response.is_object());
2308
2309        // Test text/plain content type
2310        let text_response =
2311            ResponseGenerator::generate_response(&spec, operation, 200, Some("text/plain"))
2312                .unwrap();
2313        assert!(text_response.is_string());
2314    }
2315
2316    #[test]
2317    fn test_generate_response_without_content_type() {
2318        let spec = OpenApiSpec::from_string(
2319            r#"openapi: 3.0.0
2320info:
2321  title: Test API
2322  version: 1.0.0
2323paths:
2324  /users:
2325    get:
2326      responses:
2327        '200':
2328          description: OK
2329          content:
2330            application/json:
2331              schema:
2332                type: object
2333                properties:
2334                  id:
2335                    type: integer
2336"#,
2337            Some("yaml"),
2338        )
2339        .unwrap();
2340
2341        let operation = spec
2342            .spec
2343            .paths
2344            .paths
2345            .get("/users")
2346            .and_then(|p| p.as_item())
2347            .and_then(|p| p.get.as_ref())
2348            .unwrap();
2349
2350        // No content type specified - should use first available
2351        let response = ResponseGenerator::generate_response(&spec, operation, 200, None).unwrap();
2352
2353        assert!(response.is_object());
2354    }
2355
2356    #[test]
2357    fn test_generate_response_with_no_content() {
2358        let spec = OpenApiSpec::from_string(
2359            r#"openapi: 3.0.0
2360info:
2361  title: Test API
2362  version: 1.0.0
2363paths:
2364  /users:
2365    delete:
2366      responses:
2367        '204':
2368          description: No Content
2369"#,
2370            Some("yaml"),
2371        )
2372        .unwrap();
2373
2374        let operation = spec
2375            .spec
2376            .paths
2377            .paths
2378            .get("/users")
2379            .and_then(|p| p.as_item())
2380            .and_then(|p| p.delete.as_ref())
2381            .unwrap();
2382
2383        let response = ResponseGenerator::generate_response(&spec, operation, 204, None).unwrap();
2384
2385        // Should return empty object for no content
2386        assert!(response.is_object());
2387        assert!(response.as_object().unwrap().is_empty());
2388    }
2389
2390    #[test]
2391    fn test_generate_response_with_expansion_disabled() {
2392        let spec = OpenApiSpec::from_string(
2393            r#"openapi: 3.0.0
2394info:
2395  title: Test API
2396  version: 1.0.0
2397paths:
2398  /users:
2399    get:
2400      responses:
2401        '200':
2402          description: OK
2403          content:
2404            application/json:
2405              schema:
2406                type: object
2407                properties:
2408                  id:
2409                    type: integer
2410                  name:
2411                    type: string
2412"#,
2413            Some("yaml"),
2414        )
2415        .unwrap();
2416
2417        let operation = spec
2418            .spec
2419            .paths
2420            .paths
2421            .get("/users")
2422            .and_then(|p| p.as_item())
2423            .and_then(|p| p.get.as_ref())
2424            .unwrap();
2425
2426        let response = ResponseGenerator::generate_response_with_expansion(
2427            &spec,
2428            operation,
2429            200,
2430            Some("application/json"),
2431            false, // No expansion
2432        )
2433        .unwrap();
2434
2435        assert!(response.is_object());
2436    }
2437
2438    #[test]
2439    fn test_generate_response_with_array_schema_referenced_items() {
2440        // Test array schema with referenced item schema (lines 1035-1046)
2441        let spec = OpenApiSpec::from_string(
2442            r#"openapi: 3.0.0
2443info:
2444  title: Test API
2445  version: 1.0.0
2446paths:
2447  /items:
2448    get:
2449      responses:
2450        '200':
2451          description: OK
2452          content:
2453            application/json:
2454              schema:
2455                type: array
2456                items:
2457                  $ref: '#/components/schemas/Item'
2458components:
2459  schemas:
2460    Item:
2461      type: object
2462      properties:
2463        id:
2464          type: string
2465        name:
2466          type: string
2467"#,
2468            Some("yaml"),
2469        )
2470        .unwrap();
2471
2472        let operation = spec
2473            .spec
2474            .paths
2475            .paths
2476            .get("/items")
2477            .and_then(|p| p.as_item())
2478            .and_then(|p| p.get.as_ref())
2479            .unwrap();
2480
2481        let response =
2482            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2483                .unwrap();
2484
2485        // Should generate an array with items from referenced schema
2486        let arr = response.as_array().expect("response should be array");
2487        assert!(!arr.is_empty());
2488        if let Some(item) = arr.first() {
2489            let obj = item.as_object().expect("item should be object");
2490            assert!(obj.contains_key("id") || obj.contains_key("name"));
2491        }
2492    }
2493
2494    #[test]
2495    fn test_generate_response_with_array_schema_missing_reference() {
2496        // Test array schema with missing referenced item schema (line 1045)
2497        let spec = OpenApiSpec::from_string(
2498            r#"openapi: 3.0.0
2499info:
2500  title: Test API
2501  version: 1.0.0
2502paths:
2503  /items:
2504    get:
2505      responses:
2506        '200':
2507          description: OK
2508          content:
2509            application/json:
2510              schema:
2511                type: array
2512                items:
2513                  $ref: '#/components/schemas/NonExistentItem'
2514components:
2515  schemas: {}
2516"#,
2517            Some("yaml"),
2518        )
2519        .unwrap();
2520
2521        let operation = spec
2522            .spec
2523            .paths
2524            .paths
2525            .get("/items")
2526            .and_then(|p| p.as_item())
2527            .and_then(|p| p.get.as_ref())
2528            .unwrap();
2529
2530        let response =
2531            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2532                .unwrap();
2533
2534        // Should generate an array with empty objects when reference not found
2535        let arr = response.as_array().expect("response should be array");
2536        assert!(!arr.is_empty());
2537    }
2538
2539    #[test]
2540    fn test_generate_response_with_array_example_and_pagination() {
2541        // Test array generation with pagination using example items (lines 1114-1126)
2542        let spec = OpenApiSpec::from_string(
2543            r#"openapi: 3.0.0
2544info:
2545  title: Test API
2546  version: 1.0.0
2547paths:
2548  /products:
2549    get:
2550      responses:
2551        '200':
2552          description: OK
2553          content:
2554            application/json:
2555              schema:
2556                type: array
2557                example: [{"id": 1, "name": "Product 1"}]
2558                items:
2559                  type: object
2560                  properties:
2561                    id:
2562                      type: integer
2563                    name:
2564                      type: string
2565"#,
2566            Some("yaml"),
2567        )
2568        .unwrap();
2569
2570        let operation = spec
2571            .spec
2572            .paths
2573            .paths
2574            .get("/products")
2575            .and_then(|p| p.as_item())
2576            .and_then(|p| p.get.as_ref())
2577            .unwrap();
2578
2579        let response =
2580            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2581                .unwrap();
2582
2583        // Should generate an array using the example as template
2584        let arr = response.as_array().expect("response should be array");
2585        assert!(!arr.is_empty());
2586        if let Some(item) = arr.first() {
2587            let obj = item.as_object().expect("item should be object");
2588            assert!(obj.contains_key("id") || obj.contains_key("name"));
2589        }
2590    }
2591
2592    #[test]
2593    fn test_generate_response_with_missing_response_reference() {
2594        // Test response generation with missing response reference (lines 294-298)
2595        let spec = OpenApiSpec::from_string(
2596            r#"openapi: 3.0.0
2597info:
2598  title: Test API
2599  version: 1.0.0
2600paths:
2601  /users:
2602    get:
2603      responses:
2604        '200':
2605          $ref: '#/components/responses/NonExistentResponse'
2606components:
2607  responses: {}
2608"#,
2609            Some("yaml"),
2610        )
2611        .unwrap();
2612
2613        let operation = spec
2614            .spec
2615            .paths
2616            .paths
2617            .get("/users")
2618            .and_then(|p| p.as_item())
2619            .and_then(|p| p.get.as_ref())
2620            .unwrap();
2621
2622        let response =
2623            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2624                .unwrap();
2625
2626        // Should return empty object when reference not found
2627        assert!(response.is_object());
2628        assert!(response.as_object().unwrap().is_empty());
2629    }
2630
2631    #[test]
2632    fn test_generate_response_with_no_response_for_status() {
2633        // Test response generation when no response found for status code (lines 302-310)
2634        let spec = OpenApiSpec::from_string(
2635            r#"openapi: 3.0.0
2636info:
2637  title: Test API
2638  version: 1.0.0
2639paths:
2640  /users:
2641    get:
2642      responses:
2643        '404':
2644          description: Not found
2645"#,
2646            Some("yaml"),
2647        )
2648        .unwrap();
2649
2650        let operation = spec
2651            .spec
2652            .paths
2653            .paths
2654            .get("/users")
2655            .and_then(|p| p.as_item())
2656            .and_then(|p| p.get.as_ref())
2657            .unwrap();
2658
2659        // Request status code 200 but only 404 is defined
2660        let response =
2661            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
2662                .unwrap();
2663
2664        // Should return empty object when no response found
2665        assert!(response.is_object());
2666        assert!(response.as_object().unwrap().is_empty());
2667    }
2668}
2669
2670/// Mock response data
2671#[derive(Debug, Clone)]
2672pub struct MockResponse {
2673    /// HTTP status code
2674    pub status_code: u16,
2675    /// Response headers
2676    pub headers: HashMap<String, String>,
2677    /// Response body
2678    pub body: Option<Value>,
2679}
2680
2681impl MockResponse {
2682    /// Create a new mock response
2683    pub fn new(status_code: u16) -> Self {
2684        Self {
2685            status_code,
2686            headers: HashMap::new(),
2687            body: None,
2688        }
2689    }
2690
2691    /// Add a header to the response
2692    pub fn with_header(mut self, name: String, value: String) -> Self {
2693        self.headers.insert(name, value);
2694        self
2695    }
2696
2697    /// Set the response body
2698    pub fn with_body(mut self, body: Value) -> Self {
2699        self.body = Some(body);
2700        self
2701    }
2702}
2703
2704/// OpenAPI security requirement wrapper
2705#[derive(Debug, Clone)]
2706pub struct OpenApiSecurityRequirement {
2707    /// The security scheme name
2708    pub scheme: String,
2709    /// Required scopes (for OAuth2)
2710    pub scopes: Vec<String>,
2711}
2712
2713impl OpenApiSecurityRequirement {
2714    /// Create a new security requirement
2715    pub fn new(scheme: String, scopes: Vec<String>) -> Self {
2716        Self { scheme, scopes }
2717    }
2718}
2719
2720/// OpenAPI operation wrapper with path context
2721#[derive(Debug, Clone)]
2722pub struct OpenApiOperation {
2723    /// The HTTP method
2724    pub method: String,
2725    /// The path this operation belongs to
2726    pub path: String,
2727    /// The OpenAPI operation
2728    pub operation: Operation,
2729}
2730
2731impl OpenApiOperation {
2732    /// Create a new OpenApiOperation
2733    pub fn new(method: String, path: String, operation: Operation) -> Self {
2734        Self {
2735            method,
2736            path,
2737            operation,
2738        }
2739    }
2740}