Skip to main content

rainier_orm/
bytes.rs

1//! [`Base64Bytes`] — a binary column stored as **base64 text on every
2//! dialect**.
3//!
4//! Raw binary is the one thing that doesn't travel uniformly: MySQL has
5//! `VARBINARY`, but D1/SQLite over JSON has no clean blob binding, so binary is
6//! conventionally base64-encoded into a `TEXT` column there. Modelling such a
7//! column as `Base64Bytes` makes the representation identical across MySQL,
8//! Postgres, SQLite, and D1 — a `TEXT` column holding base64 — so one generic
9//! adapter works on all of them. Use plain `Vec<u8>` only when every target is
10//! a real binary column (e.g. MySQL-only).
11
12use crate::{Error, Result, Row, StringColumn};
13use base64::{engine::general_purpose::STANDARD, Engine as _};
14
15/// Bytes persisted as base64 in a `TEXT` column, uniformly across dialects.
16/// Wire it into the ORM with the [`impl_string_column!`](crate::impl_string_column)
17/// machinery via its [`StringColumn`] impl below.
18#[derive(Debug, Clone, PartialEq, Eq, Default)]
19pub struct Base64Bytes(pub Vec<u8>);
20
21impl Base64Bytes {
22    pub fn into_vec(self) -> Vec<u8> {
23        self.0
24    }
25    pub fn as_slice(&self) -> &[u8] {
26        &self.0
27    }
28}
29
30impl From<Vec<u8>> for Base64Bytes {
31    fn from(v: Vec<u8>) -> Self {
32        Base64Bytes(v)
33    }
34}
35impl From<Base64Bytes> for Vec<u8> {
36    fn from(b: Base64Bytes) -> Self {
37        b.0
38    }
39}
40
41impl StringColumn for Base64Bytes {
42    fn to_column_str(&self) -> String {
43        STANDARD.encode(&self.0)
44    }
45    fn from_column_str(s: &str) -> Result<Self> {
46        STANDARD
47            .decode(s)
48            .map(Base64Bytes)
49            .map_err(|e| Error::msg(format!("invalid base64 in column: {e}")))
50    }
51}
52
53// Reuse the string-column wiring: Text SqlType + To/FromColumn (incl. the
54// blanket Option<Base64Bytes>). This is the macro-free expansion since the
55// macro lives in this crate.
56impl crate::SqlType for Base64Bytes {
57    const COLUMN_TYPE: crate::ColumnType = crate::ColumnType::Text;
58}
59impl crate::ToColumn for Base64Bytes {
60    fn to_value(&self) -> sea_query::Value {
61        sea_query::Value::String(Some(Box::new(self.to_column_str())))
62    }
63}
64impl crate::FromColumn for Base64Bytes {
65    fn from_column(row: &dyn Row, col: &str) -> Result<Self> {
66        let s =
67            row.get_string(col)?.ok_or_else(|| Error::msg(format!("column `{col}` was NULL")))?;
68        <Base64Bytes as StringColumn>::from_column_str(&s)
69    }
70}