alpaca-mock 0.27.2

Executable mock server for alpaca-rust trading and market-data flows
Documentation
use axum::{
    Router,
    body::Body,
    extract::State,
    http::Request,
    middleware::{self, Next},
    response::Response,
    routing::{get, post},
};

use crate::auth::{AuthenticatedAccount, MockHttpError, extract_auth};
use crate::handlers;
use crate::state::{MarketDataBridgeError, MockServerState};

pub fn build_app() -> Router {
    build_app_with_state(MockServerState::new())
}

pub fn build_app_from_env() -> Result<Router, MarketDataBridgeError> {
    Ok(build_app_with_state(MockServerState::from_env()?))
}

pub fn build_app_with_state(state: MockServerState) -> Router {
    let trading_router = Router::new()
        .route("/v2/account", get(handlers::account_get))
        .route("/v2/account/activities", get(handlers::activities_list))
        .route(
            "/v2/account/activities/{activity_type}",
            get(handlers::activities_by_type),
        )
        .route(
            "/v2/orders",
            get(handlers::orders_list)
                .post(handlers::orders_create)
                .delete(handlers::orders_cancel_all),
        )
        .route(
            "/v2/orders/{order_id}",
            get(handlers::orders_get)
                .patch(handlers::orders_replace)
                .delete(handlers::orders_cancel),
        )
        .route(
            "/v2/orders:by_client_order_id",
            get(handlers::orders_get_by_client_order_id),
        )
        .route(
            "/v2/positions",
            get(handlers::positions_list).delete(handlers::positions_close_all),
        )
        .route(
            "/v2/positions/{symbol_or_asset_id}",
            get(handlers::positions_get).delete(handlers::positions_close),
        )
        .route(
            "/v2/positions/{symbol_or_contract_id}/exercise",
            post(handlers::positions_exercise),
        )
        .route(
            "/v2/positions/{symbol_or_contract_id}/do-not-exercise",
            post(handlers::positions_do_not_exercise),
        )
        .route_layer(middleware::from_fn_with_state(
            state.clone(),
            require_trading_auth,
        ));

    Router::new()
        .route("/health", get(handlers::health))
        .route("/reset", post(handlers::admin_reset))
        .route("/admin/state", get(handlers::admin_state))
        .route("/admin/reset", post(handlers::admin_reset))
        .route("/admin/faults/http", post(handlers::admin_set_http_fault))
        .merge(trading_router)
        .with_state(state)
}

async fn require_trading_auth(
    State(state): State<MockServerState>,
    mut request: Request<Body>,
    next: Next,
) -> Result<Response, MockHttpError> {
    let auth = extract_auth(request.headers())?;
    let _secret_key = auth.secret_key;
    state.ensure_account(&auth.api_key);

    if let Some(fault) = state.take_http_fault() {
        let status = fault.status_code().map_err(MockHttpError::bad_request)?;
        return Err(MockHttpError::with_status(status, fault.message));
    }

    request.extensions_mut().insert(AuthenticatedAccount {
        api_key: auth.api_key,
    });

    Ok(next.run(request).await)
}