Skip to main content

alibi_sqlx/store/entities/
api_key_start.rs

1use crate::pool::Engine;
2use crate::value::{ColumnKind, SqlValue, SqlxValue, ValueTypeError};
3use alibi_core::{ApiKeyStartText, ApiKeyStartingCharacters, AuthError, AuthResult};
4use sqlx::error::BoxDynError;
5#[cfg(feature = "postgres")]
6use sqlx::postgres::{PgTypeInfo, PgValueRef, Postgres};
7#[cfg(feature = "sqlite")]
8use sqlx::sqlite::{Sqlite, SqliteTypeInfo, SqliteValueRef};
9use sqlx::{Decode, Type};
10
11/// SQLite preserves Bun's WTF-8 TEXT binding; only this public substring is
12/// decoded lossily. Other columns continue to require valid UTF-8.
13#[derive(Clone, Debug, PartialEq)]
14pub struct ApiKeyStart {
15    text: String,
16    sqlite_bytes: Option<Vec<u8>>,
17}
18
19impl ApiKeyStart {
20    pub(crate) fn prepare(value: &ApiKeyStartingCharacters, backend: Engine) -> AuthResult<Self> {
21        match value.storage_text() {
22            ApiKeyStartText::Utf8(text) => Ok(Self {
23                text,
24                sqlite_bytes: None,
25            }),
26            ApiKeyStartText::Wtf8(bytes) if backend == Engine::Sqlite => Ok(Self::sqlite(bytes)),
27            ApiKeyStartText::Wtf8(_) => Err(AuthError::internal(
28                "UTF-16 API-key starting characters require SQLite storage",
29            )),
30        }
31    }
32
33    fn sqlite(bytes: Vec<u8>) -> Self {
34        Self {
35            text: String::from_utf8_lossy(&bytes).into_owned(),
36            sqlite_bytes: Some(bytes),
37        }
38    }
39
40    pub(crate) fn as_str(&self) -> &str {
41        &self.text
42    }
43
44    pub(crate) const fn requires_sqlite_cast(&self) -> bool {
45        self.sqlite_bytes.is_some()
46    }
47}
48
49impl SqlxValue for ApiKeyStart {
50    const KIND: ColumnKind = ColumnKind::Text;
51    fn null() -> SqlValue {
52        SqlValue::Text(None)
53    }
54    fn into_sql_value(self) -> SqlValue {
55        self.sqlite_bytes.map_or_else(
56            || SqlValue::Text(Some(self.text)),
57            |bytes| SqlValue::Bytes(Some(bytes)),
58        )
59    }
60    fn from_sql_value(value: SqlValue) -> Result<Self, ValueTypeError> {
61        match value {
62            SqlValue::Bytes(Some(bytes)) => Ok(Self::sqlite(bytes)),
63            SqlValue::Text(Some(text)) => Ok(Self {
64                text,
65                sqlite_bytes: None,
66            }),
67            _ => Err(ValueTypeError),
68        }
69    }
70}
71
72#[cfg(feature = "sqlite")]
73impl Type<Sqlite> for ApiKeyStart {
74    fn type_info() -> SqliteTypeInfo {
75        <Vec<u8> as Type<Sqlite>>::type_info()
76    }
77    fn compatible(ty: &SqliteTypeInfo) -> bool {
78        <Vec<u8> as Type<Sqlite>>::compatible(ty)
79    }
80}
81
82#[cfg(feature = "sqlite")]
83impl<'r> Decode<'r, Sqlite> for ApiKeyStart {
84    fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
85        <Vec<u8> as Decode<'r, Sqlite>>::decode(value).map(Self::sqlite)
86    }
87}
88
89#[cfg(feature = "postgres")]
90impl Type<Postgres> for ApiKeyStart {
91    fn type_info() -> PgTypeInfo {
92        <String as Type<Postgres>>::type_info()
93    }
94    fn compatible(ty: &PgTypeInfo) -> bool {
95        <String as Type<Postgres>>::compatible(ty)
96    }
97}
98
99#[cfg(feature = "postgres")]
100impl<'r> Decode<'r, Postgres> for ApiKeyStart {
101    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
102        <String as Decode<'r, Postgres>>::decode(value).map(|text| Self {
103            text,
104            sqlite_bytes: None,
105        })
106    }
107}