mockforge_http/
token_response.rs

1//! Token-based response resolution for HTTP handlers
2//!
3//! This module integrates the token resolver with HTTP response generation.
4
5use axum::{
6    body::Body,
7    http::{Response, StatusCode},
8    response::IntoResponse,
9};
10use mockforge_data::rag::RagConfig;
11use mockforge_data::{resolve_tokens, resolve_tokens_with_rag};
12use serde_json::Value;
13use tracing::*;
14
15/// Resolve tokens in a JSON response body
16pub async fn resolve_response_tokens(body: Value) -> Result<Value, String> {
17    resolve_tokens(&body)
18        .await
19        .map_err(|e| format!("Failed to resolve tokens: {}", e))
20}
21
22/// Resolve tokens in a JSON response body with RAG support
23pub async fn resolve_response_tokens_with_rag(
24    body: Value,
25    rag_config: RagConfig,
26) -> Result<Value, String> {
27    resolve_tokens_with_rag(&body, rag_config)
28        .await
29        .map_err(|e| format!("Failed to resolve tokens with RAG: {}", e))
30}
31
32/// Create an HTTP response with token resolution
33pub async fn create_token_resolved_response(
34    status: StatusCode,
35    body: Value,
36    use_rag: bool,
37    rag_config: Option<RagConfig>,
38) -> Response<Body> {
39    let resolved_body = if use_rag {
40        let config = rag_config.unwrap_or_default();
41        match resolve_response_tokens_with_rag(body.clone(), config).await {
42            Ok(resolved) => resolved,
43            Err(e) => {
44                error!(error = %e, "Failed to resolve tokens with RAG, using original body");
45                body
46            }
47        }
48    } else {
49        match resolve_response_tokens(body.clone()).await {
50            Ok(resolved) => resolved,
51            Err(e) => {
52                error!(error = %e, "Failed to resolve tokens, using original body");
53                body
54            }
55        }
56    };
57
58    let json_string = match serde_json::to_string_pretty(&resolved_body) {
59        Ok(s) => s,
60        Err(e) => {
61            error!(error = %e, "Failed to serialize response");
62            return (StatusCode::INTERNAL_SERVER_ERROR, "Failed to serialize response")
63                .into_response();
64        }
65    };
66
67    Response::builder()
68        .status(status)
69        .header("Content-Type", "application/json")
70        .body(Body::from(json_string))
71        .unwrap_or_else(|e| {
72            error!(error = %e, "Failed to build response");
73            (StatusCode::INTERNAL_SERVER_ERROR, "Failed to build response").into_response()
74        })
75}
76
77/// Token-resolved JSON response builder
78pub struct TokenResolvedResponse {
79    status: StatusCode,
80    body: Value,
81    use_rag: bool,
82    rag_config: Option<RagConfig>,
83}
84
85impl TokenResolvedResponse {
86    /// Create a new token-resolved response
87    pub fn new(status: StatusCode, body: Value) -> Self {
88        Self {
89            status,
90            body,
91            use_rag: false,
92            rag_config: None,
93        }
94    }
95
96    /// Enable RAG-based token resolution
97    pub fn with_rag(mut self, config: RagConfig) -> Self {
98        self.use_rag = true;
99        self.rag_config = Some(config);
100        self
101    }
102
103    /// Build the response
104    pub async fn build(self) -> Response<Body> {
105        create_token_resolved_response(self.status, self.body, self.use_rag, self.rag_config).await
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use serde_json::json;
113
114    #[tokio::test]
115    async fn test_resolve_response_tokens() {
116        let body = json!({
117            "id": "$random.uuid",
118            "name": "$faker.name",
119            "email": "$faker.email"
120        });
121
122        let result = resolve_response_tokens(body).await;
123        assert!(result.is_ok());
124
125        let resolved = result.unwrap();
126        assert!(resolved["id"].is_string());
127        assert!(resolved["name"].is_string());
128        assert!(resolved["email"].is_string());
129    }
130
131    #[tokio::test]
132    async fn test_resolve_nested_tokens() {
133        let body = json!({
134            "user": {
135                "id": "$random.uuid",
136                "profile": {
137                    "name": "$faker.name",
138                    "contact": {
139                        "email": "$faker.email",
140                        "phone": "$faker.phone"
141                    }
142                }
143            }
144        });
145
146        let result = resolve_response_tokens(body).await;
147        assert!(result.is_ok());
148
149        let resolved = result.unwrap();
150        assert!(resolved["user"]["id"].is_string());
151        assert!(resolved["user"]["profile"]["name"].is_string());
152        assert!(resolved["user"]["profile"]["contact"]["email"].is_string());
153    }
154
155    #[tokio::test]
156    async fn test_resolve_array_tokens() {
157        let body = json!({
158            "users": [
159                {"id": "$random.uuid", "name": "$faker.name"},
160                {"id": "$random.uuid", "name": "$faker.name"}
161            ]
162        });
163
164        let result = resolve_response_tokens(body).await;
165        assert!(result.is_ok());
166
167        let resolved = result.unwrap();
168        let users = resolved["users"].as_array().unwrap();
169        assert_eq!(users.len(), 2);
170        assert!(users[0]["id"].is_string());
171        assert!(users[0]["name"].is_string());
172    }
173
174    #[tokio::test]
175    async fn test_token_resolved_response_builder() {
176        let body = json!({"message": "test"});
177        let response = TokenResolvedResponse::new(StatusCode::OK, body).build().await;
178
179        assert_eq!(response.status(), StatusCode::OK);
180    }
181}