architect_api/orderflow/
order_id.rs1use anyhow::{bail, Result};
2use base64::{engine::general_purpose::URL_SAFE, Engine as _};
3#[cfg(feature = "tokio-postgres")]
4use bytes::BytesMut;
5use schemars::JsonSchema;
6use serde_with::{DeserializeFromStr, SerializeDisplay};
7#[cfg(feature = "tokio-postgres")]
8use std::error::Error;
9use std::{fmt, str::FromStr};
10use uuid::Uuid;
11
12#[derive(
15 Debug,
16 Clone,
17 Copy,
18 Hash,
19 PartialEq,
20 Eq,
21 PartialOrd,
22 Ord,
23 SerializeDisplay,
24 DeserializeFromStr,
25 JsonSchema,
26 bytemuck::Pod,
27 bytemuck::Zeroable,
28)]
29#[cfg_attr(feature = "juniper", derive(juniper::GraphQLScalar))]
30#[repr(C)]
31pub struct OrderId {
32 pub seqid: Uuid,
33 pub seqno: u64,
34}
35
36impl OrderId {
37 pub fn nil(seqno: u64) -> Self {
40 Self { seqid: Uuid::nil(), seqno }
41 }
42
43 pub fn random() -> Self {
44 Self { seqid: Uuid::new_v4(), seqno: 0 }
45 }
46
47 pub fn exchange(namespace: &Uuid, exchange_id: impl AsRef<[u8]>) -> Self {
48 let seqid = Uuid::new_v5(namespace, exchange_id.as_ref());
49 Self { seqid, seqno: 0 }
50 }
51
52 pub fn as_bytes(&self) -> &[u8] {
53 bytemuck::bytes_of(self)
54 }
55
56 pub fn try_from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
57 match bytemuck::try_from_bytes(bytes.as_ref()) {
58 Ok(oid) => Ok(*oid),
59 Err(e) => bail!("casting bytes to OrderId: {e}"),
60 }
61 }
62
63 pub fn encode_base64(&self) -> String {
64 URL_SAFE.encode(bytemuck::bytes_of(self))
65 }
66
67 pub fn decode_base64(s: impl AsRef<[u8]>) -> Result<Self> {
68 let bytes = URL_SAFE.decode(s.as_ref())?;
69 Ok(Self::try_from_bytes(bytes)?)
70 }
71}
72
73impl FromStr for OrderId {
74 type Err = anyhow::Error;
75
76 fn from_str(s: &str) -> Result<Self, Self::Err> {
77 match s.split_once(':') {
78 Some((seqid_s, seqno_s)) => {
79 let seqid = Uuid::from_str(seqid_s)?;
80 let seqno = u64::from_str(seqno_s)?;
81 Ok(Self { seqid, seqno })
82 }
83 None => Ok(Self { seqid: Uuid::nil(), seqno: u64::from_str(s)? }),
84 }
85 }
86}
87
88impl fmt::Display for OrderId {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 if self.seqid.is_nil() {
91 write!(f, "{}", self.seqno)
92 } else {
93 write!(f, "{}:{}", self.seqid, self.seqno)
94 }
95 }
96}
97
98#[cfg(feature = "rusqlite")]
99impl rusqlite::ToSql for OrderId {
100 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
101 use rusqlite::types::{ToSqlOutput, Value};
102 let val = Value::Text(self.to_string());
103 Ok(ToSqlOutput::Owned(val))
104 }
105}
106
107#[cfg(feature = "tokio-postgres")]
108impl tokio_postgres::types::ToSql for OrderId {
109 tokio_postgres::types::to_sql_checked!();
110
111 fn to_sql(
112 &self,
113 ty: &tokio_postgres::types::Type,
114 out: &mut BytesMut,
115 ) -> std::result::Result<tokio_postgres::types::IsNull, Box<dyn Error + Sync + Send>>
116 {
117 self.to_string().to_sql(ty, out)
118 }
119
120 fn accepts(ty: &tokio_postgres::types::Type) -> bool {
121 String::accepts(ty)
122 }
123}
124
125#[cfg(feature = "sqlx")]
126impl<'a, DB: sqlx::Database> sqlx::Encode<'a, DB> for OrderId
127where
128 String: sqlx::Encode<'a, DB>,
129{
130 fn encode_by_ref(
131 &self,
132 buf: &mut <DB as sqlx::Database>::ArgumentBuffer<'a>,
133 ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
134 let value = self.to_string();
135 <String as sqlx::Encode<DB>>::encode(value, buf)
136 }
137}
138
139#[cfg(feature = "sqlx")]
140impl<'a, DB: sqlx::Database> sqlx::Decode<'a, DB> for OrderId
141where
142 &'a str: sqlx::Decode<'a, DB>,
143{
144 fn decode(
145 value: <DB as sqlx::Database>::ValueRef<'a>,
146 ) -> Result<Self, sqlx::error::BoxDynError> {
147 let value = <&str as sqlx::Decode<DB>>::decode(value)?;
148 Ok(value.parse()?)
149 }
150}
151
152#[cfg(feature = "sqlx")]
153impl<DB: sqlx::Database> sqlx::Type<DB> for OrderId
154where
155 for<'a> &'a str: sqlx::Type<DB>,
156{
157 fn type_info() -> <DB as sqlx::Database>::TypeInfo {
158 <&str as sqlx::Type<DB>>::type_info()
159 }
160}
161
162#[cfg(feature = "juniper")]
163impl OrderId {
164 #[allow(clippy::wrong_self_convention)]
165 fn to_output<S: juniper::ScalarValue>(&self) -> juniper::Value<S> {
166 juniper::Value::scalar(self.to_string())
167 }
168
169 fn from_input<S>(v: &juniper::InputValue<S>) -> Result<Self, String>
170 where
171 S: juniper::ScalarValue,
172 {
173 v.as_string_value()
174 .map(Self::from_str)
175 .ok_or_else(|| format!("Expected `String`, found: {v}"))?
176 .map_err(|e| e.to_string())
177 }
178
179 fn parse_token<S>(value: juniper::ScalarToken<'_>) -> juniper::ParseScalarResult<S>
180 where
181 S: juniper::ScalarValue,
182 {
183 <String as juniper::ParseScalarValue<S>>::from_str(value)
184 }
185}
186
187impl rand::distr::Distribution<OrderId> for rand::distr::StandardUniform {
188 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> OrderId {
189 OrderId { seqid: Uuid::new_v4(), seqno: rng.random() }
190 }
191}