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. It ensures consistent
5//! URL construction, proper HTTP methods, and compliance with API rate limits.
6//!
7//! ## Architecture
8//!
9//! The endpoint system is built around three core components:
10//! 1. **`KiteEndpoint`** - Enum defining all available API endpoints
11//! 2. **`RateLimitCategory`** - Categorizes endpoints by their rate limits  
12//! 3. **`HttpMethod`** - Defines HTTP methods used by endpoints
13//!
14//! ## Rate Limiting
15//!
16//! KiteConnect API enforces different rate limits based on endpoint functionality:
17//! - **Quote data**: 1 request/second (most restrictive)
18//! - **Historical data**: 3 requests/second
19//! - **Order operations**: 10 requests/second  
20//! - **Standard operations**: 3 requests/second
21//!
22//! ## URL Construction
23//!
24//! Each endpoint knows how to construct its URL path and determine its HTTP method:
25//!
26//! ```rust
27//! use kiteconnect_async_wasm::connect::endpoints::{KiteEndpoint, HttpMethod};
28//!
29//! let endpoint = KiteEndpoint::Quote;
30//! assert_eq!(endpoint.path(), "/quote");
31//! assert_eq!(endpoint.method(), HttpMethod::GET);
32//! ```
33//!
34//! ## Thread Safety
35//!
36//! All types in this module are `Send + Sync` and can be safely used across
37//! multiple threads without synchronization.
38
39use std::time::Duration;
40
41/// HTTP method types for API requests
42///
43/// Represents the standard HTTP methods used by KiteConnect API endpoints.
44/// Each endpoint uses a specific HTTP method based on the operation type:
45/// - **GET**: Data retrieval (quotes, holdings, orders)
46/// - **POST**: Resource creation (place orders, create GTT)
47/// - **PUT**: Resource updates (modify orders, update GTT)
48/// - **DELETE**: Resource deletion (cancel orders, delete GTT)
49///
50/// # Example
51///
52/// ```rust
53/// use kiteconnect_async_wasm::connect::endpoints::HttpMethod;
54///
55/// let method = HttpMethod::GET;
56/// assert_eq!(method.as_str(), "GET");
57///
58/// // Check method type
59/// assert!(matches!(method, HttpMethod::GET));
60/// ```
61#[derive(Debug, Clone, PartialEq, Eq, Hash)]
62pub enum HttpMethod {
63    /// HTTP GET method for data retrieval
64    GET,
65    /// HTTP POST method for resource creation  
66    POST,
67    /// HTTP PUT method for resource updates
68    PUT,
69    /// HTTP DELETE method for resource deletion
70    DELETE,
71}
72
73impl HttpMethod {
74    /// Convert HTTP method to string for use with reqwest
75    ///
76    /// # Returns
77    ///
78    /// A static string slice representing the HTTP method
79    ///
80    /// # Example
81    ///
82    /// ```rust
83    /// use kiteconnect_async_wasm::connect::endpoints::HttpMethod;
84    ///
85    /// assert_eq!(HttpMethod::GET.as_str(), "GET");
86    /// assert_eq!(HttpMethod::POST.as_str(), "POST");
87    /// ```
88    pub fn as_str(&self) -> &'static str {
89        match self {
90            HttpMethod::GET => "GET",
91            HttpMethod::POST => "POST",
92            HttpMethod::PUT => "PUT",
93            HttpMethod::DELETE => "DELETE",
94        }
95    }
96}
97
98/// Rate limit categories based on official KiteConnect API documentation
99///
100/// KiteConnect API enforces different rate limits for different types of operations
101/// to ensure fair usage and system stability. Understanding these categories is
102/// crucial for building responsive applications that don't hit rate limits.
103///
104/// ## Category Details
105///
106/// - **Quote**: Real-time market data (most restrictive at 1 req/sec)
107/// - **Historical**: Historical market data (3 req/sec)  
108/// - **Orders**: Trading operations (10 req/sec)
109/// - **Standard**: General operations (3 req/sec)
110///
111/// ## Rate Limit Enforcement
112///
113/// Rate limits are enforced using a token bucket algorithm where:
114/// 1. Each category has a bucket with a specific capacity
115/// 2. Tokens are consumed for each request  
116/// 3. Tokens are refilled at the category's rate
117/// 4. Requests wait when no tokens are available
118///
119/// # Example
120///
121/// ```rust
122/// use kiteconnect_async_wasm::connect::endpoints::RateLimitCategory;
123///
124/// let category = RateLimitCategory::Quote;
125/// assert_eq!(category.requests_per_second(), 1);
126///
127/// let category = RateLimitCategory::Orders;
128/// assert_eq!(category.requests_per_second(), 10);
129/// ```
130#[derive(Debug, Clone, PartialEq, Eq, Hash)]
131pub enum RateLimitCategory {
132    /// Quote endpoints: 1 request/second
133    ///
134    /// Includes real-time market data endpoints like quotes, LTP, and OHLC.
135    /// These have the most restrictive limits due to the high-frequency nature
136    /// of market data and server load considerations.
137    Quote,
138
139    /// Historical candle endpoints: 3 requests/second
140    ///
141    /// Includes historical OHLC data endpoints. These limits balance the need
142    /// for historical analysis with server resource management.
143    Historical,
144
145    /// Order placement endpoints: 10 requests/second
146    ///
147    /// Includes order placement, modification, and cancellation endpoints.
148    /// Higher limits support active trading while preventing system abuse.
149    Orders,
150
151    /// All other endpoints: 3 requests/second
152    ///
153    /// Default category for portfolio, profile, and other general operations.
154    /// Provides good throughput for typical application usage patterns.
155    Standard,
156}
157
158impl RateLimitCategory {
159    /// Get the rate limit for this category (requests per second)
160    ///
161    /// # Returns
162    ///
163    /// The maximum number of requests allowed per second for this category
164    ///
165    /// # Example
166    ///
167    /// ```rust
168    /// use kiteconnect_async_wasm::connect::endpoints::RateLimitCategory;
169    ///
170    /// assert_eq!(RateLimitCategory::Quote.requests_per_second(), 1);
171    /// assert_eq!(RateLimitCategory::Historical.requests_per_second(), 3);  
172    /// assert_eq!(RateLimitCategory::Orders.requests_per_second(), 10);
173    /// assert_eq!(RateLimitCategory::Standard.requests_per_second(), 3);
174    /// ```
175    pub fn requests_per_second(&self) -> u32 {
176        match self {
177            RateLimitCategory::Quote => 1,
178            RateLimitCategory::Historical => 3,
179            RateLimitCategory::Orders => 10,
180            RateLimitCategory::Standard => 3,
181        }
182    }
183
184    /// Get the minimum delay between requests for this category
185    pub fn min_delay(&self) -> Duration {
186        Duration::from_millis(1000 / self.requests_per_second() as u64)
187    }
188}
189
190/// Endpoint configuration containing method, path, and rate limit info
191#[derive(Debug, Clone)]
192pub struct Endpoint {
193    /// HTTP method for this endpoint
194    pub method: HttpMethod,
195    /// URL path for this endpoint (without parameters)
196    pub path: &'static str,
197    /// Rate limit category for this endpoint
198    pub rate_limit_category: RateLimitCategory,
199    /// Whether this endpoint requires authentication
200    pub requires_auth: bool,
201}
202
203impl Endpoint {
204    /// Create a new endpoint configuration
205    pub const fn new(
206        method: HttpMethod,
207        path: &'static str,
208        rate_limit_category: RateLimitCategory,
209        requires_auth: bool,
210    ) -> Self {
211        Self {
212            method,
213            path,
214            rate_limit_category,
215            requires_auth,
216        }
217    }
218}
219
220/// Comprehensive enum of all KiteConnect API endpoints
221#[derive(Debug, Clone, PartialEq, Eq, Hash)]
222pub enum KiteEndpoint {
223    // === Authentication Endpoints ===
224    /// Generate session from request token
225    GenerateSession,
226    /// Invalidate session
227    InvalidateSession,
228    /// Renew access token
229    RenewAccessToken,
230    /// Invalidate refresh token
231    InvalidateRefreshToken,
232
233    // === User Profile Endpoints ===
234    /// Get user profile
235    Profile,
236    /// Get user margins
237    Margins,
238    /// Get segment-specific margins
239    MarginsSegment,
240
241    // === Portfolio Endpoints ===
242    /// Get holdings
243    Holdings,
244    /// Get positions
245    Positions,
246    /// Convert position
247    ConvertPosition,
248
249    // === Order Management Endpoints ===
250    /// Place order
251    PlaceOrder,
252    /// Modify order
253    ModifyOrder,
254    /// Cancel order
255    CancelOrder,
256    /// Get all orders
257    Orders,
258    /// Get order history
259    OrderHistory,
260    /// Get trades
261    Trades,
262    /// Get order trades
263    OrderTrades,
264
265    // === Market Data Endpoints (Quote Category) ===
266    /// Get real-time quotes
267    Quote,
268    /// Get OHLC data
269    OHLC,
270    /// Get Last Traded Price
271    LTP,
272
273    // === Market Data Endpoints (Historical Category) ===
274    /// Get historical data
275    HistoricalData,
276
277    // === Market Data Endpoints (Standard Category) ===
278    /// Get instruments list
279    Instruments,
280    /// Get MF instruments
281    MFInstruments,
282    /// Get trigger range
283    TriggerRange,
284    /// Get market margins
285    MarketMargins,
286    /// Calculate margin for orders (JSON POST)
287    CalculateOrderMargins,
288    /// Calculate margin for a basket of orders (JSON POST)
289    CalculateBasketMargins,
290    /// Calculate order-wise charges (JSON POST)
291    CalculateOrderCharges,
292
293    // === Mutual Fund Endpoints ===
294    /// Place MF order
295    PlaceMFOrder,
296    /// Cancel MF order
297    CancelMFOrder,
298    /// Get MF orders
299    MFOrders,
300    /// Get MF order info
301    MFOrderInfo,
302    /// Get MF holdings
303    MFHoldings,
304    /// Place SIP
305    PlaceSIP,
306    /// Modify SIP
307    ModifySIP,
308    /// Cancel SIP
309    CancelSIP,
310    /// Get SIPs
311    SIPs,
312    /// Get SIP info
313    SIPInfo,
314
315    // === GTT Endpoints ===
316    /// Place GTT
317    PlaceGTT,
318    /// Modify GTT
319    ModifyGTT,
320    /// Cancel GTT
321    CancelGTT,
322    /// Get GTTs
323    GTTs,
324    /// Get GTT info
325    GTTInfo,
326}
327
328impl KiteEndpoint {
329    /// Get endpoint configuration for this endpoint
330    pub fn config(&self) -> Endpoint {
331        match self {
332            // === Authentication Endpoints ===
333            KiteEndpoint::GenerateSession => Endpoint::new(
334                HttpMethod::POST,
335                "/session/token",
336                RateLimitCategory::Standard,
337                false,
338            ),
339            KiteEndpoint::InvalidateSession => Endpoint::new(
340                HttpMethod::DELETE,
341                "/session/token",
342                RateLimitCategory::Standard,
343                true,
344            ),
345            KiteEndpoint::RenewAccessToken => Endpoint::new(
346                HttpMethod::POST,
347                "/session/refresh_token",
348                RateLimitCategory::Standard,
349                true,
350            ),
351            KiteEndpoint::InvalidateRefreshToken => Endpoint::new(
352                HttpMethod::DELETE,
353                "/session/refresh_token",
354                RateLimitCategory::Standard,
355                true,
356            ),
357
358            // === User Profile Endpoints ===
359            KiteEndpoint::Profile => Endpoint::new(
360                HttpMethod::GET,
361                "/user/profile",
362                RateLimitCategory::Standard,
363                true,
364            ),
365            KiteEndpoint::Margins => Endpoint::new(
366                HttpMethod::GET,
367                "/user/margins",
368                RateLimitCategory::Standard,
369                true,
370            ),
371            KiteEndpoint::MarginsSegment => Endpoint::new(
372                HttpMethod::GET,
373                "/user/margins",
374                RateLimitCategory::Standard,
375                true,
376            ),
377
378            // === Portfolio Endpoints ===
379            KiteEndpoint::Holdings => Endpoint::new(
380                HttpMethod::GET,
381                "/portfolio/holdings",
382                RateLimitCategory::Standard,
383                true,
384            ),
385            KiteEndpoint::Positions => Endpoint::new(
386                HttpMethod::GET,
387                "/portfolio/positions",
388                RateLimitCategory::Standard,
389                true,
390            ),
391            KiteEndpoint::ConvertPosition => Endpoint::new(
392                HttpMethod::PUT,
393                "/portfolio/positions",
394                RateLimitCategory::Standard,
395                true,
396            ),
397
398            // === Order Management Endpoints ===
399            KiteEndpoint::PlaceOrder => {
400                Endpoint::new(HttpMethod::POST, "/orders", RateLimitCategory::Orders, true)
401            }
402            KiteEndpoint::ModifyOrder => {
403                Endpoint::new(HttpMethod::PUT, "/orders", RateLimitCategory::Orders, true)
404            }
405            KiteEndpoint::CancelOrder => Endpoint::new(
406                HttpMethod::DELETE,
407                "/orders",
408                RateLimitCategory::Orders,
409                true,
410            ),
411            KiteEndpoint::Orders => Endpoint::new(
412                HttpMethod::GET,
413                "/orders",
414                RateLimitCategory::Standard,
415                true,
416            ),
417            KiteEndpoint::OrderHistory => Endpoint::new(
418                HttpMethod::GET,
419                "/orders",
420                RateLimitCategory::Standard,
421                true,
422            ),
423            KiteEndpoint::Trades => Endpoint::new(
424                HttpMethod::GET,
425                "/trades",
426                RateLimitCategory::Standard,
427                true,
428            ),
429            KiteEndpoint::OrderTrades => Endpoint::new(
430                HttpMethod::GET,
431                "/orders",
432                RateLimitCategory::Standard,
433                true,
434            ),
435
436            // === Market Data Endpoints (Quote Category) ===
437            KiteEndpoint::Quote => {
438                Endpoint::new(HttpMethod::GET, "/quote", RateLimitCategory::Quote, true)
439            }
440            KiteEndpoint::OHLC => Endpoint::new(
441                HttpMethod::GET,
442                "/quote/ohlc",
443                RateLimitCategory::Quote,
444                true,
445            ),
446            KiteEndpoint::LTP => Endpoint::new(
447                HttpMethod::GET,
448                "/quote/ltp",
449                RateLimitCategory::Quote,
450                true,
451            ),
452
453            // === Market Data Endpoints (Historical Category) ===
454            KiteEndpoint::HistoricalData => Endpoint::new(
455                HttpMethod::GET,
456                "/instruments/historical",
457                RateLimitCategory::Historical,
458                true,
459            ),
460
461            // === Market Data Endpoints (Standard Category) ===
462            KiteEndpoint::Instruments => Endpoint::new(
463                HttpMethod::GET,
464                "/instruments",
465                RateLimitCategory::Standard,
466                true,
467            ),
468            KiteEndpoint::MFInstruments => Endpoint::new(
469                HttpMethod::GET,
470                "/mf/instruments",
471                RateLimitCategory::Standard,
472                true,
473            ),
474            KiteEndpoint::TriggerRange => Endpoint::new(
475                HttpMethod::GET,
476                "/instruments/trigger_range",
477                RateLimitCategory::Standard,
478                true,
479            ),
480            KiteEndpoint::MarketMargins => Endpoint::new(
481                HttpMethod::GET,
482                "/margins",
483                RateLimitCategory::Standard,
484                true,
485            ),
486            KiteEndpoint::CalculateOrderMargins => Endpoint::new(
487                HttpMethod::POST,
488                "/margins/orders",
489                RateLimitCategory::Standard,
490                true,
491            ),
492            KiteEndpoint::CalculateBasketMargins => Endpoint::new(
493                HttpMethod::POST,
494                "/margins/basket",
495                RateLimitCategory::Standard,
496                true,
497            ),
498            KiteEndpoint::CalculateOrderCharges => Endpoint::new(
499                HttpMethod::POST,
500                "/charges/orders",
501                RateLimitCategory::Standard,
502                true,
503            ),
504
505            // === Mutual Fund Endpoints ===
506            KiteEndpoint::PlaceMFOrder => Endpoint::new(
507                HttpMethod::POST,
508                "/mf/orders",
509                RateLimitCategory::Orders,
510                true,
511            ),
512            KiteEndpoint::CancelMFOrder => Endpoint::new(
513                HttpMethod::DELETE,
514                "/mf/orders",
515                RateLimitCategory::Orders,
516                true,
517            ),
518            KiteEndpoint::MFOrders => Endpoint::new(
519                HttpMethod::GET,
520                "/mf/orders",
521                RateLimitCategory::Standard,
522                true,
523            ),
524            KiteEndpoint::MFOrderInfo => Endpoint::new(
525                HttpMethod::GET,
526                "/mf/orders",
527                RateLimitCategory::Standard,
528                true,
529            ),
530            KiteEndpoint::MFHoldings => Endpoint::new(
531                HttpMethod::GET,
532                "/mf/holdings",
533                RateLimitCategory::Standard,
534                true,
535            ),
536            KiteEndpoint::PlaceSIP => Endpoint::new(
537                HttpMethod::POST,
538                "/mf/sips",
539                RateLimitCategory::Orders,
540                true,
541            ),
542            KiteEndpoint::ModifySIP => {
543                Endpoint::new(HttpMethod::PUT, "/mf/sips", RateLimitCategory::Orders, true)
544            }
545            KiteEndpoint::CancelSIP => Endpoint::new(
546                HttpMethod::DELETE,
547                "/mf/sips",
548                RateLimitCategory::Orders,
549                true,
550            ),
551            KiteEndpoint::SIPs => Endpoint::new(
552                HttpMethod::GET,
553                "/mf/sips",
554                RateLimitCategory::Standard,
555                true,
556            ),
557            KiteEndpoint::SIPInfo => Endpoint::new(
558                HttpMethod::GET,
559                "/mf/sips",
560                RateLimitCategory::Standard,
561                true,
562            ),
563
564            // === GTT Endpoints ===
565            KiteEndpoint::PlaceGTT => Endpoint::new(
566                HttpMethod::POST,
567                "/gtt/triggers",
568                RateLimitCategory::Orders,
569                true,
570            ),
571            KiteEndpoint::ModifyGTT => Endpoint::new(
572                HttpMethod::PUT,
573                "/gtt/triggers",
574                RateLimitCategory::Orders,
575                true,
576            ),
577            KiteEndpoint::CancelGTT => Endpoint::new(
578                HttpMethod::DELETE,
579                "/gtt/triggers",
580                RateLimitCategory::Orders,
581                true,
582            ),
583            KiteEndpoint::GTTs => Endpoint::new(
584                HttpMethod::GET,
585                "/gtt/triggers",
586                RateLimitCategory::Standard,
587                true,
588            ),
589            KiteEndpoint::GTTInfo => Endpoint::new(
590                HttpMethod::GET,
591                "/gtt/triggers",
592                RateLimitCategory::Standard,
593                true,
594            ),
595        }
596    }
597
598    /// Get the HTTP method for this endpoint
599    pub fn method(&self) -> HttpMethod {
600        self.config().method
601    }
602
603    /// Get the base path for this endpoint
604    pub fn path(&self) -> &'static str {
605        self.config().path
606    }
607
608    /// Get the rate limit category for this endpoint
609    pub fn rate_limit_category(&self) -> RateLimitCategory {
610        self.config().rate_limit_category
611    }
612
613    /// Check if this endpoint requires authentication
614    pub fn requires_auth(&self) -> bool {
615        self.config().requires_auth
616    }
617
618    /// Build the full URL path with dynamic segments
619    pub fn build_path(&self, segments: &[&str]) -> String {
620        let base_path = self.path();
621        if segments.is_empty() {
622            base_path.to_string()
623        } else {
624            format!("{}/{}", base_path, segments.join("/"))
625        }
626    }
627
628    /// Get all endpoints in a specific rate limit category
629    pub fn by_rate_limit_category(category: RateLimitCategory) -> Vec<KiteEndpoint> {
630        use KiteEndpoint::*;
631
632        let all_endpoints = vec![
633            GenerateSession,
634            InvalidateSession,
635            RenewAccessToken,
636            Profile,
637            Margins,
638            MarginsSegment,
639            Holdings,
640            Positions,
641            ConvertPosition,
642            PlaceOrder,
643            ModifyOrder,
644            CancelOrder,
645            Orders,
646            OrderHistory,
647            Trades,
648            OrderTrades,
649            Quote,
650            OHLC,
651            LTP,
652            HistoricalData,
653            Instruments,
654            MFInstruments,
655            TriggerRange,
656            MarketMargins,
657            CalculateOrderMargins,
658            CalculateBasketMargins,
659            CalculateOrderCharges,
660            PlaceMFOrder,
661            CancelMFOrder,
662            MFOrders,
663            MFOrderInfo,
664            MFHoldings,
665            PlaceSIP,
666            ModifySIP,
667            CancelSIP,
668            SIPs,
669            SIPInfo,
670            PlaceGTT,
671            ModifyGTT,
672            CancelGTT,
673            GTTs,
674            GTTInfo,
675        ];
676
677        all_endpoints
678            .into_iter()
679            .filter(|endpoint| endpoint.rate_limit_category() == category)
680            .collect()
681    }
682}
683
684#[cfg(test)]
685mod tests {
686    use super::*;
687
688    #[test]
689    fn test_rate_limit_categories() {
690        assert_eq!(RateLimitCategory::Quote.requests_per_second(), 1);
691        assert_eq!(RateLimitCategory::Historical.requests_per_second(), 3);
692        assert_eq!(RateLimitCategory::Orders.requests_per_second(), 10);
693        assert_eq!(RateLimitCategory::Standard.requests_per_second(), 3);
694    }
695
696    #[test]
697    fn test_endpoint_configuration() {
698        let quote_endpoint = KiteEndpoint::Quote;
699        let config = quote_endpoint.config();
700
701        assert_eq!(config.method, HttpMethod::GET);
702        assert_eq!(config.path, "/quote");
703        assert_eq!(config.rate_limit_category, RateLimitCategory::Quote);
704        assert!(config.requires_auth);
705    }
706
707    #[test]
708    fn test_build_path() {
709        let endpoint = KiteEndpoint::OrderHistory;
710        assert_eq!(endpoint.build_path(&[]), "/orders");
711        assert_eq!(endpoint.build_path(&["order_123"]), "/orders/order_123");
712        assert_eq!(
713            endpoint.build_path(&["order_123", "trades"]),
714            "/orders/order_123/trades"
715        );
716    }
717
718    #[test]
719    fn test_endpoint_methods() {
720        assert_eq!(KiteEndpoint::Quote.method(), HttpMethod::GET);
721        assert_eq!(KiteEndpoint::PlaceOrder.method(), HttpMethod::POST);
722        assert_eq!(KiteEndpoint::ModifyOrder.method(), HttpMethod::PUT);
723        assert_eq!(KiteEndpoint::CancelOrder.method(), HttpMethod::DELETE);
724    }
725
726    #[test]
727    fn test_rate_limit_grouping() {
728        let quote_endpoints = KiteEndpoint::by_rate_limit_category(RateLimitCategory::Quote);
729        assert!(quote_endpoints.contains(&KiteEndpoint::Quote));
730        assert!(quote_endpoints.contains(&KiteEndpoint::OHLC));
731        assert!(quote_endpoints.contains(&KiteEndpoint::LTP));
732
733        let historical_endpoints =
734            KiteEndpoint::by_rate_limit_category(RateLimitCategory::Historical);
735        assert!(historical_endpoints.contains(&KiteEndpoint::HistoricalData));
736
737        let order_endpoints = KiteEndpoint::by_rate_limit_category(RateLimitCategory::Orders);
738        assert!(order_endpoints.contains(&KiteEndpoint::PlaceOrder));
739        assert!(order_endpoints.contains(&KiteEndpoint::ModifyOrder));
740        assert!(order_endpoints.contains(&KiteEndpoint::CancelOrder));
741    }
742
743    #[test]
744    fn test_min_delay_calculation() {
745        assert_eq!(
746            RateLimitCategory::Quote.min_delay(),
747            Duration::from_millis(1000)
748        );
749        assert_eq!(
750            RateLimitCategory::Historical.min_delay(),
751            Duration::from_millis(333)
752        );
753        assert_eq!(
754            RateLimitCategory::Orders.min_delay(),
755            Duration::from_millis(100)
756        );
757        assert_eq!(
758            RateLimitCategory::Standard.min_delay(),
759            Duration::from_millis(333)
760        );
761    }
762}