chio-conformance 0.1.2

Scenario loading and report generation for Chio cross-language conformance
Documentation
use std::io::Write;
use std::net::SocketAddr;

use chio_conformance::{fixture_messages_for_request, NativeFixtureRequest, NativeFixtureResponse};
use chio_kernel::transport::{read_frame, write_frame, TransportError};
use tiny_http::{Method, Response, Server, StatusCode};

fn main() {
    if let Err(error) = run() {
        eprintln!("error: {error}");
        std::process::exit(1);
    }
}

fn run() -> Result<(), Box<dyn std::error::Error>> {
    let mut args = std::env::args().skip(1);
    let mut http_listen = None;
    while let Some(flag) = args.next() {
        match flag.as_str() {
            "--http-listen" => {
                http_listen = Some(
                    args.next()
                        .ok_or_else(|| "missing value for --http-listen".to_string())?
                        .parse::<SocketAddr>()?,
                );
            }
            other => return Err(format!("unexpected flag: {other}").into()),
        }
    }

    if let Some(listen) = http_listen {
        run_http_fixture(listen)
    } else {
        run_stdio_fixture()
    }
}

fn run_stdio_fixture() -> Result<(), Box<dyn std::error::Error>> {
    let mut stdin = std::io::stdin().lock();
    let mut stdout = std::io::stdout().lock();

    let frame = match read_frame(&mut stdin) {
        Ok(frame) => frame,
        Err(TransportError::ConnectionClosed) => return Ok(()),
        Err(error) => return Err(Box::new(error)),
    };
    let request: chio_core::message::AgentMessage = serde_json::from_slice(&frame)?;
    for message in fixture_messages_for_request(&request) {
        let bytes = chio_core::canonical::canonical_json_bytes(&message)?;
        write_frame(&mut stdout, &bytes)?;
        stdout.flush()?;
    }
    Ok(())
}

fn run_http_fixture(listen: SocketAddr) -> Result<(), Box<dyn std::error::Error>> {
    let server = Server::http(listen).map_err(|error| error.to_string())?;
    for mut request in server.incoming_requests() {
        if request.method() != &Method::Post || request.url() != "/chio-conformance/v1/invoke" {
            let response = Response::empty(StatusCode(404));
            let _ = request.respond(response);
            continue;
        }

        let mut body = String::new();
        request.as_reader().read_to_string(&mut body)?;
        let fixture_request: NativeFixtureRequest = serde_json::from_str(&body)?;
        let response = NativeFixtureResponse {
            messages: fixture_messages_for_request(&fixture_request.request),
        };
        let response_body = serde_json::to_string(&response)?;
        let http_response = Response::from_string(response_body)
            .with_status_code(StatusCode(200))
            .with_header(
                tiny_http::Header::from_bytes("Content-Type", "application/json")
                    .map_err(|_| "failed to build content-type header")?,
            );
        let _ = request.respond(http_response);
    }
    Ok(())
}