1use std::fmt;
4use std::str::FromStr;
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(try_from = "String", into = "String")]
11pub enum Scope {
12 QotRead,
14 AccRead,
16 TradeSimulate,
18 TradeReal,
20 TradeUnlock,
22}
23
24impl Scope {
25 pub const ALL: &'static [Scope] = &[
26 Scope::QotRead,
27 Scope::AccRead,
28 Scope::TradeSimulate,
29 Scope::TradeReal,
30 Scope::TradeUnlock,
31 ];
32
33 pub fn as_str(&self) -> &'static str {
34 match self {
35 Scope::QotRead => "qot:read",
36 Scope::AccRead => "acc:read",
37 Scope::TradeSimulate => "trade:simulate",
38 Scope::TradeReal => "trade:real",
39 Scope::TradeUnlock => "trade:unlock",
40 }
41 }
42}
43
44impl fmt::Display for Scope {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 f.write_str(self.as_str())
47 }
48}
49
50#[derive(Debug, thiserror::Error)]
51#[error(
52 "unknown scope {0:?} (valid: qot:read, acc:read, trade:simulate, trade:real, trade:unlock)"
53)]
54pub struct ScopeParseError(pub String);
55
56impl FromStr for Scope {
57 type Err = ScopeParseError;
58
59 fn from_str(s: &str) -> Result<Self, Self::Err> {
60 match s {
61 "qot:read" => Ok(Scope::QotRead),
62 "acc:read" => Ok(Scope::AccRead),
63 "trade:simulate" => Ok(Scope::TradeSimulate),
64 "trade:real" => Ok(Scope::TradeReal),
65 "trade:unlock" => Ok(Scope::TradeUnlock),
66 other => Err(ScopeParseError(other.to_string())),
67 }
68 }
69}
70
71impl TryFrom<String> for Scope {
72 type Error = ScopeParseError;
73 fn try_from(value: String) -> Result<Self, Self::Error> {
74 value.parse()
75 }
76}
77
78impl From<Scope> for String {
79 fn from(s: Scope) -> String {
80 s.as_str().to_string()
81 }
82}
83
84pub fn scope_for_proto_id(proto_id: u32) -> Option<Scope> {
99 match proto_id {
100 1000..=1999 => None,
102
103 3000..=3999 => Some(Scope::QotRead),
105
106 2005 => Some(Scope::TradeReal),
108
109 2202 | 2205 | 2237 => Some(Scope::TradeReal),
111
112 2001 | 2008 | 2101 | 2102 | 2111 | 2201 | 2208 | 2211 | 2218 | 2221 | 2222 | 2223
114 | 2225 | 2226 | 2240 => Some(Scope::AccRead),
115
116 _ => Some(Scope::TradeReal),
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn roundtrip() {
127 for s in Scope::ALL {
128 let as_s = s.as_str();
129 let back: Scope = as_s.parse().unwrap();
130 assert_eq!(*s, back);
131 }
132 }
133
134 #[test]
135 fn unknown_rejected() {
136 assert!("qot:write".parse::<Scope>().is_err());
137 }
138
139 #[test]
140 fn serde_roundtrip() {
141 let s = Scope::TradeReal;
142 let j = serde_json::to_string(&s).unwrap();
143 assert_eq!(j, "\"trade:real\"");
144 let back: Scope = serde_json::from_str(&j).unwrap();
145 assert_eq!(back, Scope::TradeReal);
146 }
147
148 #[test]
149 fn proto_scope_common_cases() {
150 assert_eq!(scope_for_proto_id(1001), None); assert_eq!(scope_for_proto_id(1004), None); assert_eq!(scope_for_proto_id(3004), Some(Scope::QotRead));
155 assert_eq!(scope_for_proto_id(3223), Some(Scope::QotRead));
156 assert_eq!(scope_for_proto_id(2202), Some(Scope::TradeReal));
158 assert_eq!(scope_for_proto_id(2205), Some(Scope::TradeReal));
159 assert_eq!(scope_for_proto_id(2005), Some(Scope::TradeReal));
160 assert_eq!(scope_for_proto_id(2001), Some(Scope::AccRead));
162 assert_eq!(scope_for_proto_id(2208), Some(Scope::AccRead));
163 assert_eq!(scope_for_proto_id(2240), Some(Scope::AccRead));
164 assert_eq!(scope_for_proto_id(9999), Some(Scope::TradeReal));
166 }
167}