Skip to main content

rainier_orm/
string_column.rs

1//! Support for custom column types stored as text — the common case being a
2//! Rust enum persisted as a string (`"pending"`, `"verified"`, …).
3//!
4//! Implement [`StringColumn`] for your type (how it maps to/from a string),
5//! then `impl_string_column!(YourType)` to wire it into the ORM: it gets a
6//! `Text` [`SqlType`](crate::SqlType), and [`ToColumn`](crate::ToColumn) /
7//! [`FromColumn`](crate::FromColumn) impls (for both `T` and `Option<T>`) so it
8//! can be a `#[derive(Entity)]` field like any primitive.
9//!
10//! ```ignore
11//! use rainier_orm::{StringColumn, impl_string_column, Result, Error};
12//!
13//! #[derive(Clone, PartialEq)]
14//! enum Status { Pending, Verified, Rejected }
15//!
16//! impl StringColumn for Status {
17//!     fn to_column_str(&self) -> String {
18//!         match self { Status::Pending => "pending", Status::Verified => "verified", Status::Rejected => "rejected" }.into()
19//!     }
20//!     fn from_column_str(s: &str) -> Result<Self> {
21//!         match s {
22//!             "pending" => Ok(Status::Pending),
23//!             "verified" => Ok(Status::Verified),
24//!             "rejected" => Ok(Status::Rejected),
25//!             other => Err(Error::msg(format!("unknown Status `{other}`"))),
26//!         }
27//!     }
28//! }
29//! impl_string_column!(Status);
30//! ```
31
32/// How a custom type maps to and from its stored string form. The string is
33/// what lands in a `TEXT`/`VARCHAR` column.
34pub trait StringColumn: Sized {
35    fn to_column_str(&self) -> String;
36    fn from_column_str(s: &str) -> crate::Result<Self>;
37}
38
39/// Wire a [`StringColumn`] type into the ORM's column traits. See the
40/// [module docs](crate::string_column).
41#[macro_export]
42macro_rules! impl_string_column {
43    ($t:ty) => {
44        impl $crate::SqlType for $t {
45            const COLUMN_TYPE: $crate::ColumnType = $crate::ColumnType::Text;
46        }
47        impl $crate::ToColumn for $t {
48            fn to_value(&self) -> $crate::sea_query::Value {
49                $crate::sea_query::Value::String(::core::option::Option::Some(
50                    ::std::boxed::Box::new($crate::StringColumn::to_column_str(self)),
51                ))
52            }
53        }
54        impl $crate::FromColumn for $t {
55            fn from_column(row: &dyn $crate::Row, col: &str) -> $crate::Result<Self> {
56                let s = $crate::Row::get_string(row, col)?
57                    .ok_or_else(|| $crate::Error::msg(::std::format!("column `{col}` was NULL")))?;
58                <$t as $crate::StringColumn>::from_column_str(&s)
59            }
60        }
61        // `Option<$t>` support comes from the crate's blanket impls (driven by
62        // the `Text` SqlType above) — the orphan rule forbids implementing it
63        // here, and it isn't needed.
64    };
65}