Skip to main content

scope/web/api/
venues.rs

1//! Venue listing API handler.
2//!
3//! GET /api/venues — Returns available exchange venues and their capabilities.
4
5use crate::market::VenueRegistry;
6use axum::Json;
7use axum::http::StatusCode;
8use axum::response::IntoResponse;
9
10/// GET /api/venues — List available exchange venues.
11pub async fn handle() -> impl IntoResponse {
12    let registry = match VenueRegistry::load() {
13        Ok(r) => r,
14        Err(e) => {
15            return (
16                StatusCode::INTERNAL_SERVER_ERROR,
17                Json(serde_json::json!({ "error": format!("Failed to load venue registry: {e}") })),
18            )
19                .into_response();
20        }
21    };
22
23    let venues: Vec<serde_json::Value> = registry
24        .list()
25        .iter()
26        .filter_map(|id| {
27            registry.get(id).map(|desc| {
28                serde_json::json!({
29                    "id": desc.id,
30                    "name": desc.name,
31                    "base_url": desc.base_url,
32                    "capabilities": desc.capability_names(),
33                })
34            })
35        })
36        .collect();
37
38    let output = serde_json::json!({
39        "venues": venues,
40        "total": registry.len(),
41        "user_venues_dir": VenueRegistry::user_venues_dir().display().to_string(),
42    });
43
44    Json(output).into_response()
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50    use axum::response::IntoResponse;
51
52    #[tokio::test]
53    async fn test_handle_venues() {
54        let response = handle().await.into_response();
55        let status = response.status();
56        assert!(status.is_success());
57    }
58
59    #[tokio::test]
60    async fn test_handle_venues_returns_all_built_in() {
61        let response = handle().await.into_response();
62        let status = response.status();
63        assert!(status.is_success());
64
65        // Extract body and verify structure
66        let body = axum::body::to_bytes(response.into_body(), 1024 * 1024)
67            .await
68            .unwrap();
69        let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
70        assert!(json["total"].as_u64().unwrap() >= 11);
71        assert!(json["venues"].is_array());
72        let venues = json["venues"].as_array().unwrap();
73        assert!(venues.iter().any(|v| v["id"] == "binance"));
74        assert!(venues.iter().any(|v| v["id"] == "kraken"));
75        // Verify each venue has expected fields
76        for venue in venues {
77            assert!(venue["id"].is_string());
78            assert!(venue["name"].is_string());
79            assert!(venue["base_url"].is_string());
80            assert!(venue["capabilities"].is_array());
81        }
82    }
83
84    #[tokio::test]
85    async fn test_handle_venues_response_structure() {
86        let response = handle().await.into_response();
87        assert!(response.status().is_success());
88        let body = axum::body::to_bytes(response.into_body(), 1024 * 1024)
89            .await
90            .unwrap();
91        let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
92        assert!(json["total"].as_u64().unwrap() >= 11);
93        let venues = json["venues"].as_array().unwrap();
94        for v in venues {
95            assert!(v["id"].is_string());
96            assert!(v["name"].is_string());
97            assert!(v["capabilities"].is_array());
98        }
99        assert!(json["user_venues_dir"].is_string());
100    }
101}