architect_api/utils/
dir.rs1use anyhow::{bail, Result};
2#[cfg(feature = "tokio-postgres")]
3use bytes::BytesMut;
4use rust_decimal::Decimal;
5use rust_decimal_macros::dec;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8#[cfg(feature = "tokio-postgres")]
9use std::error::Error;
10use std::{
11 ops::{Deref, Mul},
12 str::FromStr,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
18#[cfg_attr(feature = "juniper", derive(juniper::GraphQLScalar))]
19#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
20#[cfg_attr(
21 feature = "sqlx",
22 sqlx(type_name = "TEXT", rename_all = "SCREAMING_SNAKE_CASE")
23)]
24#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
25pub enum Dir {
26 #[serde(alias = "Buy", alias = "buy", alias = "BUY")]
27 Buy,
28 #[serde(alias = "Sell", alias = "sell", alias = "SELL")]
29 Sell,
30}
31
32impl Mul<Dir> for Dir {
33 type Output = Dir;
34
35 fn mul(self, rhs: Dir) -> Dir {
36 match (self, rhs) {
37 (Dir::Buy, Dir::Buy) => Dir::Buy,
38 (Dir::Sell, Dir::Sell) => Dir::Buy,
39 (Dir::Buy, Dir::Sell) => Dir::Sell,
40 (Dir::Sell, Dir::Buy) => Dir::Sell,
41 }
42 }
43}
44
45#[cfg(feature = "rusqlite")]
46impl rusqlite::ToSql for Dir {
47 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
48 use rusqlite::types::{ToSqlOutput, ValueRef};
49 let value_ref = match self {
50 Self::Buy => ValueRef::Text("BUY".as_ref()),
51 Self::Sell => ValueRef::Text("SELL".as_ref()),
52 };
53 Ok(ToSqlOutput::Borrowed(value_ref))
54 }
55}
56
57#[cfg(feature = "tokio-postgres")]
58impl tokio_postgres::types::ToSql for Dir {
59 tokio_postgres::types::to_sql_checked!();
60
61 fn to_sql(
62 &self,
63 ty: &tokio_postgres::types::Type,
64 out: &mut BytesMut,
65 ) -> std::result::Result<tokio_postgres::types::IsNull, Box<dyn Error + Sync + Send>>
66 {
67 let value_repr = match self {
68 Self::Buy => "BUY",
69 Self::Sell => "SELL",
70 };
71 value_repr.to_sql(ty, out)
72 }
73
74 fn accepts(ty: &tokio_postgres::types::Type) -> bool {
75 String::accepts(ty)
76 }
77}
78
79#[cfg(feature = "juniper")]
80impl Dir {
81 #[allow(clippy::wrong_self_convention)]
82 fn to_output<S: juniper::ScalarValue>(&self) -> juniper::Value<S> {
83 juniper::Value::scalar(self.to_str_lowercase().to_string())
84 }
85
86 fn from_input<S>(v: &juniper::InputValue<S>) -> std::result::Result<Self, String>
87 where
88 S: juniper::ScalarValue,
89 {
90 v.as_string_value()
91 .map(Self::from_str_lowercase)
92 .ok_or_else(|| format!("Expected `String`, found: {v}"))?
93 .map_err(|e| e.to_string())
94 }
95
96 fn parse_token<S>(value: juniper::ScalarToken<'_>) -> juniper::ParseScalarResult<S>
97 where
98 S: juniper::ScalarValue,
99 {
100 <String as juniper::ParseScalarValue<S>>::from_str(value)
101 }
102}
103
104impl FromStr for Dir {
105 type Err = anyhow::Error;
106
107 fn from_str(s: &str) -> Result<Self, Self::Err> {
108 match s {
109 "buy" | "Buy" | "BUY" => Ok(Self::Buy),
110 "sell" | "Sell" | "SELL" => Ok(Self::Sell),
111 _ => Err(anyhow::anyhow!("invalid format: {s}")),
112 }
113 }
114}
115
116impl Dir {
117 pub fn flip(self) -> Self {
119 match self {
120 Self::Buy => Self::Sell,
121 Self::Sell => Self::Buy,
122 }
123 }
124
125 pub fn to_str_uppercase(&self) -> &'static str {
126 match self {
127 Self::Buy => "BUY",
128 Self::Sell => "SELL",
129 }
130 }
131
132 pub fn from_str_uppercase(s: &str) -> Result<Self> {
133 match s {
134 "BUY" => Ok(Self::Buy),
135 "SELL" => Ok(Self::Sell),
136 _ => bail!("invalid format: {}", s),
137 }
138 }
139
140 pub fn to_str_lowercase(&self) -> &'static str {
141 match self {
142 Self::Buy => "buy",
143 Self::Sell => "sell",
144 }
145 }
146
147 pub fn from_str_lowercase(s: &str) -> Result<Self> {
148 match s {
149 "buy" => Ok(Self::Buy),
150 "sell" => Ok(Self::Sell),
151 _ => bail!("invalid format: {}", s),
152 }
153 }
154
155 pub fn position_sign(&self) -> Decimal {
156 match self {
157 Self::Buy => dec!(1),
158 Self::Sell => dec!(-1),
159 }
160 }
161
162 #[inline]
163 pub fn sign(value: Decimal) -> Option<Dir> {
164 if value.is_sign_positive() {
165 return Some(Dir::Buy);
166 }
167 if value.is_zero() {
168 return None;
169 }
170 Some(Dir::Sell)
171 }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
176pub struct DirAsCharUpper(Dir);
177
178impl Deref for DirAsCharUpper {
179 type Target = Dir;
180
181 fn deref(&self) -> &Self::Target {
182 &self.0
183 }
184}
185
186impl From<Dir> for DirAsCharUpper {
187 fn from(dir: Dir) -> Self {
188 Self(dir)
189 }
190}
191
192impl From<DirAsCharUpper> for Dir {
193 fn from(val: DirAsCharUpper) -> Self {
194 val.0
195 }
196}
197
198impl Serialize for DirAsCharUpper {
199 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
200 where
201 S: serde::Serializer,
202 {
203 serializer.serialize_char(match **self {
204 Dir::Buy => 'B',
205 Dir::Sell => 'S',
206 })
207 }
208}
209
210struct DirAsCharUpperVisitor;
211
212impl serde::de::Visitor<'_> for DirAsCharUpperVisitor {
213 type Value = DirAsCharUpper;
214
215 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
216 formatter.write_str("a single uppercase character")
217 }
218}
219
220impl<'de> Deserialize<'de> for DirAsCharUpper {
221 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
222 where
223 D: serde::Deserializer<'de>,
224 {
225 deserializer.deserialize_char(DirAsCharUpperVisitor)
226 }
227}