1use crate::{json_schema_is_string, Str};
12use anyhow::{bail, Result};
13use derive_more::{Deref, Display};
14use schemars::JsonSchema;
15use serde::{Deserialize, Serialize};
16use std::str::FromStr;
17use uuid::Uuid;
18
19pub type AccountId = Uuid;
20
21#[derive(
22 Debug,
23 Display,
24 Deref,
25 Clone,
26 Copy,
27 PartialEq,
28 Eq,
29 PartialOrd,
30 Ord,
31 Serialize,
32 Deserialize,
33)]
34#[cfg_attr(feature = "graphql", derive(juniper::GraphQLScalar))]
35#[cfg_attr(feature = "graphql", graphql(transparent))]
36pub struct AccountName(Str);
37
38json_schema_is_string!(AccountName);
39
40impl AccountName {
41 pub fn new(
43 cpty_name: impl AsRef<str>,
44 cpty_account_id: impl AsRef<str>,
45 ) -> Result<Self> {
46 let name = format!("{}:{}", cpty_name.as_ref(), cpty_account_id.as_ref());
47 Ok(Self(Str::try_from(name)?))
48 }
49
50 pub fn cpty_name(&self) -> Option<&str> {
51 self.0.split_once(':').map(|(c, _)| c)
52 }
53
54 pub fn cpty_account_id(&self) -> Option<&str> {
55 self.0.split_once(':').map(|(_, c)| c)
56 }
57}
58
59impl FromStr for AccountName {
60 type Err = anyhow::Error;
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 if s.contains(':') {
64 Ok(Self(Str::try_from(s)?))
65 } else {
66 bail!("invalid account name: {}", s);
67 }
68 }
69}
70
71#[cfg(feature = "postgres-types")]
72impl postgres_types::ToSql for AccountName {
73 postgres_types::to_sql_checked!();
74
75 fn to_sql(
76 &self,
77 ty: &postgres_types::Type,
78 out: &mut bytes::BytesMut,
79 ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
80 self.0.as_str().to_sql(ty, out)
81 }
82
83 fn accepts(ty: &postgres_types::Type) -> bool {
84 String::accepts(ty)
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
89#[serde(untagged)]
90pub enum AccountIdOrName {
91 Id(AccountId),
92 Name(AccountName),
93}
94
95json_schema_is_string!(AccountIdOrName);
96
97impl std::str::FromStr for AccountIdOrName {
98 type Err = anyhow::Error;
99
100 fn from_str(s: &str) -> Result<Self, Self::Err> {
101 if let Ok(id) = AccountId::from_str(s) {
102 Ok(Self::Id(id))
103 } else {
104 Ok(Self::Name(AccountName::from_str(s)?))
105 }
106 }
107}
108
109#[derive(
110 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
111)]
112#[cfg_attr(feature = "graphql", derive(juniper::GraphQLObject))]
113pub struct Account {
114 pub id: AccountId,
115 pub name: AccountName,
116}
117
118pub trait AsAccount {
119 fn as_account(&self) -> Account;
120}
121
122#[derive(
124 Debug,
125 Default,
126 Clone,
127 Copy,
128 Serialize,
129 Deserialize,
130 PartialEq,
131 Eq,
132 PartialOrd,
133 Ord,
134 JsonSchema,
135)]
136#[cfg_attr(feature = "graphql", derive(juniper::GraphQLObject))]
137pub struct AccountPermissions {
138 pub list: bool, pub view: bool, pub trade: bool, pub reduce_or_close: bool, pub set_limits: bool, }
144
145impl AccountPermissions {
146 pub fn all() -> Self {
147 Self {
148 list: true,
149 view: true,
150 trade: true,
151 reduce_or_close: true,
152 set_limits: true,
153 }
154 }
155
156 pub fn none() -> Self {
157 Self {
158 list: false,
159 view: false,
160 trade: false,
161 reduce_or_close: false,
162 set_limits: false,
163 }
164 }
165
166 pub fn is_none(&self) -> bool {
167 !self.list
168 && !self.view
169 && !self.trade
170 && !self.reduce_or_close
171 && !self.set_limits
172 }
173
174 pub fn read_only() -> Self {
175 Self {
176 list: true,
177 view: true,
178 trade: false,
179 reduce_or_close: false,
180 set_limits: false,
181 }
182 }
183
184 pub fn list(&self) -> bool {
185 self.list
186 }
187
188 pub fn view(&self) -> bool {
189 self.view
190 }
191
192 pub fn trade(&self) -> bool {
193 self.trade
194 }
195
196 pub fn reduce_or_close(&self) -> bool {
197 self.reduce_or_close
198 }
199
200 pub fn set_limits(&self) -> bool {
201 self.set_limits
202 }
203
204 pub fn display(&self) -> String {
205 let mut allowed = vec![];
206 let mut denied = vec![];
207 macro_rules! sift {
208 ($perm:ident) => {
209 if self.$perm {
210 allowed.push(stringify!($perm));
211 } else {
212 denied.push(stringify!($perm));
213 }
214 };
215 }
216 sift!(list);
217 sift!(view);
218 sift!(trade);
219 sift!(reduce_or_close);
220 sift!(set_limits);
221 format!("allow({}) deny({})", allowed.join(", "), denied.join(", "))
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_account_id_or_name_json() {
231 let id: AccountId = "aa0fc734-0da2-4168-8712-4c0b67f01c59".parse().unwrap();
232 let name: AccountName = AccountName::new("COINBASE", "TEST").unwrap();
233
234 let id_spec = AccountIdOrName::Id(id);
236 insta::assert_json_snapshot!(id_spec, @r#""aa0fc734-0da2-4168-8712-4c0b67f01c59""#);
237
238 let id_json = r#""aa0fc734-0da2-4168-8712-4c0b67f01c59""#;
240 let id_deserialized: AccountIdOrName = serde_json::from_str(id_json).unwrap();
241 assert_eq!(id_spec, id_deserialized);
242
243 let name_spec = AccountIdOrName::Name(name);
245 insta::assert_json_snapshot!(name_spec, @r#""COINBASE:TEST""#);
246
247 let name_json = r#""COINBASE:TEST""#;
249 let name_deserialized: AccountIdOrName = serde_json::from_str(name_json).unwrap();
250 assert_eq!(name_spec, name_deserialized);
251 }
252}