indodax_cli/mcp/
service.rs1use std::fmt;
2use std::str::FromStr;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum ServiceGroup {
7 Market,
8 Account,
9 Trade,
10 Funding,
11 Paper,
12 Auth,
13 Alert,
14}
15
16impl ServiceGroup {
17 pub fn all() -> Vec<ServiceGroup> {
19 vec![
20 ServiceGroup::Market,
21 ServiceGroup::Account,
22 ServiceGroup::Trade,
23 ServiceGroup::Funding,
24 ServiceGroup::Paper,
25 ServiceGroup::Auth,
26 ServiceGroup::Alert,
27 ]
28 }
29
30 pub fn default_groups() -> Vec<ServiceGroup> {
32 vec![ServiceGroup::Market, ServiceGroup::Account, ServiceGroup::Paper, ServiceGroup::Alert]
33 }
34
35 pub fn is_dangerous(&self) -> bool {
37 matches!(self, ServiceGroup::Trade | ServiceGroup::Funding)
38 }
39
40 pub fn parse(s: &str) -> Result<Vec<ServiceGroup>, String> {
44 let trimmed = s.trim();
45 if trimmed.eq_ignore_ascii_case("all") {
46 return Ok(Self::all());
47 }
48
49 let mut groups = Vec::new();
50 for part in trimmed.split(',') {
51 let part = part.trim();
52 if part.is_empty() {
53 continue;
54 }
55 match part.to_ascii_lowercase().as_str() {
56 "market" => groups.push(ServiceGroup::Market),
57 "account" => groups.push(ServiceGroup::Account),
58 "trade" => groups.push(ServiceGroup::Trade),
59 "funding" => groups.push(ServiceGroup::Funding),
60 "paper" => groups.push(ServiceGroup::Paper),
61 "auth" => groups.push(ServiceGroup::Auth),
62 "alert" => groups.push(ServiceGroup::Alert),
63 _ => return Err(format!("Unknown service group: '{}'", part)),
64 }
65 }
66
67 if groups.is_empty() {
68 return Err("No service groups specified".into());
69 }
70
71 Ok(groups)
72 }
73}
74
75impl fmt::Display for ServiceGroup {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 match self {
78 ServiceGroup::Market => write!(f, "market"),
79 ServiceGroup::Account => write!(f, "account"),
80 ServiceGroup::Trade => write!(f, "trade"),
81 ServiceGroup::Funding => write!(f, "funding"),
82 ServiceGroup::Paper => write!(f, "paper"),
83 ServiceGroup::Auth => write!(f, "auth"),
84 ServiceGroup::Alert => write!(f, "alert"),
85 }
86 }
87}
88
89impl FromStr for ServiceGroup {
90 type Err = String;
91 fn from_str(s: &str) -> Result<Self, Self::Err> {
92 match s.to_ascii_lowercase().as_str() {
93 "market" => Ok(ServiceGroup::Market),
94 "account" => Ok(ServiceGroup::Account),
95 "trade" => Ok(ServiceGroup::Trade),
96 "funding" => Ok(ServiceGroup::Funding),
97 "paper" => Ok(ServiceGroup::Paper),
98 "auth" => Ok(ServiceGroup::Auth),
99 "alert" => Ok(ServiceGroup::Alert),
100 _ => Err(format!("Unknown service group: '{}'", s)),
101 }
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_parse_empty() {
111 let result = ServiceGroup::parse("");
112 assert!(result.is_err());
113 }
114
115 #[test]
116 fn test_parse_single() {
117 let groups = ServiceGroup::parse("market").unwrap();
118 assert_eq!(groups, vec![ServiceGroup::Market]);
119 }
120
121 #[test]
122 fn test_parse_multiple() {
123 let groups = ServiceGroup::parse("market,trade,paper").unwrap();
124 assert_eq!(
125 groups,
126 vec![ServiceGroup::Market, ServiceGroup::Trade, ServiceGroup::Paper]
127 );
128 }
129
130 #[test]
131 fn test_parse_all() {
132 let groups = ServiceGroup::parse("all").unwrap();
133 assert_eq!(groups.len(), 7);
134 assert!(groups.contains(&ServiceGroup::Market));
135 assert!(groups.contains(&ServiceGroup::Funding));
136 assert!(groups.contains(&ServiceGroup::Alert));
137 }
138
139 #[test]
140 fn test_parse_case_insensitive() {
141 let groups = ServiceGroup::parse("Market,TRADE").unwrap();
142 assert_eq!(groups, vec![ServiceGroup::Market, ServiceGroup::Trade]);
143 }
144
145 #[test]
146 fn test_parse_unknown_group() {
147 let result = ServiceGroup::parse("market,unknown");
148 assert!(result.is_err());
149 assert!(result.unwrap_err().contains("unknown"));
150 }
151
152 #[test]
153 fn test_parse_with_spaces() {
154 let groups = ServiceGroup::parse(" market , paper ").unwrap();
155 assert_eq!(groups, vec![ServiceGroup::Market, ServiceGroup::Paper]);
156 }
157
158 #[test]
159 fn test_default_groups() {
160 let groups = ServiceGroup::default_groups();
161 assert_eq!(groups.len(), 4);
162 assert!(groups.contains(&ServiceGroup::Market));
163 assert!(groups.contains(&ServiceGroup::Account));
164 assert!(groups.contains(&ServiceGroup::Paper));
165 assert!(groups.contains(&ServiceGroup::Alert));
166 }
167
168 #[test]
169 fn test_is_dangerous() {
170 assert!(!ServiceGroup::Market.is_dangerous());
171 assert!(!ServiceGroup::Account.is_dangerous());
172 assert!(ServiceGroup::Trade.is_dangerous());
173 assert!(ServiceGroup::Funding.is_dangerous());
174 assert!(!ServiceGroup::Paper.is_dangerous());
175 assert!(!ServiceGroup::Auth.is_dangerous());
176 }
177
178 #[test]
179 fn test_display() {
180 assert_eq!(ServiceGroup::Market.to_string(), "market");
181 assert_eq!(ServiceGroup::Trade.to_string(), "trade");
182 }
183
184 #[test]
185 fn test_from_str() {
186 assert_eq!("market".parse::<ServiceGroup>().unwrap(), ServiceGroup::Market);
187 assert_eq!("TRADE".parse::<ServiceGroup>().unwrap(), ServiceGroup::Trade);
188 assert!("invalid".parse::<ServiceGroup>().is_err());
189 }
190
191 #[test]
192 fn test_all_contains_all() {
193 let all = ServiceGroup::all();
194 assert_eq!(all.len(), 7);
195 for group in &[ServiceGroup::Market, ServiceGroup::Account, ServiceGroup::Trade,
196 ServiceGroup::Funding, ServiceGroup::Paper, ServiceGroup::Auth,
197 ServiceGroup::Alert] {
198 assert!(all.contains(group));
199 }
200 }
201}