alef 0.85.15

Opinionated polyglot binding generator for Rust libraries
Documentation
//! Runtime server source fragment for the generated standalone mock-server binary.

const RUNTIME_SERVER_SOURCE: &str = r####"// ---------------------------------------------------------------------------
// Route table
// ---------------------------------------------------------------------------

#[derive(Clone, Debug)]
struct MockRoute {
    status: u16,
    body: Vec<u8>,
    /// Whether this route serves a streaming SSE response.
    /// True even when `stream_chunks` is empty (e.g. an empty stream still sends
    /// `data: [DONE]\n\n` with the correct `content-type: text/event-stream` header).
    is_streaming: bool,
    stream_chunks: Vec<String>,
    headers: Vec<(String, String)>,
    /// Optional artificial delay applied before the handler returns. When set,
    /// the handler `tokio::time::sleep`s for this many milliseconds before
    /// constructing the response — used by timeout-error fixtures to force
    /// client-side request timeouts.
    delay_ms: Option<u64>,
}

type RouteTable = Arc<HashMap<String, MockRoute>>;

// ---------------------------------------------------------------------------
// Axum handler
// ---------------------------------------------------------------------------

async fn handle_request(State(routes): State<RouteTable>, req: Request<Body>) -> Response {
    let path = req.uri().path().to_owned();

    // Try exact match first
    if let Some(route) = routes.get(&path) {
        if let Some(delay_ms) = route.delay_ms {
            tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await;
        }
        return serve_route(route);
    }

    // Try prefix match: find a route that is a prefix of the request path
    // This allows /fixtures/basic_chat/v1/chat/completions to match /fixtures/basic_chat
    for (route_path, route) in routes.iter() {
        if path.starts_with(route_path) && (path.len() == route_path.len() || path.as_bytes()[route_path.len()] == b'/') {
            if let Some(delay_ms) = route.delay_ms {
                tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await;
            }
            return serve_route(route);
        }
    }

    // Fall back to serving a raw test document from the docs dir, so HTTP URL
    // fixtures that reference relative file paths resolve against test_documents.
    if let Some(response) = serve_test_document(&path) {
        return response;
    }

    Response::builder()
        .status(StatusCode::NOT_FOUND)
        .body(Body::from(format!("No mock route for {path}")))
        .unwrap()
        .into_response()
}

fn serve_route(route: &MockRoute) -> Response {
    let status = StatusCode::from_u16(route.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);

    if route.is_streaming {
        // Note: stream_chunks may be empty for an empty-stream fixture; we still emit
        // `data: [DONE]\n\n` with the correct SSE headers so clients do not hang.
        let mut sse = String::new();
        for chunk in &route.stream_chunks {
            sse.push_str("data: ");
            sse.push_str(chunk);
            sse.push_str("\n\n");
        }
        sse.push_str("data: [DONE]\n\n");

        let mut builder = Response::builder()
            .status(status)
            .header("content-type", "text/event-stream")
            .header("cache-control", "no-cache");
        for (name, value) in &route.headers {
            builder = builder.header(name, value);
        }
        return builder.body(Body::from(sse)).unwrap().into_response();
    }

    // Only set the default content-type if the fixture does not override it.
    // Inspect the first non-whitespace byte to detect JSON vs binary vs plain text.
    let has_content_type = route.headers.iter().any(|(k, _)| k.to_lowercase() == "content-type");
    let mut builder = Response::builder().status(status);
    if !has_content_type {
        let first_nonws = route.body.iter().find(|&&b| b != b' ' && b != b'\t' && b != b'\n' && b != b'\r');
        let default_ct = match first_nonws {
            Some(&b'{') | Some(&b'[') => "application/json",
            _ => "text/plain",
        };
        builder = builder.header("content-type", default_ct);
    }
    // A fixture that declares `content-encoding` gets a body actually encoded that way --
    // gzip and br. br is not optional coverage: a consumer with a brotli compression
    // fixture is the first thing this hard error meets, and a panic here kills the server
    // for every later test in the suite, not just the compression one. This server used to strip the header and serve plain bytes instead, which was
    // safe but left every client-side decompression branch unreachable in every suite --
    // so a compression regression in any binding's HTTP surface could not fail a single
    // test (xberg-io/xberg#1598). An unsupported encoding is a hard error rather than a
    // silent fallback to an unencoded body: serving plain bytes under a content-encoding
    // header is exactly the breakage the old strip existed to avoid, and a fixture asking
    // for an encoding this server cannot produce is a fixture bug that should be loud.
    let declared_encoding = route
        .headers
        .iter()
        .find(|(name, _)| name.to_lowercase() == "content-encoding")
        .map(|(_, value)| value.as_str());
    let response_body = match declared_encoding {
        None | Some("<<absent>>") => route.body.clone(),
        Some("gzip") => {
            let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
            encoder.write_all(&route.body).expect("mock-server: gzip encode failed");
            encoder.finish().expect("mock-server: gzip finish failed")
        }
        Some("br") => {
            let mut encoded = Vec::new();
            brotli::BrotliCompress(
                &mut route.body.as_slice(),
                &mut encoded,
                &brotli::enc::BrotliEncoderParams::default(),
            )
            .expect("mock-server: brotli encode failed");
            encoded
        }
        Some(other) => panic!(
            "mock-server: fixture declares content-encoding {other:?}, which this server \
             cannot produce (only gzip and br are supported). Serving an unencoded body under \
             that header would make clients fail to decode."
        ),
    };
    for (name, value) in &route.headers {
        // The <<absent>> sentinel means this header must NOT be present in the
        // real server response — do not emit it from the mock server either.
        if value == "<<absent>>" {
            continue;
        }
        // Replace the <<uuid>> sentinel with a real UUID v4 so clients can
        // assert the header value matches the UUID pattern.
        if value == "<<uuid>>" {
            let uuid = format!(
                "{:08x}-{:04x}-4{:03x}-{:04x}-{:012x}",
                rand_u32(),
                rand_u16(),
                rand_u16() & 0x0fff,
                (rand_u16() & 0x3fff) | 0x8000,
                rand_u48(),
            );
            builder = builder.header(name, uuid);
            continue;
        }
        builder = builder.header(name, value);
    }
    let body = response_body_with_headers(&route.headers, response_body);
    builder.body(body).unwrap().into_response()
}

/// Generate a pseudo-random u32 using the current time nanoseconds.
fn rand_u32() -> u32 {
    use std::time::{SystemTime, UNIX_EPOCH};
    let ns = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.subsec_nanos())
        .unwrap_or(0);
    ns ^ (ns.wrapping_shl(13)) ^ (ns.wrapping_shr(17))
}

fn rand_u16() -> u16 {
    (rand_u32() & 0xffff) as u16
}

fn rand_u48() -> u64 {
    ((rand_u32() as u64) << 16) | (rand_u16() as u64)
}

"####;

pub(super) fn render_runtime_server_source() -> &'static str {
    RUNTIME_SERVER_SOURCE
}