1use std::time::Duration;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub enum HttpMethod {
11 GET,
12 POST,
13 PUT,
14 DELETE,
15}
16
17impl HttpMethod {
18 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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
31pub enum RateLimitCategory {
32 Quote,
34 Historical,
36 Orders,
38 Standard,
40}
41
42impl RateLimitCategory {
43 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 pub fn min_delay(&self) -> Duration {
55 Duration::from_millis(1000 / self.requests_per_second() as u64)
56 }
57}
58
59#[derive(Debug, Clone)]
61pub struct Endpoint {
62 pub method: HttpMethod,
64 pub path: &'static str,
66 pub rate_limit_category: RateLimitCategory,
68 pub requires_auth: bool,
70}
71
72impl Endpoint {
73 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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
91pub enum KiteEndpoint {
92 LoginUrl,
95 GenerateSession,
97 InvalidateSession,
99 RenewAccessToken,
101 InvalidateRefreshToken,
103
104 Profile,
107 Margins,
109 MarginsSegment,
111
112 Holdings,
115 Positions,
117 ConvertPosition,
119
120 PlaceOrder,
123 ModifyOrder,
125 CancelOrder,
127 Orders,
129 OrderHistory,
131 Trades,
133 OrderTrades,
135
136 Quote,
139 OHLC,
141 LTP,
143
144 HistoricalData,
147
148 Instruments,
151 MFInstruments,
153 TriggerRange,
155 MarketMargins,
157
158 PlaceMFOrder,
161 CancelMFOrder,
163 MFOrders,
165 MFOrderInfo,
167 MFHoldings,
169 PlaceSIP,
171 ModifySIP,
173 CancelSIP,
175 SIPs,
177 SIPInfo,
179
180 PlaceGTT,
183 ModifyGTT,
185 CancelGTT,
187 GTTs,
189 GTTInfo,
191}
192
193impl KiteEndpoint {
194 pub fn config(&self) -> Endpoint {
196 match self {
197 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 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 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 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 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 KiteEndpoint::HistoricalData => Endpoint::new(
335 HttpMethod::GET,
336 "/instruments/historical",
337 RateLimitCategory::Historical,
338 true,
339 ),
340
341 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 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 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 pub fn method(&self) -> HttpMethod {
465 self.config().method
466 }
467
468 pub fn path(&self) -> &'static str {
470 self.config().path
471 }
472
473 pub fn rate_limit_category(&self) -> RateLimitCategory {
475 self.config().rate_limit_category
476 }
477
478 pub fn requires_auth(&self) -> bool {
480 self.config().requires_auth
481 }
482
483 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 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}