kiteconnect_async_wasm/connect/
endpoints.rs

1//! # Endpoint Management Module
2//! 
3//! This module provides centralized endpoint definitions and rate limiting
4//! configuration for all KiteConnect API endpoints.
5
6use std::time::Duration;
7
8/// HTTP method types for API requests
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub enum HttpMethod {
11    GET,
12    POST,
13    PUT,
14    DELETE,
15}
16
17impl HttpMethod {
18    /// Convert to string for use with reqwest
19    pub fn as_str(&self) -> &'static str {
20        match self {
21            HttpMethod::GET => "GET",
22            HttpMethod::POST => "POST",
23            HttpMethod::PUT => "PUT",
24            HttpMethod::DELETE => "DELETE",
25        }
26    }
27}
28
29/// Rate limit categories based on official KiteConnect API documentation
30#[derive(Debug, Clone, PartialEq, Eq, Hash)]
31pub enum RateLimitCategory {
32    /// Quote endpoints: 1 request/second
33    Quote,
34    /// Historical candle endpoints: 3 requests/second
35    Historical,
36    /// Order placement endpoints: 10 requests/second
37    Orders,
38    /// All other endpoints: 10 requests/second
39    Standard,
40}
41
42impl RateLimitCategory {
43    /// Get the rate limit for this category (requests per second)
44    pub fn requests_per_second(&self) -> u32 {
45        match self {
46            RateLimitCategory::Quote => 1,
47            RateLimitCategory::Historical => 3,
48            RateLimitCategory::Orders => 10,
49            RateLimitCategory::Standard => 10,
50        }
51    }
52
53    /// Get the minimum delay between requests for this category
54    pub fn min_delay(&self) -> Duration {
55        Duration::from_millis(1000 / self.requests_per_second() as u64)
56    }
57}
58
59/// Endpoint configuration containing method, path, and rate limit info
60#[derive(Debug, Clone)]
61pub struct Endpoint {
62    /// HTTP method for this endpoint
63    pub method: HttpMethod,
64    /// URL path for this endpoint (without parameters)
65    pub path: &'static str,
66    /// Rate limit category for this endpoint
67    pub rate_limit_category: RateLimitCategory,
68    /// Whether this endpoint requires authentication
69    pub requires_auth: bool,
70}
71
72impl Endpoint {
73    /// Create a new endpoint configuration
74    pub const fn new(
75        method: HttpMethod,
76        path: &'static str,
77        rate_limit_category: RateLimitCategory,
78        requires_auth: bool,
79    ) -> Self {
80        Self {
81            method,
82            path,
83            rate_limit_category,
84            requires_auth,
85        }
86    }
87}
88
89/// Comprehensive enum of all KiteConnect API endpoints
90#[derive(Debug, Clone, PartialEq, Eq, Hash)]
91pub enum KiteEndpoint {
92    // === Authentication Endpoints ===
93    /// Generate login URL
94    LoginUrl,
95    /// Generate session from request token
96    GenerateSession,
97    /// Invalidate session
98    InvalidateSession,
99    /// Renew access token
100    RenewAccessToken,
101    /// Invalidate refresh token
102    InvalidateRefreshToken,
103    
104    // === User Profile Endpoints ===
105    /// Get user profile
106    Profile,
107    /// Get user margins
108    Margins,
109    /// Get segment-specific margins
110    MarginsSegment,
111    
112    // === Portfolio Endpoints ===
113    /// Get holdings
114    Holdings,
115    /// Get positions
116    Positions,
117    /// Convert position
118    ConvertPosition,
119    
120    // === Order Management Endpoints ===
121    /// Place order
122    PlaceOrder,
123    /// Modify order
124    ModifyOrder,
125    /// Cancel order
126    CancelOrder,
127    /// Get all orders
128    Orders,
129    /// Get order history
130    OrderHistory,
131    /// Get trades
132    Trades,
133    /// Get order trades
134    OrderTrades,
135    
136    // === Market Data Endpoints (Quote Category) ===
137    /// Get real-time quotes
138    Quote,
139    /// Get OHLC data
140    OHLC,
141    /// Get Last Traded Price
142    LTP,
143    
144    // === Market Data Endpoints (Historical Category) ===
145    /// Get historical data
146    HistoricalData,
147    
148    // === Market Data Endpoints (Standard Category) ===
149    /// Get instruments list
150    Instruments,
151    /// Get MF instruments
152    MFInstruments,
153    /// Get trigger range
154    TriggerRange,
155    /// Get market margins
156    MarketMargins,
157    
158    // === Mutual Fund Endpoints ===
159    /// Place MF order
160    PlaceMFOrder,
161    /// Cancel MF order
162    CancelMFOrder,
163    /// Get MF orders
164    MFOrders,
165    /// Get MF order info
166    MFOrderInfo,
167    /// Get MF holdings
168    MFHoldings,
169    /// Place SIP
170    PlaceSIP,
171    /// Modify SIP
172    ModifySIP,
173    /// Cancel SIP
174    CancelSIP,
175    /// Get SIPs
176    SIPs,
177    /// Get SIP info
178    SIPInfo,
179    
180    // === GTT Endpoints ===
181    /// Place GTT
182    PlaceGTT,
183    /// Modify GTT
184    ModifyGTT,
185    /// Cancel GTT
186    CancelGTT,
187    /// Get GTTs
188    GTTs,
189    /// Get GTT info
190    GTTInfo,
191}
192
193impl KiteEndpoint {
194    /// Get endpoint configuration for this endpoint
195    pub fn config(&self) -> Endpoint {
196        match self {
197            // === Authentication Endpoints ===
198            KiteEndpoint::LoginUrl => Endpoint::new(
199                HttpMethod::GET,
200                "/connect/login",
201                RateLimitCategory::Standard,
202                false,
203            ),
204            KiteEndpoint::GenerateSession => Endpoint::new(
205                HttpMethod::POST,
206                "/session/token",
207                RateLimitCategory::Standard,
208                false,
209            ),
210            KiteEndpoint::InvalidateSession => Endpoint::new(
211                HttpMethod::DELETE,
212                "/session/token",
213                RateLimitCategory::Standard,
214                true,
215            ),
216            KiteEndpoint::RenewAccessToken => Endpoint::new(
217                HttpMethod::POST,
218                "/session/refresh_token",
219                RateLimitCategory::Standard,
220                true,
221            ),
222            KiteEndpoint::InvalidateRefreshToken => Endpoint::new(
223                HttpMethod::DELETE,
224                "/session/refresh_token",
225                RateLimitCategory::Standard,
226                true,
227            ),
228            
229            // === User Profile Endpoints ===
230            KiteEndpoint::Profile => Endpoint::new(
231                HttpMethod::GET,
232                "/user/profile",
233                RateLimitCategory::Standard,
234                true,
235            ),
236            KiteEndpoint::Margins => Endpoint::new(
237                HttpMethod::GET,
238                "/user/margins",
239                RateLimitCategory::Standard,
240                true,
241            ),
242            KiteEndpoint::MarginsSegment => Endpoint::new(
243                HttpMethod::GET,
244                "/user/margins",
245                RateLimitCategory::Standard,
246                true,
247            ),
248            
249            // === Portfolio Endpoints ===
250            KiteEndpoint::Holdings => Endpoint::new(
251                HttpMethod::GET,
252                "/portfolio/holdings",
253                RateLimitCategory::Standard,
254                true,
255            ),
256            KiteEndpoint::Positions => Endpoint::new(
257                HttpMethod::GET,
258                "/portfolio/positions",
259                RateLimitCategory::Standard,
260                true,
261            ),
262            KiteEndpoint::ConvertPosition => Endpoint::new(
263                HttpMethod::PUT,
264                "/portfolio/positions",
265                RateLimitCategory::Standard,
266                true,
267            ),
268            
269            // === Order Management Endpoints ===
270            KiteEndpoint::PlaceOrder => Endpoint::new(
271                HttpMethod::POST,
272                "/orders",
273                RateLimitCategory::Orders,
274                true,
275            ),
276            KiteEndpoint::ModifyOrder => Endpoint::new(
277                HttpMethod::PUT,
278                "/orders",
279                RateLimitCategory::Orders,
280                true,
281            ),
282            KiteEndpoint::CancelOrder => Endpoint::new(
283                HttpMethod::DELETE,
284                "/orders",
285                RateLimitCategory::Orders,
286                true,
287            ),
288            KiteEndpoint::Orders => Endpoint::new(
289                HttpMethod::GET,
290                "/orders",
291                RateLimitCategory::Standard,
292                true,
293            ),
294            KiteEndpoint::OrderHistory => Endpoint::new(
295                HttpMethod::GET,
296                "/orders",
297                RateLimitCategory::Standard,
298                true,
299            ),
300            KiteEndpoint::Trades => Endpoint::new(
301                HttpMethod::GET,
302                "/trades",
303                RateLimitCategory::Standard,
304                true,
305            ),
306            KiteEndpoint::OrderTrades => Endpoint::new(
307                HttpMethod::GET,
308                "/orders",
309                RateLimitCategory::Standard,
310                true,
311            ),
312            
313            // === Market Data Endpoints (Quote Category) ===
314            KiteEndpoint::Quote => Endpoint::new(
315                HttpMethod::GET,
316                "/quote",
317                RateLimitCategory::Quote,
318                true,
319            ),
320            KiteEndpoint::OHLC => Endpoint::new(
321                HttpMethod::GET,
322                "/quote/ohlc",
323                RateLimitCategory::Quote,
324                true,
325            ),
326            KiteEndpoint::LTP => Endpoint::new(
327                HttpMethod::GET,
328                "/quote/ltp",
329                RateLimitCategory::Quote,
330                true,
331            ),
332            
333            // === Market Data Endpoints (Historical Category) ===
334            KiteEndpoint::HistoricalData => Endpoint::new(
335                HttpMethod::GET,
336                "/instruments/historical",
337                RateLimitCategory::Historical,
338                true,
339            ),
340            
341            // === Market Data Endpoints (Standard Category) ===
342            KiteEndpoint::Instruments => Endpoint::new(
343                HttpMethod::GET,
344                "/instruments",
345                RateLimitCategory::Standard,
346                true,
347            ),
348            KiteEndpoint::MFInstruments => Endpoint::new(
349                HttpMethod::GET,
350                "/mf/instruments",
351                RateLimitCategory::Standard,
352                true,
353            ),
354            KiteEndpoint::TriggerRange => Endpoint::new(
355                HttpMethod::GET,
356                "/instruments/trigger_range",
357                RateLimitCategory::Standard,
358                true,
359            ),
360            KiteEndpoint::MarketMargins => Endpoint::new(
361                HttpMethod::GET,
362                "/margins",
363                RateLimitCategory::Standard,
364                true,
365            ),
366            
367            // === Mutual Fund Endpoints ===
368            KiteEndpoint::PlaceMFOrder => Endpoint::new(
369                HttpMethod::POST,
370                "/mf/orders",
371                RateLimitCategory::Orders,
372                true,
373            ),
374            KiteEndpoint::CancelMFOrder => Endpoint::new(
375                HttpMethod::DELETE,
376                "/mf/orders",
377                RateLimitCategory::Orders,
378                true,
379            ),
380            KiteEndpoint::MFOrders => Endpoint::new(
381                HttpMethod::GET,
382                "/mf/orders",
383                RateLimitCategory::Standard,
384                true,
385            ),
386            KiteEndpoint::MFOrderInfo => Endpoint::new(
387                HttpMethod::GET,
388                "/mf/orders",
389                RateLimitCategory::Standard,
390                true,
391            ),
392            KiteEndpoint::MFHoldings => Endpoint::new(
393                HttpMethod::GET,
394                "/mf/holdings",
395                RateLimitCategory::Standard,
396                true,
397            ),
398            KiteEndpoint::PlaceSIP => Endpoint::new(
399                HttpMethod::POST,
400                "/mf/sips",
401                RateLimitCategory::Orders,
402                true,
403            ),
404            KiteEndpoint::ModifySIP => Endpoint::new(
405                HttpMethod::PUT,
406                "/mf/sips",
407                RateLimitCategory::Orders,
408                true,
409            ),
410            KiteEndpoint::CancelSIP => Endpoint::new(
411                HttpMethod::DELETE,
412                "/mf/sips",
413                RateLimitCategory::Orders,
414                true,
415            ),
416            KiteEndpoint::SIPs => Endpoint::new(
417                HttpMethod::GET,
418                "/mf/sips",
419                RateLimitCategory::Standard,
420                true,
421            ),
422            KiteEndpoint::SIPInfo => Endpoint::new(
423                HttpMethod::GET,
424                "/mf/sips",
425                RateLimitCategory::Standard,
426                true,
427            ),
428            
429            // === GTT Endpoints ===
430            KiteEndpoint::PlaceGTT => Endpoint::new(
431                HttpMethod::POST,
432                "/gtt/triggers",
433                RateLimitCategory::Orders,
434                true,
435            ),
436            KiteEndpoint::ModifyGTT => Endpoint::new(
437                HttpMethod::PUT,
438                "/gtt/triggers",
439                RateLimitCategory::Orders,
440                true,
441            ),
442            KiteEndpoint::CancelGTT => Endpoint::new(
443                HttpMethod::DELETE,
444                "/gtt/triggers",
445                RateLimitCategory::Orders,
446                true,
447            ),
448            KiteEndpoint::GTTs => Endpoint::new(
449                HttpMethod::GET,
450                "/gtt/triggers",
451                RateLimitCategory::Standard,
452                true,
453            ),
454            KiteEndpoint::GTTInfo => Endpoint::new(
455                HttpMethod::GET,
456                "/gtt/triggers",
457                RateLimitCategory::Standard,
458                true,
459            ),
460        }
461    }
462
463    /// Get the HTTP method for this endpoint
464    pub fn method(&self) -> HttpMethod {
465        self.config().method
466    }
467
468    /// Get the base path for this endpoint
469    pub fn path(&self) -> &'static str {
470        self.config().path
471    }
472
473    /// Get the rate limit category for this endpoint
474    pub fn rate_limit_category(&self) -> RateLimitCategory {
475        self.config().rate_limit_category
476    }
477
478    /// Check if this endpoint requires authentication
479    pub fn requires_auth(&self) -> bool {
480        self.config().requires_auth
481    }
482
483    /// Build the full URL path with dynamic segments
484    /// 
485    /// # Arguments
486    /// 
487    /// * `segments` - Dynamic path segments to append
488    /// 
489    /// # Example
490    /// 
491    /// ```rust
492    /// use kiteconnect_async_wasm::connect::endpoints::KiteEndpoint;
493    /// 
494    /// let endpoint = KiteEndpoint::OrderHistory;
495    /// let path = endpoint.build_path(&["order_id_123"]);
496    /// assert_eq!(path, "/orders/order_id_123");
497    /// ```
498    pub fn build_path(&self, segments: &[&str]) -> String {
499        let base_path = self.path();
500        if segments.is_empty() {
501            base_path.to_string()
502        } else {
503            format!("{}/{}", base_path, segments.join("/"))
504        }
505    }
506
507    /// Get all endpoints in a specific rate limit category
508    pub fn by_rate_limit_category(category: RateLimitCategory) -> Vec<KiteEndpoint> {
509        use KiteEndpoint::*;
510        
511        let all_endpoints = vec![
512            LoginUrl, GenerateSession, InvalidateSession, RenewAccessToken,
513            Profile, Margins, MarginsSegment,
514            Holdings, Positions, ConvertPosition,
515            PlaceOrder, ModifyOrder, CancelOrder, Orders, OrderHistory, Trades, OrderTrades,
516            Quote, OHLC, LTP,
517            HistoricalData,
518            Instruments, MFInstruments, TriggerRange, MarketMargins,
519            PlaceMFOrder, CancelMFOrder, MFOrders, MFOrderInfo, MFHoldings,
520            PlaceSIP, ModifySIP, CancelSIP, SIPs, SIPInfo,
521            PlaceGTT, ModifyGTT, CancelGTT, GTTs, GTTInfo,
522        ];
523        
524        all_endpoints
525            .into_iter()
526            .filter(|endpoint| endpoint.rate_limit_category() == category)
527            .collect()
528    }
529}
530
531#[cfg(test)]
532mod tests {
533    use super::*;
534
535    #[test]
536    fn test_rate_limit_categories() {
537        assert_eq!(RateLimitCategory::Quote.requests_per_second(), 1);
538        assert_eq!(RateLimitCategory::Historical.requests_per_second(), 3);
539        assert_eq!(RateLimitCategory::Orders.requests_per_second(), 10);
540        assert_eq!(RateLimitCategory::Standard.requests_per_second(), 10);
541    }
542
543    #[test]
544    fn test_endpoint_configuration() {
545        let quote_endpoint = KiteEndpoint::Quote;
546        let config = quote_endpoint.config();
547        
548        assert_eq!(config.method, HttpMethod::GET);
549        assert_eq!(config.path, "/quote");
550        assert_eq!(config.rate_limit_category, RateLimitCategory::Quote);
551        assert!(config.requires_auth);
552    }
553
554    #[test]
555    fn test_build_path() {
556        let endpoint = KiteEndpoint::OrderHistory;
557        assert_eq!(endpoint.build_path(&[]), "/orders");
558        assert_eq!(endpoint.build_path(&["order_123"]), "/orders/order_123");
559        assert_eq!(endpoint.build_path(&["order_123", "trades"]), "/orders/order_123/trades");
560    }
561
562    #[test]
563    fn test_endpoint_methods() {
564        assert_eq!(KiteEndpoint::Quote.method(), HttpMethod::GET);
565        assert_eq!(KiteEndpoint::PlaceOrder.method(), HttpMethod::POST);
566        assert_eq!(KiteEndpoint::ModifyOrder.method(), HttpMethod::PUT);
567        assert_eq!(KiteEndpoint::CancelOrder.method(), HttpMethod::DELETE);
568    }
569
570    #[test]
571    fn test_rate_limit_grouping() {
572        let quote_endpoints = KiteEndpoint::by_rate_limit_category(RateLimitCategory::Quote);
573        assert!(quote_endpoints.contains(&KiteEndpoint::Quote));
574        assert!(quote_endpoints.contains(&KiteEndpoint::OHLC));
575        assert!(quote_endpoints.contains(&KiteEndpoint::LTP));
576        
577        let historical_endpoints = KiteEndpoint::by_rate_limit_category(RateLimitCategory::Historical);
578        assert!(historical_endpoints.contains(&KiteEndpoint::HistoricalData));
579        
580        let order_endpoints = KiteEndpoint::by_rate_limit_category(RateLimitCategory::Orders);
581        assert!(order_endpoints.contains(&KiteEndpoint::PlaceOrder));
582        assert!(order_endpoints.contains(&KiteEndpoint::ModifyOrder));
583        assert!(order_endpoints.contains(&KiteEndpoint::CancelOrder));
584    }
585
586    #[test]
587    fn test_authentication_requirements() {
588        assert!(!KiteEndpoint::LoginUrl.requires_auth());
589        assert!(!KiteEndpoint::GenerateSession.requires_auth());
590        assert!(KiteEndpoint::Profile.requires_auth());
591        assert!(KiteEndpoint::Holdings.requires_auth());
592        assert!(KiteEndpoint::PlaceOrder.requires_auth());
593    }
594
595    #[test]
596    fn test_min_delay_calculation() {
597        assert_eq!(RateLimitCategory::Quote.min_delay(), Duration::from_millis(1000));
598        assert_eq!(RateLimitCategory::Historical.min_delay(), Duration::from_millis(333));
599        assert_eq!(RateLimitCategory::Orders.min_delay(), Duration::from_millis(100));
600        assert_eq!(RateLimitCategory::Standard.min_delay(), Duration::from_millis(100));
601    }
602}