Skip to main content

mockforge_foundation/
response_generation_trace.rs

1//! Response generation trace for debugging and observability
2//!
3//! This module provides structures to track how responses are generated,
4//! including template selection, persona graph usage, rules/hooks execution,
5//! and template expansion details.
6
7use crate::response_selection::ResponseSelectionMode;
8use crate::schema_diff::ValidationError;
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use std::collections::HashMap;
12
13/// Response generation trace
14///
15/// Captures detailed information about how a response was generated,
16/// enabling users to understand "why did I get this response?"
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct ResponseGenerationTrace {
19    /// Selected template or fixture path
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub template_path: Option<String>,
22
23    /// Selected fixture path (if using fixtures)
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub fixture_path: Option<String>,
26
27    /// Response selection mode used
28    pub response_selection_mode: ResponseSelectionMode,
29
30    /// Selected example/scenario name (if applicable)
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub selected_example: Option<String>,
33
34    /// Persona graph nodes used in response generation
35    #[serde(default)]
36    pub persona_graph_nodes: Vec<PersonaGraphNodeUsage>,
37
38    /// Rules/hook scripts that fired during generation
39    #[serde(default)]
40    pub rules_executed: Vec<RuleExecution>,
41
42    /// Template expansion steps
43    #[serde(default)]
44    pub template_expansions: Vec<TemplateExpansion>,
45
46    /// Reality blending decisions
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub blending_decision: Option<BlendingDecision>,
49
50    /// Final resolved response payload (after all transformations)
51    ///
52    /// This is the complete response body that was sent to the client,
53    /// after all template expansions, persona graph enrichments, and
54    /// rule/hook modifications have been applied.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub final_payload: Option<Value>,
57
58    /// Schema validation diff results
59    ///
60    /// Contains validation errors if the final payload doesn't match
61    /// the expected contract schema. Empty vector means the payload
62    /// is valid according to the schema.
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub schema_validation_diff: Option<Vec<ValidationError>>,
65
66    /// Additional metadata about the generation process
67    #[serde(default)]
68    pub metadata: HashMap<String, Value>,
69}
70
71impl Default for ResponseGenerationTrace {
72    fn default() -> Self {
73        Self {
74            template_path: None,
75            fixture_path: None,
76            response_selection_mode: ResponseSelectionMode::First,
77            selected_example: None,
78            persona_graph_nodes: Vec::new(),
79            rules_executed: Vec::new(),
80            template_expansions: Vec::new(),
81            blending_decision: None,
82            final_payload: None,
83            schema_validation_diff: None,
84            metadata: HashMap::new(),
85        }
86    }
87}
88
89/// Persona graph node usage information
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct PersonaGraphNodeUsage {
92    /// Persona ID
93    pub persona_id: String,
94
95    /// Entity type (e.g., "user", "order", "payment")
96    pub entity_type: String,
97
98    /// How this node was used (e.g., "data_source", "relationship_traversal")
99    pub usage_type: String,
100
101    /// Relationship path traversed (if applicable)
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub relationship_path: Option<Vec<String>>,
104}
105
106/// Rule or hook script execution information
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct RuleExecution {
109    /// Rule or hook name
110    pub name: String,
111
112    /// Rule type (e.g., "hook", "consistency_rule", "mutation_rule")
113    pub rule_type: String,
114
115    /// Whether the rule condition matched
116    pub condition_matched: bool,
117
118    /// Actions executed by the rule
119    #[serde(default)]
120    pub actions_executed: Vec<String>,
121
122    /// Execution time in milliseconds
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub execution_time_ms: Option<u64>,
125
126    /// Error message (if execution failed)
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub error: Option<String>,
129}
130
131/// Template expansion step
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct TemplateExpansion {
134    /// Template expression that was expanded (e.g., "{{user.name}}")
135    pub template: String,
136
137    /// Expanded value
138    pub value: Value,
139
140    /// Source of the value (e.g., "persona", "faker", "context")
141    pub source: String,
142
143    /// Step number in the expansion sequence
144    pub step: usize,
145}
146
147/// Reality blending decision information
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct BlendingDecision {
150    /// Blend ratio used (0.0 = mock, 1.0 = real)
151    pub blend_ratio: f64,
152
153    /// Source of the blend ratio (e.g., "global", "route_rule", "time_schedule")
154    pub ratio_source: String,
155
156    /// Whether blending was actually performed
157    pub blended: bool,
158
159    /// Merge strategy used (if blended)
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub merge_strategy: Option<String>,
162
163    /// Field-level blending decisions (if applicable)
164    #[serde(default)]
165    pub field_decisions: Vec<FieldBlendingDecision>,
166}
167
168/// Field-level blending decision
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct FieldBlendingDecision {
171    /// JSON path to the field
172    pub field_path: String,
173
174    /// Blend ratio used for this field
175    pub field_ratio: f64,
176
177    /// Source of the field value (e.g., "mock", "real", "blended")
178    pub value_source: String,
179}
180
181impl ResponseGenerationTrace {
182    /// Create a new empty trace
183    pub fn new() -> Self {
184        Self::default()
185    }
186
187    /// Add a persona graph node usage
188    pub fn add_persona_node(&mut self, usage: PersonaGraphNodeUsage) {
189        self.persona_graph_nodes.push(usage);
190    }
191
192    /// Add a rule execution
193    pub fn add_rule_execution(&mut self, execution: RuleExecution) {
194        self.rules_executed.push(execution);
195    }
196
197    /// Add a template expansion step
198    pub fn add_template_expansion(&mut self, expansion: TemplateExpansion) {
199        self.template_expansions.push(expansion);
200    }
201
202    /// Set the blending decision
203    pub fn set_blending_decision(&mut self, decision: BlendingDecision) {
204        self.blending_decision = Some(decision);
205    }
206
207    /// Add metadata
208    pub fn add_metadata(&mut self, key: String, value: Value) {
209        self.metadata.insert(key, value);
210    }
211
212    /// Set the final resolved payload
213    pub fn set_final_payload(&mut self, payload: Value) {
214        self.final_payload = Some(payload);
215    }
216
217    /// Set the schema validation diff results
218    pub fn set_schema_validation_diff(&mut self, diff: Vec<ValidationError>) {
219        self.schema_validation_diff = Some(diff);
220    }
221}