use std::fmt::Display; use anyhow::Result; use chrono::{DateTime, Utc}; use interim_models::selection::SelectionDisplayType; use sqlx::{ ColumnIndex, Decode, Postgres, Row as _, TypeInfo as _, ValueRef as _, error::BoxDynError, postgres::{PgRow, PgTypeInfo, PgValueRef}, }; use uuid::Uuid; const DEFAULT_TIMESTAMP_FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.f%:z"; pub enum Value { Text(Option), Integer(Option), Timestamptz(Option>), Uuid(Option), } pub trait ToHtmlString { fn to_html_string(&self, display_type: &Option) -> String; } // TODO rewrite with thiserror #[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 + Display>( row: &PgRow, idx: I, ) -> Result { let value_ref = row.try_get_raw(idx)?; Self::decode(value_ref) } } impl ToHtmlString for Value { fn to_html_string(&self, display_type: &Option) -> String { macro_rules! cell_html { ($component:expr, $value:expr$(, $attr_name:expr => $attr_val:expr)*) => { { let value = $value.clone(); let attrs: Vec = vec![ format!("value=\"{}\"", serde_json::to_string(&value).unwrap().replace('"', "\\\"")), $(format!("{}=\"{}\"", $attr_name, $attr_val.replace('"', "\\\"")),)* ]; format!( "<{} {}>{}", $component, attrs.join(" "), value.map(|value| value.to_string()).unwrap_or("-".to_owned()), $component, ) } }; } match self { Self::Text(value) => cell_html!("cell-text", value), Self::Integer(value) => cell_html!("cell-integer", value), Self::Timestamptz(value) => cell_html!( "cell-timestamptz", value, "format" => DEFAULT_TIMESTAMP_FORMAT ), Self::Uuid(value) => cell_html!("cell-uuid", value), } } } impl sqlx::Type for Value { fn type_info() -> ::TypeInfo { PgTypeInfo::with_name("XXX"); todo!() } } impl<'a> Decode<'a, Postgres> for Value { fn decode(value: PgValueRef<'a>) -> Result { let type_info = value.type_info(); let ty = type_info.name(); match ty { "INT" | "INT4" => Ok(Self::Integer(if value.is_null() { None } else { Some(>::decode(value)?) })), "TEXT" | "VARCHAR" => Ok(Self::Text(if value.is_null() { None } else { Some(>::decode(value)?) })), "TIMESTAMPTZ" => Ok(Self::Timestamptz(if value.is_null() { None } else { Some( as Decode>::decode(value)?) })), "UUID" => Ok(Self::Uuid(if value.is_null() { None } else { Some(>::decode(value)?) })), _ => Err(Box::new(FromSqlError::new( "unsupported pg type for interim Value", ))), } } }