xberg 1.0.2

High-performance document intelligence library for Rust. Extract text, metadata, and structured data from PDFs, Office documents, images, and 98 formats and 306 programming languages via tree-sitter code intelligence with async/sync APIs.
Documentation
#![cfg(feature = "api")]
//! Integration test for the `/extract` API handler using multipart uploads.

use axum::{
    body::{Body, to_bytes},
    http::{Request, StatusCode},
};
use serde_json::Value;
use tower::ServiceExt;
use xberg::{
    ExtractionConfig, ExtractionResult,
    api::{ApiSizeLimits, create_router_with_limits},
};

#[tokio::test]
async fn test_extract_accepts_single_file_multipart() {
    let router = create_router_with_limits(
        ExtractionConfig::default(),
        ApiSizeLimits {
            max_request_body_bytes: 5 * 1024 * 1024,
            max_multipart_field_bytes: 5 * 1024 * 1024,
        },
    );

    let boundary = "X-BOUNDARY";
    let body = format!(
        "--{boundary}\r\n\
Content-Disposition: form-data; name=\"files\"; filename=\"test.txt\"\r\n\
Content-Type: text/plain\r\n\
\r\n\
Hello world\r\n\
--{boundary}--\r\n"
    );
    let body_bytes = body.into_bytes();

    let request = Request::builder()
        .method("POST")
        .uri("/extract")
        .header("content-type", format!("multipart/form-data; boundary={boundary}"))
        .header("content-length", body_bytes.len())
        .body(Body::from(body_bytes))
        .expect("Failed to build request");

    let response = router.oneshot(request).await.expect("Request failed");
    assert_eq!(response.status(), StatusCode::OK);

    let bytes = to_bytes(response.into_body(), 1_000_000)
        .await
        .expect("Failed to read body");
    let value: Value = serde_json::from_slice(&bytes).expect("Response JSON parse failed");
    let envelope: ExtractionResult = serde_json::from_slice(&bytes).expect("Response should match ExtractionResult");

    let content = value
        .get("results")
        .and_then(Value::as_array)
        .and_then(|results| results.first())
        .and_then(|v| v.get("content"))
        .and_then(Value::as_str)
        .expect("Response should include extracted content");

    assert_eq!(content.trim_end_matches('\n'), "Hello world");
    if let Some(errors) = value.get("errors") {
        assert!(
            errors.as_array().is_some_and(Vec::is_empty),
            "Expected empty errors array, got {errors}"
        );
    }
    assert_eq!(value.pointer("/summary/inputs").and_then(Value::as_u64), Some(1));
    assert_eq!(value.pointer("/summary/results").and_then(Value::as_u64), Some(1));
    assert_eq!(value.pointer("/summary/errors").and_then(Value::as_u64), Some(0));
    assert!(
        envelope.errors.is_empty(),
        "Multipart extraction should not report errors"
    );
    assert_eq!(envelope.summary.inputs, 1);
    assert_eq!(envelope.summary.results, 1);
    assert_eq!(envelope.summary.errors, 0);
}