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}