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