alpaca-rest-http 0.28.0

Shared HTTP transport layer for the alpaca-rust workspace
Documentation
use std::sync::{
    Arc,
    atomic::{AtomicUsize, Ordering},
};

use alpaca_core::BaseUrl;
use alpaca_http::{HttpClient, RequestParts, RetryConfig};
use reqwest::Method;
use serde_json::Value;
use tokio::{
    io::{AsyncReadExt, AsyncWriteExt},
    net::{TcpListener, TcpStream},
};

#[tokio::test]
async fn get_json_retries_body_read_failures() {
    let attempts = Arc::new(AtomicUsize::new(0));
    let listener = TcpListener::bind("127.0.0.1:0")
        .await
        .expect("test listener should bind");
    let addr = listener.local_addr().expect("listener should have address");
    let server_attempts = attempts.clone();

    let server = tokio::spawn(async move {
        for _ in 0..2 {
            let (stream, _) = listener.accept().await.expect("request should arrive");
            let attempt = server_attempts.fetch_add(1, Ordering::SeqCst) + 1;
            handle_request(stream, attempt).await;
        }
    });

    let client = HttpClient::builder()
        .retry_config(RetryConfig::default().with_max_retries(1))
        .build()
        .expect("client should build");
    let base_url = BaseUrl::new(format!("http://{}", addr)).expect("valid base url");
    let request = RequestParts::new(Method::GET, "/probe").with_operation("test.body_retry");

    let response = client
        .send_json::<Value>(&base_url, request, None)
        .await
        .expect("GET body read failures should be retried");

    assert_eq!(response.body()["ok"], true);
    assert_eq!(attempts.load(Ordering::SeqCst), 2);
    server.await.expect("server task should finish");
}

async fn handle_request(mut stream: TcpStream, attempt: usize) {
    let mut buffer = [0; 1024];
    let _ = stream.read(&mut buffer).await;

    if attempt == 1 {
        stream
            .write_all(
                b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 128\r\n\r\n{\"ok\"",
            )
            .await
            .expect("truncated response should write");
        return;
    }

    stream
        .write_all(
            b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 11\r\n\r\n{\"ok\":true}",
        )
        .await
        .expect("valid response should write");
}