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::{
7    ai_response::{expand_prompt_template, AiResponseConfig, RequestContext},
8    OpenApiSpec, Result,
9};
10use crate::intelligent_behavior::config::Persona;
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.get("data")
642            .and_then(|v| v.as_object())
643            .map(|obj| obj.contains_key("items"))
644            .unwrap_or(false);
645
646        let has_flat_items = example.get("items").is_some();
647
648        if !has_nested_items && !has_flat_items {
649            return example; // No items array found
650        }
651
652        // Extract pagination metadata
653        let total = example.get("data")
654            .and_then(|d| d.get("total"))
655            .or_else(|| example.get("total"))
656            .and_then(|v| {
657                v.as_u64().or_else(|| v.as_i64().map(|i| i as u64))
658            });
659
660        let limit = example.get("data")
661            .and_then(|d| d.get("limit"))
662            .or_else(|| example.get("limit"))
663            .and_then(|v| {
664                v.as_u64().or_else(|| v.as_i64().map(|i| i as u64))
665            });
666
667        // Get current items array
668        let items_array = example.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(spec: &OpenApiSpec, schema: &Schema, persona: Option<&Persona>) -> Value {
737        // First, check for schema-level example in schema_data
738        // OpenAPI v3 stores examples in schema_data.example
739        if let Some(example) = schema.schema_data.example.as_ref() {
740            tracing::debug!("Using schema-level example: {:?}", example);
741            return example.clone();
742        }
743
744        // Note: schema-level example check happens at the top of the function (line 380-383)
745        // At this point, if we have a schema-level example, we've already returned it
746        // So we only generate defaults when no example exists
747        match &schema.schema_kind {
748            openapiv3::SchemaKind::Type(openapiv3::Type::String(_)) => {
749                // Use faker for string fields based on field name hints
750                Value::String("example string".to_string())
751            }
752            openapiv3::SchemaKind::Type(openapiv3::Type::Integer(_)) => Value::Number(42.into()),
753            openapiv3::SchemaKind::Type(openapiv3::Type::Number(_)) => {
754                Value::Number(serde_json::Number::from_f64(std::f64::consts::PI).unwrap())
755            }
756            openapiv3::SchemaKind::Type(openapiv3::Type::Boolean(_)) => Value::Bool(true),
757            openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) => {
758                // First pass: Scan for pagination metadata (total, page, limit)
759                // This helps us generate the correct number of array items
760                let mut pagination_metadata: Option<(u64, u64, u64)> = None; // (total, page, limit)
761
762                // Check if this looks like a paginated response by scanning properties
763                // Look for "items" array property and pagination fields
764                let has_items = obj.properties.iter()
765                    .any(|(name, _)| name.to_lowercase() == "items");
766
767                if has_items {
768                    // Try to extract pagination metadata from schema properties
769                    let mut total_opt = None;
770                    let mut page_opt = None;
771                    let mut limit_opt = None;
772
773                    for (prop_name, prop_schema) in &obj.properties {
774                        let prop_lower = prop_name.to_lowercase();
775                        // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema> for extraction
776                        let schema_ref: ReferenceOr<Schema> = match prop_schema {
777                            ReferenceOr::Item(boxed) => ReferenceOr::Item(boxed.as_ref().clone()),
778                            ReferenceOr::Reference { reference } => ReferenceOr::Reference { reference: reference.clone() },
779                        };
780                        if prop_lower == "total" || prop_lower == "count" || prop_lower == "size" {
781                            total_opt = Self::extract_numeric_value_from_schema(&schema_ref);
782                        } else if prop_lower == "page" {
783                            page_opt = Self::extract_numeric_value_from_schema(&schema_ref);
784                        } else if prop_lower == "limit" || prop_lower == "per_page" {
785                            limit_opt = Self::extract_numeric_value_from_schema(&schema_ref);
786                        }
787                    }
788
789                    // If we found a total, use it (with defaults for page/limit)
790                    if let Some(total) = total_opt {
791                        let page = page_opt.unwrap_or(1);
792                        let limit = limit_opt.unwrap_or(20);
793                        pagination_metadata = Some((total, page, limit));
794                        tracing::debug!(
795                            "Detected pagination metadata: total={}, page={}, limit={}",
796                            total, page, limit
797                        );
798                    } else {
799                        // Phase 3: If no total found in schema, try to infer from parent entity
800                        // Look for "items" array to determine child entity name
801                        if obj.properties.contains_key("items") {
802                            // Try to infer parent/child relationship from schema names
803                            // This is a heuristic: if we're generating a paginated response,
804                            // check if we can find a parent entity schema with a count field
805                            if let Some(inferred_total) = Self::try_infer_total_from_context(spec, obj) {
806                                let page = page_opt.unwrap_or(1);
807                                let limit = limit_opt.unwrap_or(20);
808                                pagination_metadata = Some((inferred_total, page, limit));
809                                tracing::debug!(
810                                    "Inferred pagination metadata from parent entity: total={}, page={}, limit={}",
811                                    inferred_total, page, limit
812                                );
813                            } else {
814                                // Phase 4: Try to use persona traits if available
815                                if let Some(persona) = persona {
816                                    // Look for count-related traits (e.g., "hive_count", "apiary_count")
817                                    // Try common patterns
818                                    let count_keys = ["hive_count", "apiary_count", "item_count", "total_count"];
819                                    for key in &count_keys {
820                                        if let Some(count) = persona.get_numeric_trait(key) {
821                                            let page = page_opt.unwrap_or(1);
822                                            let limit = limit_opt.unwrap_or(20);
823                                            pagination_metadata = Some((count, page, limit));
824                                            tracing::debug!(
825                                                "Using persona trait '{}' for pagination: total={}, page={}, limit={}",
826                                                key, count, page, limit
827                                            );
828                                            break;
829                                        }
830                                    }
831                                }
832                            }
833                        }
834                    }
835                }
836
837                let mut map = serde_json::Map::new();
838                for (prop_name, prop_schema) in &obj.properties {
839                    let prop_lower = prop_name.to_lowercase();
840
841                    // Check if this is an array property that should use pagination metadata
842                    let is_items_array = prop_lower == "items" && pagination_metadata.is_some();
843
844                    let value = match prop_schema {
845                        ReferenceOr::Item(prop_schema) => {
846                            // If this is an items array with pagination metadata, always use generate_array_with_count
847                            // (it will use the example as a template if one exists)
848                            if is_items_array {
849                                // Generate array with count based on pagination metadata
850                                Self::generate_array_with_count(
851                                    spec,
852                                    prop_schema.as_ref(),
853                                    pagination_metadata.unwrap(),
854                                    persona,
855                                )
856                            } else if let Some(prop_example) = prop_schema.schema_data.example.as_ref() {
857                                // Check for property-level example (only if not items array)
858                                tracing::debug!(
859                                    "Using example for property '{}': {:?}",
860                                    prop_name,
861                                    prop_example
862                                );
863                                prop_example.clone()
864                            } else {
865                                Self::generate_example_from_schema(spec, prop_schema.as_ref(), persona)
866                            }
867                        }
868                        ReferenceOr::Reference { reference } => {
869                            // Try to resolve reference
870                            if let Some(resolved_schema) = spec.get_schema(reference) {
871                                // If this is an items array with pagination metadata, always use generate_array_with_count
872                                if is_items_array {
873                                    // Generate array with count based on pagination metadata
874                                    Self::generate_array_with_count(
875                                        spec,
876                                        &resolved_schema.schema,
877                                        pagination_metadata.unwrap(),
878                                        persona,
879                                    )
880                                } else if let Some(ref_example) =
881                                    resolved_schema.schema.schema_data.example.as_ref()
882                                {
883                                    // Check for example from referenced schema (only if not items array)
884                                    tracing::debug!(
885                                        "Using example from referenced schema '{}': {:?}",
886                                        reference,
887                                        ref_example
888                                    );
889                                    ref_example.clone()
890                                } else {
891                                    Self::generate_example_from_schema(
892                                        spec,
893                                        &resolved_schema.schema,
894                                        persona,
895                                    )
896                                }
897                            } else {
898                                Self::generate_example_for_property(prop_name)
899                            }
900                        }
901                    };
902                    let value = match value {
903                        Value::Null => Self::generate_example_for_property(prop_name),
904                        Value::Object(ref obj) if obj.is_empty() => {
905                            Self::generate_example_for_property(prop_name)
906                        }
907                        _ => value,
908                    };
909                    map.insert(prop_name.clone(), value);
910                }
911
912                // Ensure pagination metadata is set if we detected it
913                if let Some((total, page, limit)) = pagination_metadata {
914                    map.insert("total".to_string(), Value::Number(total.into()));
915                    map.insert("page".to_string(), Value::Number(page.into()));
916                    map.insert("limit".to_string(), Value::Number(limit.into()));
917                }
918
919                Value::Object(map)
920            }
921            openapiv3::SchemaKind::Type(openapiv3::Type::Array(arr)) => {
922                // Check for array-level example (schema.schema_data.example contains the full array)
923                // Note: This check is actually redundant since we check at the top,
924                // but keeping it here for clarity and defensive programming
925                // If the array schema itself has an example, it's already handled at the top
926
927                match &arr.items {
928                    Some(item_schema) => {
929                        let example_item = match item_schema {
930                            ReferenceOr::Item(item_schema) => {
931                                // Recursively generate example for array item
932                                // This will check for item-level examples
933                                Self::generate_example_from_schema(spec, item_schema.as_ref(), persona)
934                            }
935                            ReferenceOr::Reference { reference } => {
936                                // Try to resolve reference and generate example
937                                // This will check for examples in referenced schema
938                                if let Some(resolved_schema) = spec.get_schema(reference) {
939                                    Self::generate_example_from_schema(
940                                        spec,
941                                        &resolved_schema.schema,
942                                        persona,
943                                    )
944                                } else {
945                                    Value::Object(serde_json::Map::new())
946                                }
947                            }
948                        };
949                        Value::Array(vec![example_item])
950                    }
951                    None => Value::Array(vec![Value::String("item".to_string())]),
952                }
953            }
954            _ => Value::Object(serde_json::Map::new()),
955        }
956    }
957
958    /// Extract numeric value from a schema (from example or default)
959    /// Returns None if no numeric value can be extracted
960    fn extract_numeric_value_from_schema(
961        schema_ref: &ReferenceOr<Schema>,
962    ) -> Option<u64> {
963        match schema_ref {
964            ReferenceOr::Item(schema) => {
965                // Check for example value first
966                if let Some(example) = schema.schema_data.example.as_ref() {
967                    if let Some(num) = example.as_u64() {
968                        return Some(num);
969                    } else if let Some(num) = example.as_f64() {
970                        return Some(num as u64);
971                    }
972                }
973                // Check for default value
974                if let Some(default) = schema.schema_data.default.as_ref() {
975                    if let Some(num) = default.as_u64() {
976                        return Some(num);
977                    } else if let Some(num) = default.as_f64() {
978                        return Some(num as u64);
979                    }
980                }
981                // For integer types, try to extract from schema constraints
982                // Note: IntegerType doesn't have a default field in openapiv3
983                // Defaults are stored in schema_data.default instead
984                None
985            }
986            ReferenceOr::Reference { reference: _ } => {
987                // For references, we'd need to resolve them, but for now return None
988                // This can be enhanced later if needed
989                None
990            }
991        }
992    }
993
994    /// Generate an array with a specific count based on pagination metadata
995    /// Respects the limit (e.g., if total=50 and limit=20, generates 20 items)
996    fn generate_array_with_count(
997        spec: &OpenApiSpec,
998        array_schema: &Schema,
999        pagination: (u64, u64, u64), // (total, page, limit)
1000        persona: Option<&Persona>,
1001    ) -> Value {
1002        let (total, _page, limit) = pagination;
1003
1004        // Determine how many items to generate
1005        // Respect pagination: generate min(total, limit) items
1006        let count = std::cmp::min(total, limit);
1007
1008        // Cap at reasonable maximum to avoid performance issues
1009        let max_items = 100;
1010        let count = std::cmp::min(count, max_items);
1011
1012        tracing::debug!(
1013            "Generating array with count={} (total={}, limit={})",
1014            count,
1015            total,
1016            limit
1017        );
1018
1019        // Check if array schema has an example with items
1020        if let Some(example) = array_schema.schema_data.example.as_ref() {
1021            if let Some(example_array) = example.as_array() {
1022                if !example_array.is_empty() {
1023                    // Use first example item as template
1024                    let template_item = &example_array[0];
1025                    let items: Vec<Value> = (0..count)
1026                        .map(|i| {
1027                            // Clone template and add variation
1028                            let mut item = template_item.clone();
1029                            Self::add_item_variation(&mut item, i + 1);
1030                            item
1031                        })
1032                        .collect();
1033                    return Value::Array(items);
1034                }
1035            }
1036        }
1037
1038        // Generate items from schema
1039        if let openapiv3::SchemaKind::Type(openapiv3::Type::Array(arr)) = &array_schema.schema_kind {
1040            if let Some(item_schema) = &arr.items {
1041                let items: Vec<Value> = match item_schema {
1042                    ReferenceOr::Item(item_schema) => {
1043                        (0..count)
1044                            .map(|i| {
1045                                let mut item = Self::generate_example_from_schema(spec, item_schema.as_ref(), persona);
1046                                // Add variation to make items unique
1047                                Self::add_item_variation(&mut item, i + 1);
1048                                item
1049                            })
1050                            .collect()
1051                    }
1052                    ReferenceOr::Reference { reference } => {
1053                        if let Some(resolved_schema) = spec.get_schema(reference) {
1054                            (0..count)
1055                                .map(|i| {
1056                                    let mut item = Self::generate_example_from_schema(
1057                                        spec,
1058                                        &resolved_schema.schema,
1059                                        persona,
1060                                    );
1061                                    // Add variation to make items unique
1062                                    Self::add_item_variation(&mut item, i + 1);
1063                                    item
1064                                })
1065                                .collect()
1066                        } else {
1067                            vec![Value::Object(serde_json::Map::new()); count as usize]
1068                        }
1069                    }
1070                };
1071                return Value::Array(items);
1072            }
1073        }
1074
1075        // Fallback: generate simple items
1076        Value::Array(
1077            (0..count)
1078                .map(|i| Value::String(format!("item_{}", i + 1)))
1079                .collect(),
1080        )
1081    }
1082
1083    /// Add variation to an item to make it unique (for array generation)
1084    /// Varies IDs, names, addresses, and coordinates based on item index
1085    fn add_item_variation(item: &mut Value, item_index: u64) {
1086        if let Some(obj) = item.as_object_mut() {
1087            // Update ID fields to be unique
1088            if let Some(id_val) = obj.get_mut("id") {
1089                if let Some(id_str) = id_val.as_str() {
1090                    // Extract base ID (remove any existing suffix)
1091                    let base_id = id_str.split('_').next().unwrap_or(id_str);
1092                    *id_val = Value::String(format!("{}_{:03}", base_id, item_index));
1093                } else if let Some(id_num) = id_val.as_u64() {
1094                    *id_val = Value::Number((id_num + item_index).into());
1095                }
1096            }
1097
1098            // Update name fields - add variation for all names
1099            if let Some(name_val) = obj.get_mut("name") {
1100                if let Some(name_str) = name_val.as_str() {
1101                    if name_str.contains('#') {
1102                        // Pattern like "Hive #1" -> "Hive #2"
1103                        *name_val = Value::String(format!("Hive #{}", item_index));
1104                    } else {
1105                        // Pattern like "Meadow Apiary" -> use rotation of varied names
1106                        // 60+ unique apiary names with geographic diversity for realistic demo
1107                        let apiary_names = [
1108                            // Midwest/Prairie names
1109                            "Meadow Apiary", "Prairie Apiary", "Sunset Valley Apiary", "Golden Fields Apiary",
1110                            "Miller Family Apiary", "Heartland Honey Co.", "Cornfield Apiary", "Harvest Moon Apiary",
1111                            "Prairie Winds Apiary", "Amber Fields Apiary",
1112                            // California/Coastal names
1113                            "Coastal Apiary", "Sunset Coast Apiary", "Pacific Grove Apiary", "Golden Gate Apiary",
1114                            "Napa Valley Apiary", "Coastal Breeze Apiary", "Pacific Heights Apiary", "Bay Area Apiary",
1115                            "Sunset Valley Honey Co.", "Coastal Harvest Apiary",
1116                            // Texas/Ranch names
1117                            "Lone Star Apiary", "Texas Ranch Apiary", "Big Sky Apiary", "Prairie Rose Apiary",
1118                            "Hill Country Apiary", "Lone Star Honey Co.", "Texas Pride Apiary", "Wildflower Ranch",
1119                            "Desert Bloom Apiary", "Cactus Creek Apiary",
1120                            // Florida/Grove names
1121                            "Orange Grove Apiary", "Citrus Grove Apiary", "Palm Grove Apiary", "Tropical Breeze Apiary",
1122                            "Everglades Apiary", "Sunshine State Apiary", "Florida Keys Apiary", "Grove View Apiary",
1123                            "Tropical Harvest Apiary", "Palm Coast Apiary",
1124                            // Northeast/Valley names
1125                            "Mountain View Apiary", "Valley Apiary", "Riverside Apiary", "Hilltop Apiary",
1126                            "Forest Apiary", "Mountain Apiary", "Lakeside Apiary", "Ridge Apiary",
1127                            "Brook Apiary", "Hillside Apiary",
1128                            // Generic/Professional names
1129                            "Field Apiary", "Creek Apiary", "Woodland Apiary", "Farm Apiary",
1130                            "Orchard Apiary", "Pasture Apiary", "Green Valley Apiary", "Blue Sky Apiary",
1131                            "Sweet Honey Apiary", "Nature's Best Apiary",
1132                            // Business/Commercial names
1133                            "Premium Honey Co.", "Artisan Apiary", "Heritage Apiary", "Summit Apiary",
1134                            "Crystal Springs Apiary", "Maple Grove Apiary", "Wildflower Apiary", "Thistle Apiary",
1135                            "Clover Field Apiary", "Honeycomb Apiary"
1136                        ];
1137                        let name_index = (item_index - 1) as usize % apiary_names.len();
1138                        *name_val = Value::String(apiary_names[name_index].to_string());
1139                    }
1140                }
1141            }
1142
1143            // Update location/address fields
1144            if let Some(location_val) = obj.get_mut("location") {
1145                if let Some(location_obj) = location_val.as_object_mut() {
1146                    // Update address
1147                    if let Some(address_val) = location_obj.get_mut("address") {
1148                        if let Some(address_str) = address_val.as_str() {
1149                            // Extract street number if present, otherwise add variation
1150                            if let Some(num_str) = address_str.split_whitespace().next() {
1151                                if let Ok(num) = num_str.parse::<u64>() {
1152                                    *address_val = Value::String(format!("{} Farm Road", num + item_index));
1153                                } else {
1154                                    *address_val = Value::String(format!("{} Farm Road", 100 + item_index));
1155                                }
1156                            } else {
1157                                *address_val = Value::String(format!("{} Farm Road", 100 + item_index));
1158                            }
1159                        }
1160                    }
1161
1162                    // Vary coordinates slightly
1163                    if let Some(lat_val) = location_obj.get_mut("latitude") {
1164                        if let Some(lat) = lat_val.as_f64() {
1165                            *lat_val = Value::Number(serde_json::Number::from_f64(lat + (item_index as f64 * 0.01)).unwrap());
1166                        }
1167                    }
1168                    if let Some(lng_val) = location_obj.get_mut("longitude") {
1169                        if let Some(lng) = lng_val.as_f64() {
1170                            *lng_val = Value::Number(serde_json::Number::from_f64(lng + (item_index as f64 * 0.01)).unwrap());
1171                        }
1172                    }
1173                } else if let Some(address_str) = location_val.as_str() {
1174                    // Flat address string
1175                    if let Some(num_str) = address_str.split_whitespace().next() {
1176                        if let Ok(num) = num_str.parse::<u64>() {
1177                            *location_val = Value::String(format!("{} Farm Road", num + item_index));
1178                        } else {
1179                            *location_val = Value::String(format!("{} Farm Road", 100 + item_index));
1180                        }
1181                    }
1182                }
1183            }
1184
1185            // Update address field if it exists at root level
1186            if let Some(address_val) = obj.get_mut("address") {
1187                if let Some(address_str) = address_val.as_str() {
1188                    if let Some(num_str) = address_str.split_whitespace().next() {
1189                        if let Ok(num) = num_str.parse::<u64>() {
1190                            *address_val = Value::String(format!("{} Farm Road", num + item_index));
1191                        } else {
1192                            *address_val = Value::String(format!("{} Farm Road", 100 + item_index));
1193                        }
1194                    }
1195                }
1196            }
1197
1198            // Vary status fields (common enum values)
1199            if let Some(status_val) = obj.get_mut("status") {
1200                if let Some(status_str) = status_val.as_str() {
1201                    let statuses = ["healthy", "sick", "needs_attention", "quarantined", "active", "inactive"];
1202                    let status_index = (item_index - 1) as usize % statuses.len();
1203                    // Bias towards "healthy" and "active" (70% of items)
1204                    let final_status = if (item_index - 1) % 10 < 7 {
1205                        statuses[0] // "healthy" or "active"
1206                    } else {
1207                        statuses[status_index]
1208                    };
1209                    *status_val = Value::String(final_status.to_string());
1210                }
1211            }
1212
1213            // Vary hive_type fields
1214            if let Some(hive_type_val) = obj.get_mut("hive_type") {
1215                if let Some(_) = hive_type_val.as_str() {
1216                    let hive_types = ["langstroth", "top_bar", "warre", "flow_hive", "national"];
1217                    let type_index = (item_index - 1) as usize % hive_types.len();
1218                    *hive_type_val = Value::String(hive_types[type_index].to_string());
1219                }
1220            }
1221
1222            // Vary nested queen breed fields
1223            if let Some(queen_val) = obj.get_mut("queen") {
1224                if let Some(queen_obj) = queen_val.as_object_mut() {
1225                    if let Some(breed_val) = queen_obj.get_mut("breed") {
1226                        if let Some(_) = breed_val.as_str() {
1227                            let breeds = ["italian", "carniolan", "russian", "buckfast", "caucasian"];
1228                            let breed_index = (item_index - 1) as usize % breeds.len();
1229                            *breed_val = Value::String(breeds[breed_index].to_string());
1230                        }
1231                    }
1232                    // Vary queen age
1233                    if let Some(age_val) = queen_obj.get_mut("age_days") {
1234                        if let Some(base_age) = age_val.as_u64() {
1235                            *age_val = Value::Number((base_age + (item_index * 10) % 200).into());
1236                        } else if let Some(base_age) = age_val.as_i64() {
1237                            *age_val = Value::Number((base_age + (item_index as i64 * 10) % 200).into());
1238                        }
1239                    }
1240                    // Vary queen mark color
1241                    if let Some(color_val) = queen_obj.get_mut("mark_color") {
1242                        if let Some(_) = color_val.as_str() {
1243                            let colors = ["yellow", "white", "red", "green", "blue"];
1244                            let color_index = (item_index - 1) as usize % colors.len();
1245                            *color_val = Value::String(colors[color_index].to_string());
1246                        }
1247                    }
1248                }
1249            }
1250
1251            // Vary description fields if they exist
1252            if let Some(desc_val) = obj.get_mut("description") {
1253                if let Some(desc_str) = desc_val.as_str() {
1254                    let descriptions = [
1255                        "Production apiary",
1256                        "Research apiary",
1257                        "Commercial operation",
1258                        "Backyard apiary",
1259                        "Educational apiary"
1260                    ];
1261                    let desc_index = (item_index - 1) as usize % descriptions.len();
1262                    *desc_val = Value::String(descriptions[desc_index].to_string());
1263                }
1264            }
1265
1266            // Vary timestamp fields (created_at, updated_at, timestamp, date) for realistic time-series data
1267            // Generate timestamps spanning 12-24 months with proper distribution
1268            let timestamp_fields = ["created_at", "updated_at", "timestamp", "date", "forecastDate", "predictedDate"];
1269            for field_name in &timestamp_fields {
1270                if let Some(timestamp_val) = obj.get_mut(*field_name) {
1271                    if let Some(_timestamp_str) = timestamp_val.as_str() {
1272                        // Generate realistic timestamp: distribute items over past 12-18 months
1273                        // Use item_index to create variation (not all same date)
1274                        let months_ago = 12 + ((item_index - 1) % 6); // Distribute over 6 months (12-18 months ago)
1275                        let days_offset = (item_index - 1) % 28; // Distribute within month (cap at 28)
1276                        let hours_offset = ((item_index * 7) % 24) as u8; // Distribute throughout day
1277                        let minutes_offset = ((item_index * 11) % 60) as u8; // Vary minutes
1278
1279                        // Calculate timestamp relative to current date (November 2024)
1280                        // Format: ISO 8601 (e.g., "2024-11-12T14:30:00Z")
1281                        let base_year = 2024;
1282                        let base_month = 11;
1283
1284                        // Calculate target month (going back in time)
1285                        let target_year = if months_ago >= base_month as u64 {
1286                            base_year - 1
1287                        } else {
1288                            base_year
1289                        };
1290                        let target_month = if months_ago >= base_month as u64 {
1291                            12 - (months_ago - base_month as u64) as u8
1292                        } else {
1293                            (base_month as u64 - months_ago) as u8
1294                        };
1295                        let target_day = std::cmp::min(28, 1 + days_offset as u8); // Start from day 1, cap at 28
1296
1297                        // Format as ISO 8601
1298                        let timestamp = format!("{:04}-{:02}-{:02}T{:02}:{:02}:00Z",
1299                            target_year,
1300                            target_month,
1301                            target_day,
1302                            hours_offset,
1303                            minutes_offset
1304                        );
1305                        *timestamp_val = Value::String(timestamp);
1306                    }
1307                }
1308            }
1309        }
1310    }
1311
1312    /// Try to infer total count from context (parent entity schemas)
1313    /// This is a heuristic that looks for common relationship patterns
1314    fn try_infer_total_from_context(
1315        spec: &OpenApiSpec,
1316        obj_type: &openapiv3::ObjectType,
1317    ) -> Option<u64> {
1318        // Look for "items" array to determine what we're generating
1319        if let Some(items_schema_ref) = obj_type.properties.get("items") {
1320            // Try to determine child entity name from items schema
1321            // This is a heuristic: check schema names in the spec
1322            if let Some(components) = &spec.spec.components {
1323                let schemas = &components.schemas;
1324                // Look through all schemas to find potential parent entities
1325                // that might have count fields matching the items type
1326                for (schema_name, schema_ref) in schemas {
1327                    if let ReferenceOr::Item(schema) = schema_ref {
1328                        if let openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) = &schema.schema_kind {
1329                            // Look for count fields that might match
1330                            for (prop_name, prop_schema) in &obj.properties {
1331                                let prop_lower = prop_name.to_lowercase();
1332                                if prop_lower.ends_with("_count") {
1333                                    // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema>
1334                                    let schema_ref: ReferenceOr<Schema> = match prop_schema {
1335                                        ReferenceOr::Item(boxed) => ReferenceOr::Item(boxed.as_ref().clone()),
1336                                        ReferenceOr::Reference { reference } => ReferenceOr::Reference { reference: reference.clone() },
1337                                    };
1338                                    // Found a count field, try to extract its value
1339                                    if let Some(count) = Self::extract_numeric_value_from_schema(&schema_ref) {
1340                                        // Use a reasonable default if count is very large
1341                                        if count > 0 && count <= 1000 {
1342                                            tracing::debug!(
1343                                                "Inferred count {} from parent schema {} field {}",
1344                                                count,
1345                                                schema_name,
1346                                                prop_name
1347                                            );
1348                                            return Some(count);
1349                                        }
1350                                    }
1351                                }
1352                            }
1353                        }
1354                    }
1355                }
1356            }
1357        }
1358
1359        None
1360    }
1361
1362    /// Infer relationship count from parent entity schema
1363    /// When generating a child entity list, check if parent entity has a count field
1364    fn infer_count_from_parent_schema(
1365        spec: &OpenApiSpec,
1366        parent_entity_name: &str,
1367        child_entity_name: &str,
1368    ) -> Option<u64> {
1369        // Look for parent entity schema
1370        let parent_schema_name = format!("{}", parent_entity_name);
1371        let count_field_name = format!("{}_count", child_entity_name);
1372
1373        // Try to find the schema
1374        if let Some(components) = &spec.spec.components {
1375            let schemas = &components.schemas;
1376            // Look for parent schema (case-insensitive)
1377            for (schema_name, schema_ref) in schemas {
1378                let schema_name_lower = schema_name.to_lowercase();
1379                if schema_name_lower.contains(&parent_entity_name.to_lowercase()) {
1380                    if let ReferenceOr::Item(schema) = schema_ref {
1381                        // Check if this schema has the count field
1382                        if let openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) = &schema.schema_kind {
1383                            for (prop_name, prop_schema) in &obj.properties {
1384                                if prop_name.to_lowercase() == count_field_name.to_lowercase() {
1385                                    // Convert ReferenceOr<Box<Schema>> to ReferenceOr<Schema>
1386                                    let schema_ref: ReferenceOr<Schema> = match prop_schema {
1387                                        ReferenceOr::Item(boxed) => ReferenceOr::Item(boxed.as_ref().clone()),
1388                                        ReferenceOr::Reference { reference } => ReferenceOr::Reference { reference: reference.clone() },
1389                                    };
1390                                    // Extract count value from schema
1391                                    return Self::extract_numeric_value_from_schema(&schema_ref);
1392                                }
1393                            }
1394                        }
1395                    }
1396                }
1397            }
1398        }
1399
1400        None
1401    }
1402
1403    /// Generate example value for a property based on its name
1404    fn generate_example_for_property(prop_name: &str) -> Value {
1405        let prop_lower = prop_name.to_lowercase();
1406
1407        // Generate realistic data based on property name patterns
1408        if prop_lower.contains("id") || prop_lower.contains("uuid") {
1409            Value::String(uuid::Uuid::new_v4().to_string())
1410        } else if prop_lower.contains("email") {
1411            Value::String(format!("user{}@example.com", rng().random_range(1000..=9999)))
1412        } else if prop_lower.contains("name") || prop_lower.contains("title") {
1413            let names = ["John Doe", "Jane Smith", "Bob Johnson", "Alice Brown"];
1414            Value::String(names[rng().random_range(0..names.len())].to_string())
1415        } else if prop_lower.contains("phone") || prop_lower.contains("mobile") {
1416            Value::String(format!("+1-555-{:04}", rng().random_range(1000..=9999)))
1417        } else if prop_lower.contains("address") || prop_lower.contains("street") {
1418            let streets = ["123 Main St", "456 Oak Ave", "789 Pine Rd", "321 Elm St"];
1419            Value::String(streets[rng().random_range(0..streets.len())].to_string())
1420        } else if prop_lower.contains("city") {
1421            let cities = ["New York", "London", "Tokyo", "Paris", "Sydney"];
1422            Value::String(cities[rng().random_range(0..cities.len())].to_string())
1423        } else if prop_lower.contains("country") {
1424            let countries = ["USA", "UK", "Japan", "France", "Australia"];
1425            Value::String(countries[rng().random_range(0..countries.len())].to_string())
1426        } else if prop_lower.contains("company") || prop_lower.contains("organization") {
1427            let companies = ["Acme Corp", "Tech Solutions", "Global Inc", "Innovate Ltd"];
1428            Value::String(companies[rng().random_range(0..companies.len())].to_string())
1429        } else if prop_lower.contains("url") || prop_lower.contains("website") {
1430            Value::String("https://example.com".to_string())
1431        } else if prop_lower.contains("age") {
1432            Value::Number((18 + rng().random_range(0..60)).into())
1433        } else if prop_lower.contains("count") || prop_lower.contains("quantity") {
1434            Value::Number((1 + rng().random_range(0..100)).into())
1435        } else if prop_lower.contains("price")
1436            || prop_lower.contains("amount")
1437            || prop_lower.contains("cost")
1438        {
1439            Value::Number(
1440                serde_json::Number::from_f64(
1441                    (rng().random::<f64>() * 1000.0 * 100.0).round() / 100.0,
1442                )
1443                .unwrap(),
1444            )
1445        } else if prop_lower.contains("active")
1446            || prop_lower.contains("enabled")
1447            || prop_lower.contains("is_")
1448        {
1449            Value::Bool(rng().random_bool(0.5))
1450        } else if prop_lower.contains("date") || prop_lower.contains("time") {
1451            Value::String(chrono::Utc::now().to_rfc3339())
1452        } else if prop_lower.contains("description") || prop_lower.contains("comment") {
1453            Value::String("This is a sample description text.".to_string())
1454        } else {
1455            Value::String(format!("example {}", prop_name))
1456        }
1457    }
1458
1459    /// Generate example responses from OpenAPI examples
1460    pub fn generate_from_examples(
1461        response: &Response,
1462        content_type: Option<&str>,
1463    ) -> Result<Option<Value>> {
1464        use openapiv3::ReferenceOr;
1465
1466        // If content_type is specified, look for examples in that media type
1467        if let Some(content_type) = content_type {
1468            if let Some(media_type) = response.content.get(content_type) {
1469                // Check for single example first
1470                if let Some(example) = &media_type.example {
1471                    return Ok(Some(example.clone()));
1472                }
1473
1474                // Check for multiple examples
1475                for (_, example_ref) in &media_type.examples {
1476                    if let ReferenceOr::Item(example) = example_ref {
1477                        if let Some(value) = &example.value {
1478                            return Ok(Some(value.clone()));
1479                        }
1480                    }
1481                    // Reference resolution would require spec parameter to be added to this function
1482                }
1483            }
1484        }
1485
1486        // If no content_type specified or not found, check all media types
1487        for (_, media_type) in &response.content {
1488            // Check for single example first
1489            if let Some(example) = &media_type.example {
1490                return Ok(Some(example.clone()));
1491            }
1492
1493            // Check for multiple examples
1494            for (_, example_ref) in &media_type.examples {
1495                if let ReferenceOr::Item(example) = example_ref {
1496                    if let Some(value) = &example.value {
1497                        return Ok(Some(value.clone()));
1498                    }
1499                }
1500                // Reference resolution would require spec parameter to be added to this function
1501            }
1502        }
1503
1504        Ok(None)
1505    }
1506
1507    /// Expand templates like {{now}} and {{uuid}} in JSON values
1508    fn expand_templates(value: &Value) -> Value {
1509        match value {
1510            Value::String(s) => {
1511                let expanded = s
1512                    .replace("{{now}}", &chrono::Utc::now().to_rfc3339())
1513                    .replace("{{uuid}}", &uuid::Uuid::new_v4().to_string());
1514                Value::String(expanded)
1515            }
1516            Value::Object(map) => {
1517                let mut new_map = serde_json::Map::new();
1518                for (key, val) in map {
1519                    new_map.insert(key.clone(), Self::expand_templates(val));
1520                }
1521                Value::Object(new_map)
1522            }
1523            Value::Array(arr) => {
1524                let new_arr: Vec<Value> = arr.iter().map(Self::expand_templates).collect();
1525                Value::Array(new_arr)
1526            }
1527            _ => value.clone(),
1528        }
1529    }
1530}
1531
1532#[cfg(test)]
1533mod tests {
1534    use super::*;
1535    use openapiv3::ReferenceOr;
1536
1537    #[test]
1538    fn generates_example_using_referenced_schemas() {
1539        let yaml = r#"
1540openapi: 3.0.3
1541info:
1542  title: Test API
1543  version: "1.0.0"
1544paths:
1545  /apiaries:
1546    get:
1547      responses:
1548        '200':
1549          description: ok
1550          content:
1551            application/json:
1552              schema:
1553                $ref: '#/components/schemas/Apiary'
1554components:
1555  schemas:
1556    Apiary:
1557      type: object
1558      properties:
1559        id:
1560          type: string
1561        hive:
1562          $ref: '#/components/schemas/Hive'
1563    Hive:
1564      type: object
1565      properties:
1566        name:
1567          type: string
1568        active:
1569          type: boolean
1570        "#;
1571
1572        let spec = OpenApiSpec::from_string(yaml, Some("yaml")).expect("load spec");
1573        let path_item = spec
1574            .spec
1575            .paths
1576            .paths
1577            .get("/apiaries")
1578            .and_then(ReferenceOr::as_item)
1579            .expect("path item");
1580        let operation = path_item.get.as_ref().expect("GET operation");
1581
1582        let response =
1583            ResponseGenerator::generate_response(&spec, operation, 200, Some("application/json"))
1584                .expect("generate response");
1585
1586        let obj = response.as_object().expect("response object");
1587        assert!(obj.contains_key("id"));
1588        let hive = obj.get("hive").and_then(|value| value.as_object()).expect("hive object");
1589        assert!(hive.contains_key("name"));
1590        assert!(hive.contains_key("active"));
1591    }
1592}
1593
1594/// Mock response data
1595#[derive(Debug, Clone)]
1596pub struct MockResponse {
1597    /// HTTP status code
1598    pub status_code: u16,
1599    /// Response headers
1600    pub headers: HashMap<String, String>,
1601    /// Response body
1602    pub body: Option<Value>,
1603}
1604
1605impl MockResponse {
1606    /// Create a new mock response
1607    pub fn new(status_code: u16) -> Self {
1608        Self {
1609            status_code,
1610            headers: HashMap::new(),
1611            body: None,
1612        }
1613    }
1614
1615    /// Add a header to the response
1616    pub fn with_header(mut self, name: String, value: String) -> Self {
1617        self.headers.insert(name, value);
1618        self
1619    }
1620
1621    /// Set the response body
1622    pub fn with_body(mut self, body: Value) -> Self {
1623        self.body = Some(body);
1624        self
1625    }
1626}
1627
1628/// OpenAPI security requirement wrapper
1629#[derive(Debug, Clone)]
1630pub struct OpenApiSecurityRequirement {
1631    /// The security scheme name
1632    pub scheme: String,
1633    /// Required scopes (for OAuth2)
1634    pub scopes: Vec<String>,
1635}
1636
1637impl OpenApiSecurityRequirement {
1638    /// Create a new security requirement
1639    pub fn new(scheme: String, scopes: Vec<String>) -> Self {
1640        Self { scheme, scopes }
1641    }
1642}
1643
1644/// OpenAPI operation wrapper with path context
1645#[derive(Debug, Clone)]
1646pub struct OpenApiOperation {
1647    /// The HTTP method
1648    pub method: String,
1649    /// The path this operation belongs to
1650    pub path: String,
1651    /// The OpenAPI operation
1652    pub operation: openapiv3::Operation,
1653}
1654
1655impl OpenApiOperation {
1656    /// Create a new OpenApiOperation
1657    pub fn new(method: String, path: String, operation: openapiv3::Operation) -> Self {
1658        Self {
1659            method,
1660            path,
1661            operation,
1662        }
1663    }
1664}