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::{expand_prompt_template, AiResponseConfig, RequestContext},
9    OpenApiSpec, Result,
10};
11use async_trait::async_trait;
12use chrono;
13use openapiv3::{Operation, ReferenceOr, Response, Responses, Schema};
14use rand::{rng, Rng};
15use serde_json::Value;
16use std::collections::{BTreeMap, 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        let expanded_prompt = expand_prompt_template(prompt_template, context);
64
65        tracing::info!("AI response generation requested with prompt: {}", expanded_prompt);
66
67        // Use the provided generator if available
68        if let Some(gen) = generator {
69            tracing::debug!("Using provided AI generator for response");
70            return gen.generate(&expanded_prompt, ai_config).await;
71        }
72
73        // Fallback: return a descriptive placeholder if no generator is provided
74        tracing::warn!("No AI generator provided, returning placeholder response");
75        Ok(serde_json::json!({
76            "ai_response": "AI generation placeholder",
77            "note": "This endpoint is configured for AI-assisted responses, but no AI generator was provided",
78            "expanded_prompt": expanded_prompt,
79            "mode": format!("{:?}", ai_config.mode),
80            "temperature": ai_config.temperature,
81            "implementation_note": "Pass an AiGenerator implementation to ResponseGenerator::generate_ai_response to enable actual AI generation"
82        }))
83    }
84
85    /// Generate a mock response for an operation and status code
86    pub fn generate_response(
87        spec: &OpenApiSpec,
88        operation: &Operation,
89        status_code: u16,
90        content_type: Option<&str>,
91    ) -> Result<Value> {
92        Self::generate_response_with_expansion(spec, operation, status_code, content_type, true)
93    }
94
95    /// Generate a mock response for an operation and status code with token expansion control
96    pub fn generate_response_with_expansion(
97        spec: &OpenApiSpec,
98        operation: &Operation,
99        status_code: u16,
100        content_type: Option<&str>,
101        expand_tokens: bool,
102    ) -> Result<Value> {
103        Self::generate_response_with_expansion_and_mode(
104            spec,
105            operation,
106            status_code,
107            content_type,
108            expand_tokens,
109            None,
110            None,
111        )
112    }
113
114    /// Generate response with token expansion and selection mode
115    pub fn generate_response_with_expansion_and_mode(
116        spec: &OpenApiSpec,
117        operation: &Operation,
118        status_code: u16,
119        content_type: Option<&str>,
120        expand_tokens: bool,
121        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
122        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
123    ) -> Result<Value> {
124        Self::generate_response_with_expansion_and_mode_and_persona(
125            spec,
126            operation,
127            status_code,
128            content_type,
129            expand_tokens,
130            selection_mode,
131            selector,
132            None, // No persona by default
133        )
134    }
135
136    /// Generate response with token expansion, selection mode, and persona
137    pub fn generate_response_with_expansion_and_mode_and_persona(
138        spec: &OpenApiSpec,
139        operation: &Operation,
140        status_code: u16,
141        content_type: Option<&str>,
142        expand_tokens: bool,
143        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
144        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
145        persona: Option<&Persona>,
146    ) -> Result<Value> {
147        Self::generate_response_with_scenario_and_mode_and_persona(
148            spec,
149            operation,
150            status_code,
151            content_type,
152            expand_tokens,
153            None, // No scenario by default
154            selection_mode,
155            selector,
156            persona,
157        )
158    }
159
160    /// Generate a mock response with scenario support
161    ///
162    /// This method allows selection of specific example scenarios from the OpenAPI spec.
163    /// Scenarios are defined using the standard OpenAPI `examples` field (not the singular `example`).
164    ///
165    /// # Arguments
166    /// * `spec` - The OpenAPI specification
167    /// * `operation` - The operation to generate a response for
168    /// * `status_code` - The HTTP status code
169    /// * `content_type` - Optional content type (e.g., "application/json")
170    /// * `expand_tokens` - Whether to expand template tokens like {{now}} and {{uuid}}
171    /// * `scenario` - Optional scenario name to select from the examples map
172    ///
173    /// # Example
174    /// ```yaml
175    /// responses:
176    ///   '200':
177    ///     content:
178    ///       application/json:
179    ///         examples:
180    ///           happy:
181    ///             value: { "status": "success", "message": "All good!" }
182    ///           error:
183    ///             value: { "status": "error", "message": "Something went wrong" }
184    /// ```
185    pub fn generate_response_with_scenario(
186        spec: &OpenApiSpec,
187        operation: &Operation,
188        status_code: u16,
189        content_type: Option<&str>,
190        expand_tokens: bool,
191        scenario: Option<&str>,
192    ) -> Result<Value> {
193        Self::generate_response_with_scenario_and_mode(
194            spec,
195            operation,
196            status_code,
197            content_type,
198            expand_tokens,
199            scenario,
200            None,
201            None,
202        )
203    }
204
205    /// Generate response with scenario support and selection mode
206    pub fn generate_response_with_scenario_and_mode(
207        spec: &OpenApiSpec,
208        operation: &Operation,
209        status_code: u16,
210        content_type: Option<&str>,
211        expand_tokens: bool,
212        scenario: Option<&str>,
213        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
214        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
215    ) -> Result<Value> {
216        Self::generate_response_with_scenario_and_mode_and_persona(
217            spec,
218            operation,
219            status_code,
220            content_type,
221            expand_tokens,
222            scenario,
223            selection_mode,
224            selector,
225            None, // No persona by default
226        )
227    }
228
229    /// Generate response with scenario support, selection mode, and persona
230    pub fn generate_response_with_scenario_and_mode_and_persona(
231        spec: &OpenApiSpec,
232        operation: &Operation,
233        status_code: u16,
234        content_type: Option<&str>,
235        expand_tokens: bool,
236        scenario: Option<&str>,
237        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
238        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
239        persona: Option<&Persona>,
240    ) -> Result<Value> {
241        // Find the response for the status code
242        let response = Self::find_response_for_status(&operation.responses, status_code);
243
244        match response {
245            Some(response_ref) => {
246                match response_ref {
247                    ReferenceOr::Item(response) => {
248                        Self::generate_from_response_with_scenario_and_mode(
249                            spec,
250                            response,
251                            content_type,
252                            expand_tokens,
253                            scenario,
254                            selection_mode,
255                            selector,
256                        )
257                    }
258                    ReferenceOr::Reference { reference } => {
259                        // Resolve the reference
260                        if let Some(resolved_response) = spec.get_response(reference) {
261                            Self::generate_from_response_with_scenario_and_mode(
262                                spec,
263                                resolved_response,
264                                content_type,
265                                expand_tokens,
266                                scenario,
267                                selection_mode,
268                                selector,
269                            )
270                        } else {
271                            // Reference not found, return empty object
272                            Ok(Value::Object(serde_json::Map::new()))
273                        }
274                    }
275                }
276            }
277            None => {
278                // No response found for this status code
279                Ok(Value::Object(serde_json::Map::new()))
280            }
281        }
282    }
283
284    /// Find response for a given status code
285    fn find_response_for_status(
286        responses: &Responses,
287        status_code: u16,
288    ) -> Option<&ReferenceOr<Response>> {
289        // First try exact match
290        if let Some(response) = responses.responses.get(&openapiv3::StatusCode::Code(status_code)) {
291            return Some(response);
292        }
293
294        // Try default response
295        if let Some(default_response) = &responses.default {
296            return Some(default_response);
297        }
298
299        None
300    }
301
302    /// Generate response from a Response object
303    fn generate_from_response(
304        spec: &OpenApiSpec,
305        response: &Response,
306        content_type: Option<&str>,
307        expand_tokens: bool,
308    ) -> Result<Value> {
309        Self::generate_from_response_with_scenario(
310            spec,
311            response,
312            content_type,
313            expand_tokens,
314            None,
315        )
316    }
317
318    /// Generate response from a Response object with scenario support
319    fn generate_from_response_with_scenario(
320        spec: &OpenApiSpec,
321        response: &Response,
322        content_type: Option<&str>,
323        expand_tokens: bool,
324        scenario: Option<&str>,
325    ) -> Result<Value> {
326        Self::generate_from_response_with_scenario_and_mode(
327            spec,
328            response,
329            content_type,
330            expand_tokens,
331            scenario,
332            None,
333            None,
334        )
335    }
336
337    /// Generate response from a Response object with scenario support and selection mode
338    fn generate_from_response_with_scenario_and_mode(
339        spec: &OpenApiSpec,
340        response: &Response,
341        content_type: Option<&str>,
342        expand_tokens: bool,
343        scenario: Option<&str>,
344        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
345        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
346    ) -> Result<Value> {
347        Self::generate_from_response_with_scenario_and_mode_and_persona(
348            spec,
349            response,
350            content_type,
351            expand_tokens,
352            scenario,
353            selection_mode,
354            selector,
355            None, // No persona by default
356        )
357    }
358
359    /// Generate response from a Response object with scenario support, selection mode, and persona
360    fn generate_from_response_with_scenario_and_mode_and_persona(
361        spec: &OpenApiSpec,
362        response: &Response,
363        content_type: Option<&str>,
364        expand_tokens: bool,
365        scenario: Option<&str>,
366        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
367        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
368        persona: Option<&Persona>,
369    ) -> Result<Value> {
370        // If content_type is specified, look for that media type
371        if let Some(content_type) = content_type {
372            if let Some(media_type) = response.content.get(content_type) {
373                return Self::generate_from_media_type_with_scenario_and_mode_and_persona(
374                    spec,
375                    media_type,
376                    expand_tokens,
377                    scenario,
378                    selection_mode,
379                    selector,
380                    persona,
381                );
382            }
383        }
384
385        // If no content_type specified or not found, try common content types
386        let preferred_types = ["application/json", "application/xml", "text/plain"];
387
388        for content_type in &preferred_types {
389            if let Some(media_type) = response.content.get(*content_type) {
390                return Self::generate_from_media_type_with_scenario_and_mode_and_persona(
391                    spec,
392                    media_type,
393                    expand_tokens,
394                    scenario,
395                    selection_mode,
396                    selector,
397                    persona,
398                );
399            }
400        }
401
402        // If no suitable content type found, return the first available
403        if let Some((_, media_type)) = response.content.iter().next() {
404            return Self::generate_from_media_type_with_scenario_and_mode_and_persona(
405                spec,
406                media_type,
407                expand_tokens,
408                scenario,
409                selection_mode,
410                selector,
411                persona,
412            );
413        }
414
415        // No content found, return empty object
416        Ok(Value::Object(serde_json::Map::new()))
417    }
418
419    /// Generate response from a MediaType with optional scenario selection
420    fn generate_from_media_type(
421        spec: &OpenApiSpec,
422        media_type: &openapiv3::MediaType,
423        expand_tokens: bool,
424    ) -> Result<Value> {
425        Self::generate_from_media_type_with_scenario(spec, media_type, expand_tokens, None)
426    }
427
428    /// Generate response from a MediaType with scenario support and selection mode
429    fn generate_from_media_type_with_scenario(
430        spec: &OpenApiSpec,
431        media_type: &openapiv3::MediaType,
432        expand_tokens: bool,
433        scenario: Option<&str>,
434    ) -> Result<Value> {
435        Self::generate_from_media_type_with_scenario_and_mode(
436            spec,
437            media_type,
438            expand_tokens,
439            scenario,
440            None,
441            None,
442        )
443    }
444
445    /// Generate response from a MediaType with scenario support and selection mode
446    fn generate_from_media_type_with_scenario_and_mode(
447        spec: &OpenApiSpec,
448        media_type: &openapiv3::MediaType,
449        expand_tokens: bool,
450        scenario: Option<&str>,
451        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
452        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
453    ) -> Result<Value> {
454        Self::generate_from_media_type_with_scenario_and_mode_and_persona(
455            spec,
456            media_type,
457            expand_tokens,
458            scenario,
459            selection_mode,
460            selector,
461            None, // No persona by default
462        )
463    }
464
465    /// Generate response from a MediaType with scenario support, selection mode, and persona
466    fn generate_from_media_type_with_scenario_and_mode_and_persona(
467        spec: &OpenApiSpec,
468        media_type: &openapiv3::MediaType,
469        expand_tokens: bool,
470        scenario: Option<&str>,
471        selection_mode: Option<crate::openapi::response_selection::ResponseSelectionMode>,
472        selector: Option<&crate::openapi::response_selection::ResponseSelector>,
473        persona: Option<&Persona>,
474    ) -> Result<Value> {
475        // First, check if there's an explicit example
476        if let Some(example) = &media_type.example {
477            tracing::debug!("Using explicit example from media type: {:?}", example);
478            // Expand templates in the example if enabled
479            if expand_tokens {
480                let expanded_example = Self::expand_templates(example);
481                return Ok(expanded_example);
482            } else {
483                return Ok(example.clone());
484            }
485        }
486
487        // Then check examples map - with scenario support and selection modes
488        if !media_type.examples.is_empty() {
489            use crate::openapi::response_selection::{ResponseSelectionMode, ResponseSelector};
490
491            // If a scenario is specified, try to find it first
492            if let Some(scenario_name) = scenario {
493                if let Some(example_ref) = media_type.examples.get(scenario_name) {
494                    tracing::debug!("Using scenario '{}' from examples map", scenario_name);
495                    return Ok(Self::extract_example_value_with_persona(
496                        spec,
497                        example_ref,
498                        expand_tokens,
499                        persona,
500                        media_type.schema.as_ref(),
501                    )?);
502                } else {
503                    tracing::warn!(
504                        "Scenario '{}' not found in examples, falling back based on selection mode",
505                        scenario_name
506                    );
507                }
508            }
509
510            // Determine selection mode
511            let mode = selection_mode.unwrap_or(ResponseSelectionMode::First);
512
513            // Get list of example names for selection
514            let example_names: Vec<String> = media_type.examples.keys().cloned().collect();
515
516            if example_names.is_empty() {
517                // No examples available, fall back to schema
518            } else if mode == ResponseSelectionMode::Scenario && scenario.is_some() {
519                // Scenario mode was requested but scenario not found, fall through to selection mode
520            } else {
521                // Use selection mode to choose an example
522                let selected_index = if let Some(sel) = selector {
523                    sel.select(&example_names)
524                } else {
525                    // Create temporary selector for this selection
526                    let temp_selector = ResponseSelector::new(mode);
527                    temp_selector.select(&example_names)
528                };
529
530                if let Some(example_name) = example_names.get(selected_index) {
531                    if let Some(example_ref) = media_type.examples.get(example_name) {
532                        tracing::debug!(
533                            "Using example '{}' from examples map (mode: {:?}, index: {})",
534                            example_name,
535                            mode,
536                            selected_index
537                        );
538                        return Ok(Self::extract_example_value_with_persona(
539                            spec,
540                            example_ref,
541                            expand_tokens,
542                            persona,
543                            media_type.schema.as_ref(),
544                        )?);
545                    }
546                }
547            }
548
549            // Fall back to first example if selection failed
550            if let Some((example_name, example_ref)) = media_type.examples.iter().next() {
551                tracing::debug!(
552                    "Using first example '{}' from examples map as fallback",
553                    example_name
554                );
555                return Ok(Self::extract_example_value_with_persona(
556                    spec,
557                    example_ref,
558                    expand_tokens,
559                    persona,
560                    media_type.schema.as_ref(),
561                )?);
562            }
563        }
564
565        // Fall back to schema-based generation
566        // Pass persona through to schema generation for consistent data patterns
567        if let Some(schema_ref) = &media_type.schema {
568            Ok(Self::generate_example_from_schema_ref(spec, schema_ref, persona))
569        } else {
570            Ok(Value::Object(serde_json::Map::new()))
571        }
572    }
573
574    /// Extract value from an example reference
575    /// Optionally expands items arrays based on pagination metadata if persona is provided
576    fn extract_example_value(
577        spec: &OpenApiSpec,
578        example_ref: &ReferenceOr<openapiv3::Example>,
579        expand_tokens: bool,
580    ) -> Result<Value> {
581        Self::extract_example_value_with_persona(spec, example_ref, expand_tokens, None, None)
582    }
583
584    /// Extract value from an example reference with optional persona and schema for pagination expansion
585    fn extract_example_value_with_persona(
586        spec: &OpenApiSpec,
587        example_ref: &ReferenceOr<openapiv3::Example>,
588        expand_tokens: bool,
589        persona: Option<&Persona>,
590        schema_ref: Option<&ReferenceOr<Schema>>,
591    ) -> Result<Value> {
592        let mut value = match example_ref {
593            ReferenceOr::Item(example) => {
594                if let Some(v) = &example.value {
595                    tracing::debug!("Using example from examples map: {:?}", v);
596                    if expand_tokens {
597                        Self::expand_templates(v)
598                    } else {
599                        v.clone()
600                    }
601                } else {
602                    return Ok(Value::Object(serde_json::Map::new()));
603                }
604            }
605            ReferenceOr::Reference { reference } => {
606                // Resolve the example reference
607                if let Some(example) = spec.get_example(reference) {
608                    if let Some(v) = &example.value {
609                        tracing::debug!("Using resolved example reference: {:?}", v);
610                        if expand_tokens {
611                            Self::expand_templates(v)
612                        } else {
613                            v.clone()
614                        }
615                    } else {
616                        return Ok(Value::Object(serde_json::Map::new()));
617                    }
618                } else {
619                    tracing::warn!("Example reference '{}' not found", reference);
620                    return Ok(Value::Object(serde_json::Map::new()));
621                }
622            }
623        };
624
625        // Check for pagination mismatch and expand items array if needed
626        value = Self::expand_example_items_if_needed(spec, value, persona, schema_ref);
627
628        Ok(value)
629    }
630
631    /// Expand items array in example if pagination metadata suggests more items
632    /// Checks for common response structures: { data: { items: [...], total, limit } } or { items: [...], total, limit }
633    fn expand_example_items_if_needed(
634        spec: &OpenApiSpec,
635        mut example: Value,
636        persona: Option<&Persona>,
637        schema_ref: Option<&ReferenceOr<Schema>>,
638    ) -> Value {
639        // Try to find items array and pagination metadata in the example
640        // Support both nested (data.items) and flat (items) structures
641        let has_nested_items = example
642            .get("data")
643            .and_then(|v| v.as_object())
644            .map(|obj| obj.contains_key("items"))
645            .unwrap_or(false);
646
647        let has_flat_items = example.get("items").is_some();
648
649        if !has_nested_items && !has_flat_items {
650            return example; // No items array found
651        }
652
653        // Extract pagination metadata
654        let total = example
655            .get("data")
656            .and_then(|d| d.get("total"))
657            .or_else(|| example.get("total"))
658            .and_then(|v| v.as_u64().or_else(|| v.as_i64().map(|i| i as u64)));
659
660        let limit = example
661            .get("data")
662            .and_then(|d| d.get("limit"))
663            .or_else(|| example.get("limit"))
664            .and_then(|v| v.as_u64().or_else(|| v.as_i64().map(|i| i as u64)));
665
666        // Get current items array
667        let items_array = example
668            .get("data")
669            .and_then(|d| d.get("items"))
670            .or_else(|| example.get("items"))
671            .and_then(|v| v.as_array())
672            .cloned();
673
674        if let (Some(total_val), Some(limit_val), Some(mut items)) = (total, limit, items_array) {
675            let current_count = items.len() as u64;
676            let expected_count = std::cmp::min(total_val, limit_val);
677            let max_items = 100; // Cap at reasonable maximum
678            let expected_count = std::cmp::min(expected_count, max_items);
679
680            // If items array is smaller than expected, expand it
681            if current_count < expected_count && !items.is_empty() {
682                tracing::debug!(
683                    "Expanding example items array: {} -> {} items (total={}, limit={})",
684                    current_count,
685                    expected_count,
686                    total_val,
687                    limit_val
688                );
689
690                // Use first item as template
691                let template = items[0].clone();
692                let additional_count = expected_count - current_count;
693
694                // Generate additional items
695                for i in 0..additional_count {
696                    let mut new_item = template.clone();
697                    // Use the centralized variation function
698                    let item_index = current_count + i + 1;
699                    Self::add_item_variation(&mut new_item, item_index);
700                    items.push(new_item);
701                }
702
703                // Update the items array in the example
704                if let Some(data_obj) = example.get_mut("data").and_then(|v| v.as_object_mut()) {
705                    data_obj.insert("items".to_string(), Value::Array(items));
706                } else if let Some(root_obj) = example.as_object_mut() {
707                    root_obj.insert("items".to_string(), Value::Array(items));
708                }
709            }
710        }
711
712        example
713    }
714
715    fn generate_example_from_schema_ref(
716        spec: &OpenApiSpec,
717        schema_ref: &ReferenceOr<Schema>,
718        persona: Option<&Persona>,
719    ) -> Value {
720        match schema_ref {
721            ReferenceOr::Item(schema) => Self::generate_example_from_schema(spec, schema, persona),
722            ReferenceOr::Reference { reference } => spec
723                .get_schema(reference)
724                .map(|schema| Self::generate_example_from_schema(spec, &schema.schema, persona))
725                .unwrap_or_else(|| Value::Object(serde_json::Map::new())),
726        }
727    }
728
729    /// Generate example data from an OpenAPI schema
730    ///
731    /// Priority order:
732    /// 1. Schema-level example (schema.schema_data.example)
733    /// 2. Property-level examples when generating objects
734    /// 3. Generated values based on schema type
735    /// 4. Persona traits (if persona provided)
736    fn generate_example_from_schema(
737        spec: &OpenApiSpec,
738        schema: &Schema,
739        persona: Option<&Persona>,
740    ) -> Value {
741        // First, check for schema-level example in schema_data
742        // OpenAPI v3 stores examples in schema_data.example
743        if let Some(example) = schema.schema_data.example.as_ref() {
744            tracing::debug!("Using schema-level example: {:?}", example);
745            return example.clone();
746        }
747
748        // Note: schema-level example check happens at the top of the function (line 380-383)
749        // At this point, if we have a schema-level example, we've already returned it
750        // So we only generate defaults when no example exists
751        match &schema.schema_kind {
752            openapiv3::SchemaKind::Type(openapiv3::Type::String(_)) => {
753                // Use faker for string fields based on field name hints
754                Value::String("example string".to_string())
755            }
756            openapiv3::SchemaKind::Type(openapiv3::Type::Integer(_)) => Value::Number(42.into()),
757            openapiv3::SchemaKind::Type(openapiv3::Type::Number(_)) => {
758                Value::Number(serde_json::Number::from_f64(std::f64::consts::PI).unwrap())
759            }
760            openapiv3::SchemaKind::Type(openapiv3::Type::Boolean(_)) => Value::Bool(true),
761            openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) => {
762                // First pass: Scan for pagination metadata (total, page, limit)
763                // This helps us generate the correct number of array items
764                let mut pagination_metadata: Option<(u64, u64, u64)> = None; // (total, page, limit)
765
766                // Check if this looks like a paginated response by scanning properties
767                // Look for "items" array property and pagination fields
768                let has_items =
769                    obj.properties.iter().any(|(name, _)| name.to_lowercase() == "items");
770
771                if has_items {
772                    // Try to extract pagination metadata from schema properties
773                    let mut total_opt = None;
774                    let mut page_opt = None;
775                    let mut limit_opt = None;
776
777                    for (prop_name, prop_schema) in &obj.properties {
778                        let prop_lower = prop_name.to_lowercase();
779                        // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema> for extraction
780                        let schema_ref: ReferenceOr<Schema> = match prop_schema {
781                            ReferenceOr::Item(boxed) => ReferenceOr::Item(boxed.as_ref().clone()),
782                            ReferenceOr::Reference { reference } => ReferenceOr::Reference {
783                                reference: reference.clone(),
784                            },
785                        };
786                        if prop_lower == "total" || prop_lower == "count" || prop_lower == "size" {
787                            total_opt = Self::extract_numeric_value_from_schema(&schema_ref);
788                        } else if prop_lower == "page" {
789                            page_opt = Self::extract_numeric_value_from_schema(&schema_ref);
790                        } else if prop_lower == "limit" || prop_lower == "per_page" {
791                            limit_opt = Self::extract_numeric_value_from_schema(&schema_ref);
792                        }
793                    }
794
795                    // If we found a total, use it (with defaults for page/limit)
796                    if let Some(total) = total_opt {
797                        let page = page_opt.unwrap_or(1);
798                        let limit = limit_opt.unwrap_or(20);
799                        pagination_metadata = Some((total, page, limit));
800                        tracing::debug!(
801                            "Detected pagination metadata: total={}, page={}, limit={}",
802                            total,
803                            page,
804                            limit
805                        );
806                    } else {
807                        // Phase 3: If no total found in schema, try to infer from parent entity
808                        // Look for "items" array to determine child entity name
809                        if obj.properties.contains_key("items") {
810                            // Try to infer parent/child relationship from schema names
811                            // This is a heuristic: if we're generating a paginated response,
812                            // check if we can find a parent entity schema with a count field
813                            if let Some(inferred_total) =
814                                Self::try_infer_total_from_context(spec, obj)
815                            {
816                                let page = page_opt.unwrap_or(1);
817                                let limit = limit_opt.unwrap_or(20);
818                                pagination_metadata = Some((inferred_total, page, limit));
819                                tracing::debug!(
820                                    "Inferred pagination metadata from parent entity: total={}, page={}, limit={}",
821                                    inferred_total, page, limit
822                                );
823                            } else {
824                                // Phase 4: Try to use persona traits if available
825                                if let Some(persona) = persona {
826                                    // Look for count-related traits (e.g., "hive_count", "apiary_count")
827                                    // Try common patterns
828                                    let count_keys =
829                                        ["hive_count", "apiary_count", "item_count", "total_count"];
830                                    for key in &count_keys {
831                                        if let Some(count) = persona.get_numeric_trait(key) {
832                                            let page = page_opt.unwrap_or(1);
833                                            let limit = limit_opt.unwrap_or(20);
834                                            pagination_metadata = Some((count, page, limit));
835                                            tracing::debug!(
836                                                "Using persona trait '{}' for pagination: total={}, page={}, limit={}",
837                                                key, count, page, limit
838                                            );
839                                            break;
840                                        }
841                                    }
842                                }
843                            }
844                        }
845                    }
846                }
847
848                let mut map = serde_json::Map::new();
849                for (prop_name, prop_schema) in &obj.properties {
850                    let prop_lower = prop_name.to_lowercase();
851
852                    // Check if this is an array property that should use pagination metadata
853                    let is_items_array = prop_lower == "items" && pagination_metadata.is_some();
854
855                    let value = match prop_schema {
856                        ReferenceOr::Item(prop_schema) => {
857                            // If this is an items array with pagination metadata, always use generate_array_with_count
858                            // (it will use the example as a template if one exists)
859                            if is_items_array {
860                                // Generate array with count based on pagination metadata
861                                Self::generate_array_with_count(
862                                    spec,
863                                    prop_schema.as_ref(),
864                                    pagination_metadata.unwrap(),
865                                    persona,
866                                )
867                            } else if let Some(prop_example) =
868                                prop_schema.schema_data.example.as_ref()
869                            {
870                                // Check for property-level example (only if not items array)
871                                tracing::debug!(
872                                    "Using example for property '{}': {:?}",
873                                    prop_name,
874                                    prop_example
875                                );
876                                prop_example.clone()
877                            } else {
878                                Self::generate_example_from_schema(
879                                    spec,
880                                    prop_schema.as_ref(),
881                                    persona,
882                                )
883                            }
884                        }
885                        ReferenceOr::Reference { reference } => {
886                            // Try to resolve reference
887                            if let Some(resolved_schema) = spec.get_schema(reference) {
888                                // If this is an items array with pagination metadata, always use generate_array_with_count
889                                if is_items_array {
890                                    // Generate array with count based on pagination metadata
891                                    Self::generate_array_with_count(
892                                        spec,
893                                        &resolved_schema.schema,
894                                        pagination_metadata.unwrap(),
895                                        persona,
896                                    )
897                                } else if let Some(ref_example) =
898                                    resolved_schema.schema.schema_data.example.as_ref()
899                                {
900                                    // Check for example from referenced schema (only if not items array)
901                                    tracing::debug!(
902                                        "Using example from referenced schema '{}': {:?}",
903                                        reference,
904                                        ref_example
905                                    );
906                                    ref_example.clone()
907                                } else {
908                                    Self::generate_example_from_schema(
909                                        spec,
910                                        &resolved_schema.schema,
911                                        persona,
912                                    )
913                                }
914                            } else {
915                                Self::generate_example_for_property(prop_name)
916                            }
917                        }
918                    };
919                    let value = match value {
920                        Value::Null => Self::generate_example_for_property(prop_name),
921                        Value::Object(ref obj) if obj.is_empty() => {
922                            Self::generate_example_for_property(prop_name)
923                        }
924                        _ => value,
925                    };
926                    map.insert(prop_name.clone(), value);
927                }
928
929                // Ensure pagination metadata is set if we detected it
930                if let Some((total, page, limit)) = pagination_metadata {
931                    map.insert("total".to_string(), Value::Number(total.into()));
932                    map.insert("page".to_string(), Value::Number(page.into()));
933                    map.insert("limit".to_string(), Value::Number(limit.into()));
934                }
935
936                Value::Object(map)
937            }
938            openapiv3::SchemaKind::Type(openapiv3::Type::Array(arr)) => {
939                // Check for array-level example (schema.schema_data.example contains the full array)
940                // Note: This check is actually redundant since we check at the top,
941                // but keeping it here for clarity and defensive programming
942                // If the array schema itself has an example, it's already handled at the top
943
944                match &arr.items {
945                    Some(item_schema) => {
946                        let example_item = match item_schema {
947                            ReferenceOr::Item(item_schema) => {
948                                // Recursively generate example for array item
949                                // This will check for item-level examples
950                                Self::generate_example_from_schema(
951                                    spec,
952                                    item_schema.as_ref(),
953                                    persona,
954                                )
955                            }
956                            ReferenceOr::Reference { reference } => {
957                                // Try to resolve reference and generate example
958                                // This will check for examples in referenced schema
959                                if let Some(resolved_schema) = spec.get_schema(reference) {
960                                    Self::generate_example_from_schema(
961                                        spec,
962                                        &resolved_schema.schema,
963                                        persona,
964                                    )
965                                } else {
966                                    Value::Object(serde_json::Map::new())
967                                }
968                            }
969                        };
970                        Value::Array(vec![example_item])
971                    }
972                    None => Value::Array(vec![Value::String("item".to_string())]),
973                }
974            }
975            _ => Value::Object(serde_json::Map::new()),
976        }
977    }
978
979    /// Extract numeric value from a schema (from example or default)
980    /// Returns None if no numeric value can be extracted
981    fn extract_numeric_value_from_schema(schema_ref: &ReferenceOr<Schema>) -> Option<u64> {
982        match schema_ref {
983            ReferenceOr::Item(schema) => {
984                // Check for example value first
985                if let Some(example) = schema.schema_data.example.as_ref() {
986                    if let Some(num) = example.as_u64() {
987                        return Some(num);
988                    } else if let Some(num) = example.as_f64() {
989                        return Some(num as u64);
990                    }
991                }
992                // Check for default value
993                if let Some(default) = schema.schema_data.default.as_ref() {
994                    if let Some(num) = default.as_u64() {
995                        return Some(num);
996                    } else if let Some(num) = default.as_f64() {
997                        return Some(num as u64);
998                    }
999                }
1000                // For integer types, try to extract from schema constraints
1001                // Note: IntegerType doesn't have a default field in openapiv3
1002                // Defaults are stored in schema_data.default instead
1003                None
1004            }
1005            ReferenceOr::Reference { reference: _ } => {
1006                // For references, we'd need to resolve them, but for now return None
1007                // This can be enhanced later if needed
1008                None
1009            }
1010        }
1011    }
1012
1013    /// Generate an array with a specific count based on pagination metadata
1014    /// Respects the limit (e.g., if total=50 and limit=20, generates 20 items)
1015    fn generate_array_with_count(
1016        spec: &OpenApiSpec,
1017        array_schema: &Schema,
1018        pagination: (u64, u64, u64), // (total, page, limit)
1019        persona: Option<&Persona>,
1020    ) -> Value {
1021        let (total, _page, limit) = pagination;
1022
1023        // Determine how many items to generate
1024        // Respect pagination: generate min(total, limit) items
1025        let count = std::cmp::min(total, limit);
1026
1027        // Cap at reasonable maximum to avoid performance issues
1028        let max_items = 100;
1029        let count = std::cmp::min(count, max_items);
1030
1031        tracing::debug!("Generating array with count={} (total={}, limit={})", count, total, limit);
1032
1033        // Check if array schema has an example with items
1034        if let Some(example) = array_schema.schema_data.example.as_ref() {
1035            if let Some(example_array) = example.as_array() {
1036                if !example_array.is_empty() {
1037                    // Use first example item as template
1038                    let template_item = &example_array[0];
1039                    let items: Vec<Value> = (0..count)
1040                        .map(|i| {
1041                            // Clone template and add variation
1042                            let mut item = template_item.clone();
1043                            Self::add_item_variation(&mut item, i + 1);
1044                            item
1045                        })
1046                        .collect();
1047                    return Value::Array(items);
1048                }
1049            }
1050        }
1051
1052        // Generate items from schema
1053        if let openapiv3::SchemaKind::Type(openapiv3::Type::Array(arr)) = &array_schema.schema_kind
1054        {
1055            if let Some(item_schema) = &arr.items {
1056                let items: Vec<Value> = match item_schema {
1057                    ReferenceOr::Item(item_schema) => {
1058                        (0..count)
1059                            .map(|i| {
1060                                let mut item = Self::generate_example_from_schema(
1061                                    spec,
1062                                    item_schema.as_ref(),
1063                                    persona,
1064                                );
1065                                // Add variation to make items unique
1066                                Self::add_item_variation(&mut item, i + 1);
1067                                item
1068                            })
1069                            .collect()
1070                    }
1071                    ReferenceOr::Reference { reference } => {
1072                        if let Some(resolved_schema) = spec.get_schema(reference) {
1073                            (0..count)
1074                                .map(|i| {
1075                                    let mut item = Self::generate_example_from_schema(
1076                                        spec,
1077                                        &resolved_schema.schema,
1078                                        persona,
1079                                    );
1080                                    // Add variation to make items unique
1081                                    Self::add_item_variation(&mut item, i + 1);
1082                                    item
1083                                })
1084                                .collect()
1085                        } else {
1086                            vec![Value::Object(serde_json::Map::new()); count as usize]
1087                        }
1088                    }
1089                };
1090                return Value::Array(items);
1091            }
1092        }
1093
1094        // Fallback: generate simple items
1095        Value::Array((0..count).map(|i| Value::String(format!("item_{}", i + 1))).collect())
1096    }
1097
1098    /// Add variation to an item to make it unique (for array generation)
1099    /// Varies IDs, names, addresses, and coordinates based on item index
1100    fn add_item_variation(item: &mut Value, item_index: u64) {
1101        if let Some(obj) = item.as_object_mut() {
1102            // Update ID fields to be unique
1103            if let Some(id_val) = obj.get_mut("id") {
1104                if let Some(id_str) = id_val.as_str() {
1105                    // Extract base ID (remove any existing suffix)
1106                    let base_id = id_str.split('_').next().unwrap_or(id_str);
1107                    *id_val = Value::String(format!("{}_{:03}", base_id, item_index));
1108                } else if let Some(id_num) = id_val.as_u64() {
1109                    *id_val = Value::Number((id_num + item_index).into());
1110                }
1111            }
1112
1113            // Update name fields - add variation for all names
1114            if let Some(name_val) = obj.get_mut("name") {
1115                if let Some(name_str) = name_val.as_str() {
1116                    if name_str.contains('#') {
1117                        // Pattern like "Hive #1" -> "Hive #2"
1118                        *name_val = Value::String(format!("Hive #{}", item_index));
1119                    } else {
1120                        // Pattern like "Meadow Apiary" -> use rotation of varied names
1121                        // 60+ unique apiary names with geographic diversity for realistic demo
1122                        let apiary_names = [
1123                            // Midwest/Prairie names
1124                            "Meadow Apiary",
1125                            "Prairie Apiary",
1126                            "Sunset Valley Apiary",
1127                            "Golden Fields Apiary",
1128                            "Miller Family Apiary",
1129                            "Heartland Honey Co.",
1130                            "Cornfield Apiary",
1131                            "Harvest Moon Apiary",
1132                            "Prairie Winds Apiary",
1133                            "Amber Fields Apiary",
1134                            // California/Coastal names
1135                            "Coastal Apiary",
1136                            "Sunset Coast Apiary",
1137                            "Pacific Grove Apiary",
1138                            "Golden Gate Apiary",
1139                            "Napa Valley Apiary",
1140                            "Coastal Breeze Apiary",
1141                            "Pacific Heights Apiary",
1142                            "Bay Area Apiary",
1143                            "Sunset Valley Honey Co.",
1144                            "Coastal Harvest Apiary",
1145                            // Texas/Ranch names
1146                            "Lone Star Apiary",
1147                            "Texas Ranch Apiary",
1148                            "Big Sky Apiary",
1149                            "Prairie Rose Apiary",
1150                            "Hill Country Apiary",
1151                            "Lone Star Honey Co.",
1152                            "Texas Pride Apiary",
1153                            "Wildflower Ranch",
1154                            "Desert Bloom Apiary",
1155                            "Cactus Creek Apiary",
1156                            // Florida/Grove names
1157                            "Orange Grove Apiary",
1158                            "Citrus Grove Apiary",
1159                            "Palm Grove Apiary",
1160                            "Tropical Breeze Apiary",
1161                            "Everglades Apiary",
1162                            "Sunshine State Apiary",
1163                            "Florida Keys Apiary",
1164                            "Grove View Apiary",
1165                            "Tropical Harvest Apiary",
1166                            "Palm Coast Apiary",
1167                            // Northeast/Valley names
1168                            "Mountain View Apiary",
1169                            "Valley Apiary",
1170                            "Riverside Apiary",
1171                            "Hilltop Apiary",
1172                            "Forest Apiary",
1173                            "Mountain Apiary",
1174                            "Lakeside Apiary",
1175                            "Ridge Apiary",
1176                            "Brook Apiary",
1177                            "Hillside Apiary",
1178                            // Generic/Professional names
1179                            "Field Apiary",
1180                            "Creek Apiary",
1181                            "Woodland Apiary",
1182                            "Farm Apiary",
1183                            "Orchard Apiary",
1184                            "Pasture Apiary",
1185                            "Green Valley Apiary",
1186                            "Blue Sky Apiary",
1187                            "Sweet Honey Apiary",
1188                            "Nature's Best Apiary",
1189                            // Business/Commercial names
1190                            "Premium Honey Co.",
1191                            "Artisan Apiary",
1192                            "Heritage Apiary",
1193                            "Summit Apiary",
1194                            "Crystal Springs Apiary",
1195                            "Maple Grove Apiary",
1196                            "Wildflower Apiary",
1197                            "Thistle Apiary",
1198                            "Clover Field Apiary",
1199                            "Honeycomb Apiary",
1200                        ];
1201                        let name_index = (item_index - 1) as usize % apiary_names.len();
1202                        *name_val = Value::String(apiary_names[name_index].to_string());
1203                    }
1204                }
1205            }
1206
1207            // Update location/address fields
1208            if let Some(location_val) = obj.get_mut("location") {
1209                if let Some(location_obj) = location_val.as_object_mut() {
1210                    // Update address
1211                    if let Some(address_val) = location_obj.get_mut("address") {
1212                        if let Some(address_str) = address_val.as_str() {
1213                            // Extract street number if present, otherwise add variation
1214                            if let Some(num_str) = address_str.split_whitespace().next() {
1215                                if let Ok(num) = num_str.parse::<u64>() {
1216                                    *address_val =
1217                                        Value::String(format!("{} Farm Road", num + item_index));
1218                                } else {
1219                                    *address_val =
1220                                        Value::String(format!("{} Farm Road", 100 + item_index));
1221                                }
1222                            } else {
1223                                *address_val =
1224                                    Value::String(format!("{} Farm Road", 100 + item_index));
1225                            }
1226                        }
1227                    }
1228
1229                    // Vary coordinates slightly
1230                    if let Some(lat_val) = location_obj.get_mut("latitude") {
1231                        if let Some(lat) = lat_val.as_f64() {
1232                            *lat_val = Value::Number(
1233                                serde_json::Number::from_f64(lat + (item_index as f64 * 0.01))
1234                                    .unwrap(),
1235                            );
1236                        }
1237                    }
1238                    if let Some(lng_val) = location_obj.get_mut("longitude") {
1239                        if let Some(lng) = lng_val.as_f64() {
1240                            *lng_val = Value::Number(
1241                                serde_json::Number::from_f64(lng + (item_index as f64 * 0.01))
1242                                    .unwrap(),
1243                            );
1244                        }
1245                    }
1246                } else if let Some(address_str) = location_val.as_str() {
1247                    // Flat address string
1248                    if let Some(num_str) = address_str.split_whitespace().next() {
1249                        if let Ok(num) = num_str.parse::<u64>() {
1250                            *location_val =
1251                                Value::String(format!("{} Farm Road", num + item_index));
1252                        } else {
1253                            *location_val =
1254                                Value::String(format!("{} Farm Road", 100 + item_index));
1255                        }
1256                    }
1257                }
1258            }
1259
1260            // Update address field if it exists at root level
1261            if let Some(address_val) = obj.get_mut("address") {
1262                if let Some(address_str) = address_val.as_str() {
1263                    if let Some(num_str) = address_str.split_whitespace().next() {
1264                        if let Ok(num) = num_str.parse::<u64>() {
1265                            *address_val = Value::String(format!("{} Farm Road", num + item_index));
1266                        } else {
1267                            *address_val = Value::String(format!("{} Farm Road", 100 + item_index));
1268                        }
1269                    }
1270                }
1271            }
1272
1273            // Vary status fields (common enum values)
1274            if let Some(status_val) = obj.get_mut("status") {
1275                if let Some(status_str) = status_val.as_str() {
1276                    let statuses = [
1277                        "healthy",
1278                        "sick",
1279                        "needs_attention",
1280                        "quarantined",
1281                        "active",
1282                        "inactive",
1283                    ];
1284                    let status_index = (item_index - 1) as usize % statuses.len();
1285                    // Bias towards "healthy" and "active" (70% of items)
1286                    let final_status = if (item_index - 1) % 10 < 7 {
1287                        statuses[0] // "healthy" or "active"
1288                    } else {
1289                        statuses[status_index]
1290                    };
1291                    *status_val = Value::String(final_status.to_string());
1292                }
1293            }
1294
1295            // Vary hive_type fields
1296            if let Some(hive_type_val) = obj.get_mut("hive_type") {
1297                if let Some(_) = hive_type_val.as_str() {
1298                    let hive_types = ["langstroth", "top_bar", "warre", "flow_hive", "national"];
1299                    let type_index = (item_index - 1) as usize % hive_types.len();
1300                    *hive_type_val = Value::String(hive_types[type_index].to_string());
1301                }
1302            }
1303
1304            // Vary nested queen breed fields
1305            if let Some(queen_val) = obj.get_mut("queen") {
1306                if let Some(queen_obj) = queen_val.as_object_mut() {
1307                    if let Some(breed_val) = queen_obj.get_mut("breed") {
1308                        if let Some(_) = breed_val.as_str() {
1309                            let breeds =
1310                                ["italian", "carniolan", "russian", "buckfast", "caucasian"];
1311                            let breed_index = (item_index - 1) as usize % breeds.len();
1312                            *breed_val = Value::String(breeds[breed_index].to_string());
1313                        }
1314                    }
1315                    // Vary queen age
1316                    if let Some(age_val) = queen_obj.get_mut("age_days") {
1317                        if let Some(base_age) = age_val.as_u64() {
1318                            *age_val = Value::Number((base_age + (item_index * 10) % 200).into());
1319                        } else if let Some(base_age) = age_val.as_i64() {
1320                            *age_val =
1321                                Value::Number((base_age + (item_index as i64 * 10) % 200).into());
1322                        }
1323                    }
1324                    // Vary queen mark color
1325                    if let Some(color_val) = queen_obj.get_mut("mark_color") {
1326                        if let Some(_) = color_val.as_str() {
1327                            let colors = ["yellow", "white", "red", "green", "blue"];
1328                            let color_index = (item_index - 1) as usize % colors.len();
1329                            *color_val = Value::String(colors[color_index].to_string());
1330                        }
1331                    }
1332                }
1333            }
1334
1335            // Vary description fields if they exist
1336            if let Some(desc_val) = obj.get_mut("description") {
1337                if let Some(desc_str) = desc_val.as_str() {
1338                    let descriptions = [
1339                        "Production apiary",
1340                        "Research apiary",
1341                        "Commercial operation",
1342                        "Backyard apiary",
1343                        "Educational apiary",
1344                    ];
1345                    let desc_index = (item_index - 1) as usize % descriptions.len();
1346                    *desc_val = Value::String(descriptions[desc_index].to_string());
1347                }
1348            }
1349
1350            // Vary timestamp fields (created_at, updated_at, timestamp, date) for realistic time-series data
1351            // Generate timestamps spanning 12-24 months with proper distribution
1352            let timestamp_fields = [
1353                "created_at",
1354                "updated_at",
1355                "timestamp",
1356                "date",
1357                "forecastDate",
1358                "predictedDate",
1359            ];
1360            for field_name in &timestamp_fields {
1361                if let Some(timestamp_val) = obj.get_mut(*field_name) {
1362                    if let Some(_timestamp_str) = timestamp_val.as_str() {
1363                        // Generate realistic timestamp: distribute items over past 12-18 months
1364                        // Use item_index to create variation (not all same date)
1365                        let months_ago = 12 + ((item_index - 1) % 6); // Distribute over 6 months (12-18 months ago)
1366                        let days_offset = (item_index - 1) % 28; // Distribute within month (cap at 28)
1367                        let hours_offset = ((item_index * 7) % 24) as u8; // Distribute throughout day
1368                        let minutes_offset = ((item_index * 11) % 60) as u8; // Vary minutes
1369
1370                        // Calculate timestamp relative to current date (November 2024)
1371                        // Format: ISO 8601 (e.g., "2024-11-12T14:30:00Z")
1372                        let base_year = 2024;
1373                        let base_month = 11;
1374
1375                        // Calculate target month (going back in time)
1376                        let target_year = if months_ago >= base_month as u64 {
1377                            base_year - 1
1378                        } else {
1379                            base_year
1380                        };
1381                        let target_month = if months_ago >= base_month as u64 {
1382                            12 - (months_ago - base_month as u64) as u8
1383                        } else {
1384                            (base_month as u64 - months_ago) as u8
1385                        };
1386                        let target_day = std::cmp::min(28, 1 + days_offset as u8); // Start from day 1, cap at 28
1387
1388                        // Format as ISO 8601
1389                        let timestamp = format!(
1390                            "{:04}-{:02}-{:02}T{:02}:{:02}:00Z",
1391                            target_year, target_month, target_day, hours_offset, minutes_offset
1392                        );
1393                        *timestamp_val = Value::String(timestamp);
1394                    }
1395                }
1396            }
1397        }
1398    }
1399
1400    /// Try to infer total count from context (parent entity schemas)
1401    /// This is a heuristic that looks for common relationship patterns
1402    fn try_infer_total_from_context(
1403        spec: &OpenApiSpec,
1404        obj_type: &openapiv3::ObjectType,
1405    ) -> Option<u64> {
1406        // Look for "items" array to determine what we're generating
1407        if let Some(items_schema_ref) = obj_type.properties.get("items") {
1408            // Try to determine child entity name from items schema
1409            // This is a heuristic: check schema names in the spec
1410            if let Some(components) = &spec.spec.components {
1411                let schemas = &components.schemas;
1412                // Look through all schemas to find potential parent entities
1413                // that might have count fields matching the items type
1414                for (schema_name, schema_ref) in schemas {
1415                    if let ReferenceOr::Item(schema) = schema_ref {
1416                        if let openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) =
1417                            &schema.schema_kind
1418                        {
1419                            // Look for count fields that might match
1420                            for (prop_name, prop_schema) in &obj.properties {
1421                                let prop_lower = prop_name.to_lowercase();
1422                                if prop_lower.ends_with("_count") {
1423                                    // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema>
1424                                    let schema_ref: ReferenceOr<Schema> = match prop_schema {
1425                                        ReferenceOr::Item(boxed) => {
1426                                            ReferenceOr::Item(boxed.as_ref().clone())
1427                                        }
1428                                        ReferenceOr::Reference { reference } => {
1429                                            ReferenceOr::Reference {
1430                                                reference: reference.clone(),
1431                                            }
1432                                        }
1433                                    };
1434                                    // Found a count field, try to extract its value
1435                                    if let Some(count) =
1436                                        Self::extract_numeric_value_from_schema(&schema_ref)
1437                                    {
1438                                        // Use a reasonable default if count is very large
1439                                        if count > 0 && count <= 1000 {
1440                                            tracing::debug!(
1441                                                "Inferred count {} from parent schema {} field {}",
1442                                                count,
1443                                                schema_name,
1444                                                prop_name
1445                                            );
1446                                            return Some(count);
1447                                        }
1448                                    }
1449                                }
1450                            }
1451                        }
1452                    }
1453                }
1454            }
1455        }
1456
1457        None
1458    }
1459
1460    /// Infer relationship count from parent entity schema
1461    /// When generating a child entity list, check if parent entity has a count field
1462    fn infer_count_from_parent_schema(
1463        spec: &OpenApiSpec,
1464        parent_entity_name: &str,
1465        child_entity_name: &str,
1466    ) -> Option<u64> {
1467        // Look for parent entity schema
1468        let parent_schema_name = format!("{}", parent_entity_name);
1469        let count_field_name = format!("{}_count", child_entity_name);
1470
1471        // Try to find the schema
1472        if let Some(components) = &spec.spec.components {
1473            let schemas = &components.schemas;
1474            // Look for parent schema (case-insensitive)
1475            for (schema_name, schema_ref) in schemas {
1476                let schema_name_lower = schema_name.to_lowercase();
1477                if schema_name_lower.contains(&parent_entity_name.to_lowercase()) {
1478                    if let ReferenceOr::Item(schema) = schema_ref {
1479                        // Check if this schema has the count field
1480                        if let openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) =
1481                            &schema.schema_kind
1482                        {
1483                            for (prop_name, prop_schema) in &obj.properties {
1484                                if prop_name.to_lowercase() == count_field_name.to_lowercase() {
1485                                    // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema>
1486                                    let schema_ref: ReferenceOr<Schema> = match prop_schema {
1487                                        ReferenceOr::Item(boxed) => {
1488                                            ReferenceOr::Item(boxed.as_ref().clone())
1489                                        }
1490                                        ReferenceOr::Reference { reference } => {
1491                                            ReferenceOr::Reference {
1492                                                reference: reference.clone(),
1493                                            }
1494                                        }
1495                                    };
1496                                    // Extract count value from schema
1497                                    return Self::extract_numeric_value_from_schema(&schema_ref);
1498                                }
1499                            }
1500                        }
1501                    }
1502                }
1503            }
1504        }
1505
1506        None
1507    }
1508
1509    /// Generate example value for a property based on its name
1510    fn generate_example_for_property(prop_name: &str) -> Value {
1511        let prop_lower = prop_name.to_lowercase();
1512
1513        // Generate realistic data based on property name patterns
1514        if prop_lower.contains("id") || prop_lower.contains("uuid") {
1515            Value::String(uuid::Uuid::new_v4().to_string())
1516        } else if prop_lower.contains("email") {
1517            Value::String(format!("user{}@example.com", rng().random_range(1000..=9999)))
1518        } else if prop_lower.contains("name") || prop_lower.contains("title") {
1519            let names = ["John Doe", "Jane Smith", "Bob Johnson", "Alice Brown"];
1520            Value::String(names[rng().random_range(0..names.len())].to_string())
1521        } else if prop_lower.contains("phone") || prop_lower.contains("mobile") {
1522            Value::String(format!("+1-555-{:04}", rng().random_range(1000..=9999)))
1523        } else if prop_lower.contains("address") || prop_lower.contains("street") {
1524            let streets = ["123 Main St", "456 Oak Ave", "789 Pine Rd", "321 Elm St"];
1525            Value::String(streets[rng().random_range(0..streets.len())].to_string())
1526        } else if prop_lower.contains("city") {
1527            let cities = ["New York", "London", "Tokyo", "Paris", "Sydney"];
1528            Value::String(cities[rng().random_range(0..cities.len())].to_string())
1529        } else if prop_lower.contains("country") {
1530            let countries = ["USA", "UK", "Japan", "France", "Australia"];
1531            Value::String(countries[rng().random_range(0..countries.len())].to_string())
1532        } else if prop_lower.contains("company") || prop_lower.contains("organization") {
1533            let companies = ["Acme Corp", "Tech Solutions", "Global Inc", "Innovate Ltd"];
1534            Value::String(companies[rng().random_range(0..companies.len())].to_string())
1535        } else if prop_lower.contains("url") || prop_lower.contains("website") {
1536            Value::String("https://example.com".to_string())
1537        } else if prop_lower.contains("age") {
1538            Value::Number((18 + rng().random_range(0..60)).into())
1539        } else if prop_lower.contains("count") || prop_lower.contains("quantity") {
1540            Value::Number((1 + rng().random_range(0..100)).into())
1541        } else if prop_lower.contains("price")
1542            || prop_lower.contains("amount")
1543            || prop_lower.contains("cost")
1544        {
1545            Value::Number(
1546                serde_json::Number::from_f64(
1547                    (rng().random::<f64>() * 1000.0 * 100.0).round() / 100.0,
1548                )
1549                .unwrap(),
1550            )
1551        } else if prop_lower.contains("active")
1552            || prop_lower.contains("enabled")
1553            || prop_lower.contains("is_")
1554        {
1555            Value::Bool(rng().random_bool(0.5))
1556        } else if prop_lower.contains("date") || prop_lower.contains("time") {
1557            Value::String(chrono::Utc::now().to_rfc3339())
1558        } else if prop_lower.contains("description") || prop_lower.contains("comment") {
1559            Value::String("This is a sample description text.".to_string())
1560        } else {
1561            Value::String(format!("example {}", prop_name))
1562        }
1563    }
1564
1565    /// Generate example responses from OpenAPI examples
1566    pub fn generate_from_examples(
1567        response: &Response,
1568        content_type: Option<&str>,
1569    ) -> Result<Option<Value>> {
1570        use openapiv3::ReferenceOr;
1571
1572        // If content_type is specified, look for examples in that media type
1573        if let Some(content_type) = content_type {
1574            if let Some(media_type) = response.content.get(content_type) {
1575                // Check for single example first
1576                if let Some(example) = &media_type.example {
1577                    return Ok(Some(example.clone()));
1578                }
1579
1580                // Check for multiple examples
1581                for (_, example_ref) in &media_type.examples {
1582                    if let ReferenceOr::Item(example) = example_ref {
1583                        if let Some(value) = &example.value {
1584                            return Ok(Some(value.clone()));
1585                        }
1586                    }
1587                    // Reference resolution would require spec parameter to be added to this function
1588                }
1589            }
1590        }
1591
1592        // If no content_type specified or not found, check all media types
1593        for (_, media_type) in &response.content {
1594            // Check for single example first
1595            if let Some(example) = &media_type.example {
1596                return Ok(Some(example.clone()));
1597            }
1598
1599            // Check for multiple examples
1600            for (_, example_ref) in &media_type.examples {
1601                if let ReferenceOr::Item(example) = example_ref {
1602                    if let Some(value) = &example.value {
1603                        return Ok(Some(value.clone()));
1604                    }
1605                }
1606                // Reference resolution would require spec parameter to be added to this function
1607            }
1608        }
1609
1610        Ok(None)
1611    }
1612
1613    /// Expand templates like {{now}} and {{uuid}} in JSON values
1614    fn expand_templates(value: &Value) -> Value {
1615        match value {
1616            Value::String(s) => {
1617                let expanded = s
1618                    .replace("{{now}}", &chrono::Utc::now().to_rfc3339())
1619                    .replace("{{uuid}}", &uuid::Uuid::new_v4().to_string());
1620                Value::String(expanded)
1621            }
1622            Value::Object(map) => {
1623                let mut new_map = serde_json::Map::new();
1624                for (key, val) in map {
1625                    new_map.insert(key.clone(), Self::expand_templates(val));
1626                }
1627                Value::Object(new_map)
1628            }
1629            Value::Array(arr) => {
1630                let new_arr: Vec<Value> = arr.iter().map(Self::expand_templates).collect();
1631                Value::Array(new_arr)
1632            }
1633            _ => value.clone(),
1634        }
1635    }
1636}
1637
1638#[cfg(test)]
1639mod tests {
1640    use super::*;
1641    use openapiv3::ReferenceOr;
1642
1643    #[test]
1644    fn generates_example_using_referenced_schemas() {
1645        let yaml = r#"
1646openapi: 3.0.3
1647info:
1648  title: Test API
1649  version: "1.0.0"
1650paths:
1651  /apiaries:
1652    get:
1653      responses:
1654        '200':
1655          description: ok
1656          content:
1657            application/json:
1658              schema:
1659                $ref: '#/components/schemas/Apiary'
1660components:
1661  schemas:
1662    Apiary:
1663      type: object
1664      properties:
1665        id:
1666          type: string
1667        hive:
1668          $ref: '#/components/schemas/Hive'
1669    Hive:
1670      type: object
1671      properties:
1672        name:
1673          type: string
1674        active:
1675          type: boolean
1676        "#;
1677
1678        let spec = OpenApiSpec::from_string(yaml, Some("yaml")).expect("load spec");
1679        let path_item = spec
1680            .spec
1681            .paths
1682            .paths
1683            .get("/apiaries")
1684            .and_then(ReferenceOr::as_item)
1685            .expect("path item");
1686        let operation = path_item.get.as_ref().expect("GET operation");
1687
1688        let response =
1689            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1690                .expect("generate response");
1691
1692        let obj = response.as_object().expect("response object");
1693        assert!(obj.contains_key("id"));
1694        let hive = obj.get("hive").and_then(|value| value.as_object()).expect("hive object");
1695        assert!(hive.contains_key("name"));
1696        assert!(hive.contains_key("active"));
1697    }
1698}
1699
1700/// Mock response data
1701#[derive(Debug, Clone)]
1702pub struct MockResponse {
1703    /// HTTP status code
1704    pub status_code: u16,
1705    /// Response headers
1706    pub headers: HashMap<String, String>,
1707    /// Response body
1708    pub body: Option<Value>,
1709}
1710
1711impl MockResponse {
1712    /// Create a new mock response
1713    pub fn new(status_code: u16) -> Self {
1714        Self {
1715            status_code,
1716            headers: HashMap::new(),
1717            body: None,
1718        }
1719    }
1720
1721    /// Add a header to the response
1722    pub fn with_header(mut self, name: String, value: String) -> Self {
1723        self.headers.insert(name, value);
1724        self
1725    }
1726
1727    /// Set the response body
1728    pub fn with_body(mut self, body: Value) -> Self {
1729        self.body = Some(body);
1730        self
1731    }
1732}
1733
1734/// OpenAPI security requirement wrapper
1735#[derive(Debug, Clone)]
1736pub struct OpenApiSecurityRequirement {
1737    /// The security scheme name
1738    pub scheme: String,
1739    /// Required scopes (for OAuth2)
1740    pub scopes: Vec<String>,
1741}
1742
1743impl OpenApiSecurityRequirement {
1744    /// Create a new security requirement
1745    pub fn new(scheme: String, scopes: Vec<String>) -> Self {
1746        Self { scheme, scopes }
1747    }
1748}
1749
1750/// OpenAPI operation wrapper with path context
1751#[derive(Debug, Clone)]
1752pub struct OpenApiOperation {
1753    /// The HTTP method
1754    pub method: String,
1755    /// The path this operation belongs to
1756    pub path: String,
1757    /// The OpenAPI operation
1758    pub operation: openapiv3::Operation,
1759}
1760
1761impl OpenApiOperation {
1762    /// Create a new OpenApiOperation
1763    pub fn new(method: String, path: String, operation: openapiv3::Operation) -> Self {
1764        Self {
1765            method,
1766            path,
1767            operation,
1768        }
1769    }
1770}