forked from 2sys/phonograph
118 lines
3.1 KiB
Rust
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,
|
|
}
|