Skip to content

Expose handler modules as pub so embedders can compose their own routes #75

Description

@clearloop

crabllm-proxy today gates the route tree behind proxy::router(state, admin_routes), which assembles the full set of /v1/* routes and unconditionally installs the built-in auth middleware and the active-connections tracker. The handler functions themselves (handlers::chat_completions, handlers::embeddings, etc.) are pub(crate) and therefore unreachable from outside the crate.

This is fine for the standalone gateway use case but locks out anyone embedding crabllm-proxy inside another binary who needs a different middleware stack — e.g. wanting to install their own auth layer (DB-backed, OAuth-introspection, mTLS) instead of the built-in HashMap<token, key_name> lookup, or needing to add edge-level rate limiting / tracing / request-id propagation that has to wrap the handler.

Today the workaround is either to fork the crate or to wrap the assembled Router from the outside, neither of which lets you actually replace the auth layer (axum doesn't support removing layers, and the inner middleware still runs).

Proposal

Make handlers pub (currently pub(crate) mod handlers;). Double-check anthropic — its module is already pub, but verify the handler fns inside are reachable. Embedders can then assemble their own Router:

let app = Router::new()
    .route("/v1/chat/completions", post(handlers::chat_completions::<S, P>))
    .route("/v1/messages",         post(anthropic::messages::<S, P>))
    // ... other routes ...
    .layer(my_auth_middleware)
    .layer(my_observability_middleware)
    .with_state(state);

proxy::router() stays as the convenient default for users who want the batteries-included shape; the change is purely additive — exposing what's already there.

Scope

One-line visibility change on mod handlers;. No API redesign, no behavior change. The default proxy::router() continues to work exactly as today.

Out of scope (deliberately)

Changes to Extension, KeyName, or Storage. This issue is only about giving embedders the same routing flexibility the crate has internally. Auth-layer pluggability (a resolver trait), typed identity in RequestContext, and additional storage backends are separate conversations that should each earn their merit on their own.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions