alibi_sqlx/store/entities/
wallet_address.rs1use crate::value::{ColumnKind, SqlValue, SqlxValue, ValueTypeError};
2use chrono::{DateTime, Utc};
3use sqlx::error::BoxDynError;
4#[cfg(feature = "postgres")]
5use sqlx::postgres::{PgTypeInfo, PgValueRef, Postgres};
6#[cfg(feature = "sqlite")]
7use sqlx::sqlite::{Sqlite, SqliteTypeInfo, SqliteValueRef};
8use sqlx::{Decode, Type, ValueRef};
9
10#[derive(Clone, Copy, Debug, PartialEq)]
16pub struct WalletChainId(pub f64);
17
18impl From<f64> for WalletChainId {
19 fn from(value: f64) -> Self {
20 Self(value)
21 }
22}
23
24impl SqlxValue for WalletChainId {
25 const KIND: ColumnKind = ColumnKind::Other;
26 fn null() -> SqlValue {
27 SqlValue::Double(None)
28 }
29 #[expect(
30 clippy::as_conversions,
31 clippy::cast_possible_truncation,
32 clippy::cast_precision_loss,
33 reason = "JavaScript-compatible numbers deliberately retain IEEE754 rounding and guarded integer coercion at the wire or adapter boundary"
34 )]
35 fn into_sql_value(self) -> SqlValue {
36 if self.0.fract() == 0.0 && self.0 >= i64::MIN as f64 && self.0 < i64::MAX as f64 {
37 SqlValue::BigInt(Some(self.0 as i64))
38 } else {
39 SqlValue::Double(Some(self.0))
40 }
41 }
42 #[expect(
43 clippy::as_conversions,
44 clippy::cast_precision_loss,
45 reason = "JavaScript-compatible numbers deliberately retain IEEE754 rounding and guarded integer coercion at the wire or adapter boundary"
46 )]
47 fn from_sql_value(value: SqlValue) -> Result<Self, ValueTypeError> {
48 match value {
49 SqlValue::Double(Some(value)) => Ok(Self(value)),
50 SqlValue::BigInt(Some(value)) => Ok(Self(value as f64)),
51 SqlValue::Int(Some(value)) => Ok(Self(f64::from(value))),
52 _ => Err(ValueTypeError),
53 }
54 }
55}
56
57#[cfg(feature = "sqlite")]
58impl Type<Sqlite> for WalletChainId {
59 fn type_info() -> SqliteTypeInfo {
60 <f64 as Type<Sqlite>>::type_info()
61 }
62 fn compatible(ty: &SqliteTypeInfo) -> bool {
63 <f64 as Type<Sqlite>>::compatible(ty)
64 || <i64 as Type<Sqlite>>::compatible(ty)
65 || <i32 as Type<Sqlite>>::compatible(ty)
66 }
67}
68
69#[cfg(feature = "sqlite")]
70impl<'r> Decode<'r, Sqlite> for WalletChainId {
71 #[expect(
72 clippy::as_conversions,
73 clippy::cast_precision_loss,
74 reason = "JavaScript-compatible numbers deliberately retain IEEE754 rounding and guarded integer coercion at the wire or adapter boundary"
75 )]
76 fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
77 let ty = value.type_info().into_owned();
79 if <f64 as Type<Sqlite>>::compatible(&ty) {
80 <f64 as Decode<'r, Sqlite>>::decode(value).map(Self)
81 } else if <i64 as Type<Sqlite>>::compatible(&ty) {
82 <i64 as Decode<'r, Sqlite>>::decode(value).map(|value| Self(value as f64))
83 } else {
84 <i32 as Decode<'r, Sqlite>>::decode(value).map(|value| Self(f64::from(value)))
85 }
86 }
87}
88
89#[cfg(feature = "postgres")]
90impl Type<Postgres> for WalletChainId {
91 fn type_info() -> PgTypeInfo {
92 <f64 as Type<Postgres>>::type_info()
93 }
94 fn compatible(ty: &PgTypeInfo) -> bool {
95 <f64 as Type<Postgres>>::compatible(ty)
96 || <i64 as Type<Postgres>>::compatible(ty)
97 || <i32 as Type<Postgres>>::compatible(ty)
98 }
99}
100
101#[cfg(feature = "postgres")]
102impl<'r> Decode<'r, Postgres> for WalletChainId {
103 #[expect(
104 clippy::as_conversions,
105 clippy::cast_precision_loss,
106 reason = "JavaScript-compatible numbers deliberately retain IEEE754 rounding and guarded integer coercion at the wire or adapter boundary"
107 )]
108 fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
109 let ty = value.type_info().into_owned();
110 if <f64 as Type<Postgres>>::compatible(&ty) {
111 <f64 as Decode<'r, Postgres>>::decode(value).map(Self)
112 } else if <i64 as Type<Postgres>>::compatible(&ty) {
113 <i64 as Decode<'r, Postgres>>::decode(value).map(|value| Self(value as f64))
114 } else {
115 <i32 as Decode<'r, Postgres>>::decode(value).map(|value| Self(f64::from(value)))
116 }
117 }
118}
119
120#[derive(Clone, Debug, PartialEq, sqlx::FromRow, crate::SqlxModel)]
121#[auth(table = "wallet_address")]
122pub struct Model {
123 pub id: String,
124 pub user_id: String,
125 pub address: String,
126 pub chain_id: WalletChainId,
127 pub is_primary: bool,
128 pub created_at: DateTime<Utc>,
129}
130
131impl From<Model> for alibi_core::WalletAddress {
132 fn from(row: Model) -> Self {
133 Self {
134 id: row.id,
135 user_id: row.user_id,
136 address: row.address,
137 chain_id: row.chain_id.0,
138 is_primary: row.is_primary,
139 created_at: row.created_at,
140 }
141 }
142}