1
0
Fork 0
forked from 2sys/phonograph
phonograph/src/data_layer.rs
2025-05-13 00:02:33 -07:00

118 lines
3.1 KiB
Rust

use std::fmt::Display;
use anyhow::Result;
use chrono::{DateTime, Utc};
use deadpool_postgres::tokio_postgres::{
row::RowIndex,
types::{FromSql, Type},
Row,
};
use derive_builder::Builder;
use uuid::Uuid;
pub enum Contents {
Text(String),
Integer(i32),
Timestamptz(DateTime<Utc>),
Uuid(Uuid),
}
pub struct Value(Option<Contents>);
#[derive(Builder)]
#[builder(pattern = "owned", setter(prefix = "with"))]
pub struct FieldOptions {
/// Format with which to render timestamptz values
#[builder(default = "\"%Y-%m-%dT%H:%M:%S%.f%:z\".to_owned()")]
pub date_format: String,
/// If some, treat text column like an enum
#[builder(default)]
pub select_options: Option<Vec<String>>,
/// Text to display in place of actual column name
#[builder(default)]
pub label: Option<String>,
#[builder(default = "true")]
pub editable: bool,
}
pub trait ToHtmlString {
fn to_html_string(&self, options: &FieldOptions) -> String;
}
#[derive(Clone, Debug)]
pub struct FromSqlError {
message: String,
}
impl std::error::Error for FromSqlError {}
impl Display for FromSqlError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl FromSqlError {
fn new(message: &str) -> Self {
Self {
message: message.to_owned(),
}
}
}
impl Value {
pub fn get_from_row<I: RowIndex + Display>(row: &Row, idx: I) -> Result<Self> {
Ok(Self(row.try_get::<_, Option<Contents>>(idx)?))
}
}
impl ToHtmlString for Value {
fn to_html_string(&self, options: &FieldOptions) -> String {
if let Self(Some(contents)) = self {
match contents {
Contents::Text(value) => value.clone(),
&Contents::Integer(value) => value.to_string(),
&Contents::Timestamptz(value) => value.format(&options.date_format).to_string(),
&Contents::Uuid(value) => format!(
"<span class=\"pg-value-uuid\">{}</span>",
value.hyphenated()
),
}
} else {
"<span class=\"pg-value-null\">NULL</span>".to_owned()
}
}
}
impl<'a> FromSql<'a> for Contents {
fn from_sql(
ty: &Type,
raw: &'a [u8],
) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
match ty {
&Type::INT4 => Ok(Self::Integer(i32::from_sql(ty, raw)?)),
&Type::TEXT | &Type::VARCHAR => Ok(Self::Text(String::from_sql(ty, raw)?)),
&Type::TIMESTAMPTZ => Ok(Self::Timestamptz(DateTime::<Utc>::from_sql(ty, raw)?)),
&Type::UUID => Ok(Self::Uuid(<Uuid as FromSql>::from_sql(ty, raw)?)),
_ => Err(Box::new(FromSqlError::new(
"unsupported pg type for interim Value",
))),
}
}
fn accepts(ty: &Type) -> bool {
matches!(ty, &Type::TEXT | &Type::VARCHAR | &Type::UUID)
}
}
pub struct Lens {
pub fields: Vec<Field>,
}
pub struct Field {
pub options: FieldOptions,
pub name: String,
}