Skip to main content

folk_plugin_http/
payload.rs

1//! HTTP payload encoding: axum Request/Response ↔ `serde_json::Value`.
2//!
3//! No JSON string serialization — data flows as structured Values.
4
5use std::collections::HashMap;
6
7use anyhow::{Context, Result};
8use axum::body::Body;
9use base64::Engine;
10use bytes::Bytes;
11
12/// Encode an axum Request to a `serde_json::Value` (no string serialization).
13pub async fn encode_request(
14    req: axum::http::Request<Body>,
15    max_body_size: usize,
16) -> Result<serde_json::Value> {
17    let (parts, body) = req.into_parts();
18    let body_bytes = axum::body::to_bytes(body, max_body_size)
19        .await
20        .context("read request body")?;
21
22    let headers: HashMap<String, String> = parts
23        .headers
24        .iter()
25        .filter_map(|(k, v)| Some((k.to_string(), v.to_str().ok()?.to_string())))
26        .collect();
27
28    let (body_value, body_encoding) = match String::from_utf8(body_bytes.to_vec()) {
29        Ok(s) => (s, None),
30        Err(e) => {
31            let encoded = base64::engine::general_purpose::STANDARD.encode(e.into_bytes());
32            (encoded, Some("base64"))
33        }
34    };
35
36    let mut payload = serde_json::json!({
37        "method": parts.method.to_string(),
38        "uri": parts.uri.to_string(),
39        "headers": headers,
40        "body": body_value,
41    });
42
43    if let Some(enc) = body_encoding {
44        payload["body_encoding"] = serde_json::json!(enc);
45    }
46
47    Ok(payload)
48}
49
50/// Decode a `serde_json::Value` to an axum Response (no string deserialization).
51pub fn decode_response(value: serde_json::Value) -> Result<axum::http::Response<Body>> {
52    let status = value.get("status").and_then(|v| v.as_u64()).unwrap_or(200) as u16;
53
54    let body_str = value.get("body").and_then(|v| v.as_str()).unwrap_or("");
55    let body_encoding = value.get("body_encoding").and_then(|v| v.as_str());
56
57    let body_bytes = if body_encoding == Some("base64") {
58        base64::engine::general_purpose::STANDARD
59            .decode(body_str)
60            .context("decode base64 response body")?
61    } else {
62        body_str.as_bytes().to_vec()
63    };
64
65    let mut builder = axum::http::Response::builder().status(status);
66
67    if let Some(headers) = value.get("headers").and_then(|v| v.as_object()) {
68        for (k, v) in headers {
69            if let Some(v_str) = v.as_str() {
70                builder = builder.header(k.as_str(), v_str);
71            }
72        }
73    }
74
75    builder
76        .body(Body::from(Bytes::from(body_bytes)))
77        .context("build response")
78}