Skip to main content

px_core/exchange/
manifest.rs

1use crate::models::MarketStatus;
2
3/// Complete auditable manifest for an exchange.
4/// When opened, shows SOURCE and TRANSFORMATION side-by-side.
5#[derive(Debug, Clone)]
6pub struct ExchangeManifest {
7    // ========================================
8    // SECTION 1: CONNECTION AUDIT (Where do we go?)
9    // ========================================
10    /// Exchange identifier (e.g., "kalshi", "polymarket")
11    pub id: &'static str,
12
13    /// Human-readable exchange name
14    pub name: &'static str,
15
16    /// Base API URL
17    pub base_url: &'static str,
18
19    /// Markets list endpoint (relative to base_url)
20    pub markets_endpoint: &'static str,
21
22    /// Pagination configuration
23    pub pagination: PaginationConfig,
24
25    /// Rate limiting configuration
26    pub rate_limit: RateLimitConfig,
27
28    // ========================================
29    // SECTION 2: DATA AUDIT (How do we map it?)
30    // ========================================
31    /// Field mappings from raw exchange JSON to Market
32    pub field_mappings: &'static [FieldMapping],
33
34    /// Status value mappings (exchange status -> MarketStatus)
35    pub status_map: &'static [(&'static str, MarketStatus)],
36}
37
38impl ExchangeManifest {
39    /// Look up the MarketStatus for a given exchange status string.
40    /// Status map entries should be lowercase at definition time for O(n) without allocation.
41    pub fn map_status(&self, exchange_status: &str) -> Option<MarketStatus> {
42        self.status_map
43            .iter()
44            .find(|(s, _)| s.eq_ignore_ascii_case(exchange_status))
45            .map(|(_, status)| *status)
46    }
47
48    /// Get a field mapping by unified field name.
49    pub fn get_field_mapping(&self, unified_field: &str) -> Option<&FieldMapping> {
50        self.field_mappings
51            .iter()
52            .find(|m| m.unified_field == unified_field)
53    }
54}
55
56#[derive(Debug, Clone, Copy)]
57pub struct PaginationConfig {
58    /// Pagination style: Cursor, Offset, Page
59    pub style: PaginationStyle,
60    /// Maximum items per page
61    pub max_page_size: usize,
62    /// Query param name for limit
63    pub limit_param: &'static str,
64    /// Query param name for cursor/offset
65    pub cursor_param: &'static str,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum PaginationStyle {
70    /// Cursor-based pagination (Kalshi)
71    Cursor,
72    /// Offset-based pagination (Polymarket)
73    Offset,
74    /// Page-number pagination (Opinion, 1-indexed)
75    PageNumber,
76    /// No pagination supported - endpoint returns all data in single call
77    None,
78}
79
80#[derive(Debug, Clone, Copy)]
81pub struct RateLimitConfig {
82    /// Requests per second
83    pub requests_per_second: u32,
84    /// Burst limit
85    pub burst: u32,
86}
87
88/// Mapping from raw exchange JSON field to unified field.
89#[derive(Debug, Clone)]
90pub struct FieldMapping {
91    /// Target field in Market
92    pub unified_field: &'static str,
93    /// Source path(s) in raw JSON (fallback chain)
94    pub source_paths: &'static [&'static str],
95    /// Transformation to apply
96    pub transform: Transform,
97    /// Whether field can be null
98    pub nullable: bool,
99}
100
101/// Transformation to apply when mapping a field.
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum Transform {
104    /// No transformation - use value directly
105    Direct,
106    /// Divide by 100 (Kalshi prices: cents -> decimal)
107    CentsToDollars,
108    /// Unix timestamp (seconds) to DateTime
109    UnixSecsToDateTime,
110    /// Unix timestamp (milliseconds) to DateTime
111    UnixMillisToDateTime,
112    /// ISO8601 string to DateTime
113    Iso8601ToDateTime,
114    /// String/Float -> i64
115    ParseInt,
116    /// String -> f64
117    ParseFloat,
118    /// Extract element at index from JSON array
119    JsonArrayIndex(usize),
120    /// Nested path extraction (dot-notation handled by source_paths)
121    NestedPath,
122}