spikard-http 0.16.0

High-performance HTTP server for Spikard with tower-http middleware stack
Documentation
use axum::http::HeaderValue;
use axum::{Router, response::IntoResponse, routing::get};
use brotli::CompressorWriter;
use flate2::Compression;
use flate2::write::GzEncoder;
use spikard_http::testing::{MultipartFilePart, build_multipart_body, encode_urlencoded_body, snapshot_response};
use std::io::Write;

#[test]
fn urlencoded_encoding_handles_scalars_and_objects() {
    let s = serde_json::Value::String("a=b&c=d".to_string());
    assert_eq!(encode_urlencoded_body(&s).unwrap(), b"a=b&c=d".to_vec());

    let mut obj = serde_json::Map::new();
    obj.insert("name".to_string(), serde_json::Value::String("Alice".to_string()));
    obj.insert("tags".to_string(), serde_json::json!(["a", "b"]));
    let value = serde_json::Value::Object(obj);
    let encoded = String::from_utf8(encode_urlencoded_body(&value).unwrap()).unwrap();
    assert!(encoded.contains("name=Alice"));
    assert!(encoded.contains("tags"));
}

#[test]
fn multipart_body_contains_fields_and_files() {
    let (body, boundary) = build_multipart_body(
        &[("field".to_string(), "value".to_string())],
        &[MultipartFilePart {
            field_name: "file".to_string(),
            filename: "hello.txt".to_string(),
            content_type: Some("text/plain".to_string()),
            content: b"hello".to_vec(),
        }],
    );

    let body_str = String::from_utf8_lossy(&body);
    assert!(body_str.contains(&format!("--{boundary}")));
    assert!(body_str.contains("name=\"field\""));
    assert!(body_str.contains("value"));
    assert!(body_str.contains("name=\"file\"; filename=\"hello.txt\""));
    assert!(body_str.contains("Content-Type: text/plain"));
    assert!(body_str.contains("hello"));
}

#[tokio::test]
async fn snapshot_response_decodes_gzip_body() {
    let app = Router::new().route(
        "/gzip",
        get(|| async move {
            let raw = b"hello gzip".to_vec();
            let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
            encoder.write_all(&raw).unwrap();
            let compressed = encoder.finish().unwrap();

            ([("content-encoding", HeaderValue::from_static("gzip"))], compressed).into_response()
        }),
    );

    let server = axum_test::TestServer::new(app);
    let response = server.get("/gzip").await;

    let snapshot = snapshot_response(response).await.expect("snapshot failed");
    assert_eq!(snapshot.status, 200);
    assert_eq!(snapshot.text().unwrap(), "hello gzip");
}

#[tokio::test]
async fn snapshot_response_decodes_brotli_body() {
    let app = Router::new().route(
        "/br",
        get(|| async move {
            let raw = b"hello br".to_vec();
            let mut writer = CompressorWriter::new(Vec::new(), 4096, 6, 22);
            writer.write_all(&raw).unwrap();
            writer.flush().unwrap();
            let compressed = writer.into_inner();

            ([("content-encoding", HeaderValue::from_static("br"))], compressed).into_response()
        }),
    );

    let server = axum_test::TestServer::new(app);
    let response = server.get("/br").await;

    let snapshot = snapshot_response(response).await.expect("snapshot failed");
    assert_eq!(snapshot.status, 200);
    assert_eq!(snapshot.text().unwrap(), "hello br");
}