kiteconnect_async_wasm/models/auth/
user.rs1use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct UserProfile {
13 pub user_id: String,
15
16 pub user_name: String,
18
19 pub user_shortname: String,
21
22 pub user_type: String,
24
25 pub email: String,
27
28 #[serde(default)]
30 pub avatar_url: Option<String>,
31
32 pub broker: String,
34
35 pub exchanges: Vec<String>,
37
38 pub products: Vec<String>,
40
41 pub order_types: Vec<String>,
43
44 #[serde(default)]
46 pub meta: Option<UserMeta>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct UserMeta {
52 #[serde(default)]
54 pub demat_consent: String,
55}
56
57impl UserProfile {
58 pub fn has_exchange(&self, exchange: &str) -> bool {
60 self.exchanges.iter().any(|e| e == exchange)
61 }
62
63 pub fn has_product(&self, product: &str) -> bool {
65 self.products.iter().any(|p| p == product)
66 }
67
68 pub fn has_order_type(&self, order_type: &str) -> bool {
70 self.order_types.iter().any(|o| o == order_type)
71 }
72
73 pub fn display_name(&self) -> &str {
75 if !self.user_name.is_empty() {
76 &self.user_name
77 } else {
78 &self.user_shortname
79 }
80 }
81
82 pub fn is_complete(&self) -> bool {
84 !self.user_id.is_empty() && !self.email.is_empty() && !self.exchanges.is_empty()
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
90#[serde(rename_all = "lowercase")]
91pub enum UserType {
92 Individual,
93 Corporate,
94 Partnership,
95 Huf, }
97
98impl UserType {
99 pub fn supports_family_account(&self) -> bool {
101 matches!(self, UserType::Huf)
102 }
103
104 pub fn is_individual(&self) -> bool {
106 matches!(self, UserType::Individual)
107 }
108
109 pub fn is_business(&self) -> bool {
111 matches!(self, UserType::Corporate | UserType::Partnership)
112 }
113}
114
115impl std::fmt::Display for UserType {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 match self {
118 UserType::Individual => write!(f, "individual"),
119 UserType::Corporate => write!(f, "corporate"),
120 UserType::Partnership => write!(f, "partnership"),
121 UserType::Huf => write!(f, "huf"),
122 }
123 }
124}
125
126impl From<String> for UserType {
127 fn from(s: String) -> Self {
128 match s.to_lowercase().as_str() {
129 "individual" => UserType::Individual,
130 "corporate" => UserType::Corporate,
131 "partnership" => UserType::Partnership,
132 "huf" => UserType::Huf,
133 _ => UserType::Individual, }
135 }
136}
137
138impl From<&str> for UserType {
139 fn from(s: &str) -> Self {
140 Self::from(s.to_string())
141 }
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct AccountStatus {
147 pub active: bool,
149
150 pub trading_enabled: bool,
152
153 #[serde(default)]
155 pub created_at: Option<DateTime<Utc>>,
156
157 #[serde(default)]
159 pub last_login: Option<DateTime<Utc>>,
160
161 #[serde(default)]
163 pub restrictions: Vec<String>,
164
165 #[serde(default)]
167 pub kyc_status: Option<String>,
168}
169
170impl AccountStatus {
171 pub fn can_trade(&self) -> bool {
173 self.active && self.trading_enabled && self.restrictions.is_empty()
174 }
175
176 pub fn has_restrictions(&self) -> bool {
178 !self.restrictions.is_empty()
179 }
180
181 pub fn is_kyc_complete(&self) -> bool {
183 self.kyc_status
184 .as_ref()
185 .map(|status| status.to_lowercase() == "complete")
186 .unwrap_or(false)
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn test_user_profile() {
196 let profile = UserProfile {
197 user_id: "TEST123".to_string(),
198 user_name: "Test User".to_string(),
199 user_shortname: "testuser".to_string(),
200 user_type: "individual".to_string(),
201 email: "test@example.com".to_string(),
202 avatar_url: None,
203 broker: "ZERODHA".to_string(),
204 exchanges: vec!["NSE".to_string(), "BSE".to_string()],
205 products: vec!["CNC".to_string(), "MIS".to_string()],
206 order_types: vec!["MARKET".to_string(), "LIMIT".to_string()],
207 meta: None,
208 };
209
210 assert!(profile.is_complete());
211 assert!(profile.has_exchange("NSE"));
212 assert!(!profile.has_exchange("MCX"));
213 assert_eq!(profile.display_name(), "Test User");
214 }
215
216 #[test]
217 fn test_user_type() {
218 let individual = UserType::Individual;
219 assert!(individual.is_individual());
220 assert!(!individual.is_business());
221 assert!(!individual.supports_family_account());
222
223 let corporate = UserType::Corporate;
224 assert!(!corporate.is_individual());
225 assert!(corporate.is_business());
226
227 let huf = UserType::Huf;
228 assert!(huf.supports_family_account());
229
230 assert_eq!(UserType::from("individual"), UserType::Individual);
232 assert_eq!(UserType::from("corporate"), UserType::Corporate);
233 assert_eq!(UserType::from("unknown"), UserType::Individual); }
235
236 #[test]
237 fn test_account_status() {
238 let mut status = AccountStatus {
239 active: true,
240 trading_enabled: true,
241 created_at: None,
242 last_login: None,
243 restrictions: vec![],
244 kyc_status: Some("complete".to_string()),
245 };
246
247 assert!(status.can_trade());
248 assert!(!status.has_restrictions());
249 assert!(status.is_kyc_complete());
250
251 status.restrictions.push("day_trading_disabled".to_string());
253 assert!(!status.can_trade());
254 assert!(status.has_restrictions());
255
256 status.kyc_status = Some("pending".to_string());
258 assert!(!status.is_kyc_complete());
259 }
260}