httpio 0.2.4

A transport-agnostic, async HTTP/1.1 client library for any runtime.
Documentation
use criterion::{Criterion, black_box, criterion_group, criterion_main};
use std::str::FromStr;

use httpio::enums::header::http_header::HttpHeader;
use httpio::enums::http_body::HttpBody;
use httpio::structures::header::header_list::HttpHeaderList;

/// Benchmark HttpHeaderList::add() operations
/// Measures the overhead of lowercase conversion and HashMap insertion
fn bench_header_list_add(c: &mut Criterion) {
    let mut group = c.benchmark_group("header_list_add");

    let known_header = HttpHeader::from_str("Content-Type: application/json").unwrap();
    let generic_header = HttpHeader::from_str("X-Custom-Header: some-value").unwrap();

    group.bench_function("known_header", |b| {
        let mut list = HttpHeaderList::default();
        b.iter(|| {
            list.add(black_box(known_header.clone()));
        });
    });

    group.bench_function("generic_header", |b| {
        let mut list = HttpHeaderList::default();
        b.iter(|| {
            list.add(black_box(generic_header.clone()));
        });
    });

    group.bench_function("multiple_headers", |b| {
        let headers = vec![
            HttpHeader::from_str("Content-Type: application/json").unwrap(),
            HttpHeader::from_str("Content-Length: 100").unwrap(),
            HttpHeader::from_str("Host: example.com").unwrap(),
            HttpHeader::from_str("X-Custom-1: value1").unwrap(),
            HttpHeader::from_str("X-Custom-2: value2").unwrap(),
        ];
        b.iter(|| {
            let mut list = HttpHeaderList::default();
            for header in &headers {
                list.add(black_box(header.clone()));
            }
        });
    });

    group.finish();
}

/// Benchmark HttpRequestBuilder::new() construction
/// Measures encoding string building and header merging overhead
fn bench_request_builder_new(c: &mut Criterion) {
    let mut group = c.benchmark_group("request_builder_new");

    use httpio::enums::http_version::HttpVersion;
    use httpio::structures::http_request_builder::HttpRequestBuilder;

    let empty_headers = HttpHeaderList::default();
    let some_headers: HttpHeaderList = vec![
        HttpHeader::from_str("Accept: application/json").unwrap(),
        HttpHeader::from_str("Authorization: Bearer token123").unwrap(),
    ]
    .into();

    group.bench_function("empty_headers", |b| {
        b.iter(|| {
            HttpRequestBuilder::new(
                HttpVersion::Http11,
                black_box(empty_headers.clone()),
                "example.com",
            )
        });
    });

    group.bench_function("with_headers", |b| {
        b.iter(|| {
            HttpRequestBuilder::new(
                HttpVersion::Http11,
                black_box(some_headers.clone()),
                "example.com",
            )
        });
    });

    group.finish();
}

/// Benchmark HttpRequestBuilder::build_request()
/// Measures header merging and cloning overhead
fn bench_request_builder_build(c: &mut Criterion) {
    let mut group = c.benchmark_group("request_builder_build");

    use httpio::enums::http_request_method::HttpRequestMethod;
    use httpio::enums::http_version::HttpVersion;
    use httpio::structures::http_request_builder::HttpRequestBuilder;

    let builder = HttpRequestBuilder::new(
        HttpVersion::Http11,
        HttpHeaderList::default(),
        "example.com",
    );

    let empty_headers = HttpHeaderList::default();
    let some_headers: HttpHeaderList = vec![
        HttpHeader::from_str("Accept: application/json").unwrap(),
        HttpHeader::from_str("X-Request-ID: abc123").unwrap(),
    ]
    .into();

    let small_body = HttpBody::Content(b"test".to_vec());
    let medium_body = HttpBody::Content(vec![b'x'; 1024]);

    group.bench_function("empty_headers_no_body", |b| {
        b.iter(|| {
            builder.build_request(
                HttpRequestMethod::Get,
                "/api/test",
                black_box(empty_headers.clone()),
                HttpBody::None,
            )
        });
    });

    group.bench_function("with_headers_no_body", |b| {
        b.iter(|| {
            builder.build_request(
                HttpRequestMethod::Get,
                "/api/test",
                black_box(some_headers.clone()),
                HttpBody::None,
            )
        });
    });

    group.bench_function("with_headers_small_body", |b| {
        b.iter(|| {
            builder.build_request(
                HttpRequestMethod::Post,
                "/api/test",
                black_box(some_headers.clone()),
                black_box(small_body.clone()),
            )
        });
    });

    group.bench_function("with_headers_medium_body", |b| {
        b.iter(|| {
            builder.build_request(
                HttpRequestMethod::Post,
                "/api/test",
                black_box(some_headers.clone()),
                black_box(medium_body.clone()),
            )
        });
    });

    group.finish();
}

/// Benchmark HttpBodyPart::as_bytes()
/// Measures serialization overhead for body parts
fn bench_body_part_as_bytes(c: &mut Criterion) {
    let mut group = c.benchmark_group("body_part_as_bytes");

    use httpio::structures::http_body_part::HttpBodyPart;

    let part_no_headers = HttpBodyPart {
        headers: HttpHeaderList::default(),
        content: b"Hello, World!".to_vec(),
    };

    let part_with_headers = HttpBodyPart {
        headers: vec![
            HttpHeader::from_str("Content-Type: text/plain").unwrap(),
            HttpHeader::from_str("Content-Disposition: inline").unwrap(),
        ]
        .into(),
        content: b"Hello, World!".to_vec(),
    };

    let part_large_content = HttpBodyPart {
        headers: vec![HttpHeader::from_str("Content-Type: application/octet-stream").unwrap()]
            .into(),
        content: vec![b'x'; 10000],
    };

    group.bench_function("no_headers_small", |b| {
        b.iter(|| part_no_headers.as_bytes())
    });

    group.bench_function("with_headers_small", |b| {
        b.iter(|| part_with_headers.as_bytes())
    });

    group.bench_function("large_content", |b| {
        b.iter(|| part_large_content.as_bytes())
    });

    group.finish();
}

/// Benchmark HttpMultipartBody::as_bytes()
/// Measures serialization overhead for multipart bodies
fn bench_multipart_as_bytes(c: &mut Criterion) {
    let mut group = c.benchmark_group("multipart_as_bytes");

    use httpio::structures::http_body_part::HttpBodyPart;
    use httpio::structures::http_multipart_body::HttpMultipartBody;

    let small_multipart = HttpMultipartBody {
        parts: vec![
            HttpBodyPart {
                headers: vec![HttpHeader::from_str("Content-Type: text/plain").unwrap()].into(),
                content: b"Part 1 content".to_vec(),
            },
            HttpBodyPart {
                headers: vec![HttpHeader::from_str("Content-Type: text/plain").unwrap()].into(),
                content: b"Part 2 content".to_vec(),
            },
        ],
        boundary: "boundary123".to_string(),
        subtype: "form-data".to_string(),
        preamble: vec![],
        epilogue: vec![],
    };

    let medium_multipart = HttpMultipartBody {
        parts: (0..10)
            .map(|i| HttpBodyPart {
                headers: vec![HttpHeader::from_str("Content-Type: text/plain").unwrap()].into(),
                content: format!("Part {} content with some data", i).into_bytes(),
            })
            .collect(),
        boundary: "boundary123".to_string(),
        subtype: "form-data".to_string(),
        preamble: vec![],
        epilogue: vec![],
    };

    let large_multipart = HttpMultipartBody {
        parts: (0..5)
            .map(|_i| HttpBodyPart {
                headers: vec![
                    HttpHeader::from_str("Content-Type: application/octet-stream").unwrap(),
                ]
                .into(),
                content: vec![b'x'; 2000],
            })
            .collect(),
        boundary: "boundary123".to_string(),
        subtype: "form-data".to_string(),
        preamble: vec![],
        epilogue: vec![],
    };

    group.bench_function("small_2_parts", |b| b.iter(|| small_multipart.as_bytes()));

    group.bench_function("medium_10_parts", |b| {
        b.iter(|| medium_multipart.as_bytes())
    });

    group.bench_function("large_5_parts_10kb", |b| {
        b.iter(|| large_multipart.as_bytes())
    });

    group.finish();
}

/// Benchmark header list parsing simulation
/// Measures overhead of parsing multiple headers in sequence
fn bench_header_list_parsing(c: &mut Criterion) {
    let mut group = c.benchmark_group("header_list_parsing");

    let header_strings = vec![
        "Content-Type: application/json",
        "Content-Length: 12345",
        "Host: example.com",
        "Accept: */*",
        "Accept-Encoding: gzip, deflate",
        "User-Agent: benchmark/1.0",
        "Connection: keep-alive",
        "Cache-Control: no-cache",
        "X-Request-ID: abc123",
        "Authorization: Bearer token",
    ];

    group.bench_function("parse_3_headers", |b| {
        let headers = &header_strings[..3];
        b.iter(|| {
            let mut list = HttpHeaderList::default();
            for header_str in headers {
                let header = HttpHeader::from_str(black_box(header_str)).unwrap();
                list.add(header);
            }
        });
    });

    group.bench_function("parse_10_headers", |b| {
        b.iter(|| {
            let mut list = HttpHeaderList::default();
            for header_str in &header_strings {
                let header = HttpHeader::from_str(black_box(header_str)).unwrap();
                list.add(header);
            }
        });
    });

    group.finish();
}

criterion_group!(
    benches,
    bench_header_list_add,
    bench_request_builder_new,
    bench_request_builder_build,
    bench_body_part_as_bytes,
    bench_multipart_as_bytes,
    bench_header_list_parsing,
);
criterion_main!(benches);