use chrono::{DateTime, Utc}; use interim_pgtypes::pg_attribute::PgAttribute; use serde::{Deserialize, Serialize}; use sqlx::{Decode, Postgres, Row as _, TypeInfo as _, ValueRef as _, postgres::PgRow}; use thiserror::Error; use uuid::Uuid; const RFC_3339_S: &str = "%Y-%m-%dT%H:%M:%S"; /// A single column which can be passed to a front-end viewer. A Selection may /// resolve to zero or more Fields. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Field { pub name: String, pub label: Option, pub field_type: sqlx::types::Json, pub width_px: i32, } impl Field { pub fn default_from_attr(attr: &PgAttribute) -> Self { Self { name: attr.attname.clone(), label: None, field_type: sqlx::types::Json(FieldType::default_from_attr(attr)), width_px: 200, } } pub fn webc_tag(&self) -> &str { match self.field_type.0 { FieldType::Integer => "cell-integer", FieldType::InterimUser => "cell-interim-user", FieldType::Text => "cell-text", FieldType::Timestamp { .. } => "cell-timestamp", FieldType::Uuid => "cell-uuid", FieldType::Unknown => "cell-unknown", } } pub fn webc_custom_attrs(&self) -> Vec<(String, String)> { vec![] } pub fn render(&self, value: &Encodable) -> String { match (self.field_type.0.clone(), value) { (FieldType::Integer, Encodable::Integer(Some(value))) => value.to_string(), (FieldType::Integer, Encodable::Integer(None)) => "".to_owned(), (FieldType::Integer, _) => "###".to_owned(), (FieldType::InterimUser, Encodable::Text(value)) => todo!(), (FieldType::InterimUser, _) => "###".to_owned(), (FieldType::Text, Encodable::Text(Some(value))) => value.clone(), (FieldType::Text, Encodable::Text(None)) => "".to_owned(), (FieldType::Text, _) => "###".to_owned(), (FieldType::Timestamp { format }, Encodable::Timestamptz(value)) => value .map(|value| value.format(&format).to_string()) .unwrap_or("".to_owned()), (FieldType::Timestamp { .. }, _) => "###".to_owned(), (FieldType::Uuid, Encodable::Uuid(Some(value))) => value.hyphenated().to_string(), (FieldType::Uuid, Encodable::Uuid(None)) => "".to_owned(), (FieldType::Uuid, _) => "###".to_owned(), (FieldType::Unknown, _) => "###".to_owned(), } } pub fn get_value_encodable(&self, row: &PgRow) -> Result { let value_ref = row .try_get_raw(self.name.as_str()) .or(Err(ParseError::FieldNotFound))?; let type_info = value_ref.type_info(); let ty = type_info.name(); Ok(match ty { "INT" | "INT4" => { Encodable::Integer( as Decode>::decode(value_ref).unwrap()) } "TEXT" | "VARCHAR" => { Encodable::Text( as Decode>::decode(value_ref).unwrap()) } "UUID" => { Encodable::Uuid( as Decode>::decode(value_ref).unwrap()) } _ => return Err(ParseError::UnknownType), }) } } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(tag = "t", content = "c")] pub enum FieldType { Integer, InterimUser, Text, Timestamp { format: String, }, Uuid, /// A special variant for when the field type is not specified and cannot be /// inferred. This isn't represented as an error, because we still want to /// be able to define display behavior via the .render() method. Unknown, } impl FieldType { pub fn default_from_attr(attr: &PgAttribute) -> Self { match attr.regtype.as_str() { "integer" => Self::Integer, "text" => Self::Text, "timestamp" => Self::Timestamp { format: RFC_3339_S.to_owned(), }, "uuid" => Self::Uuid, _ => Self::Unknown, } } } /// Error when parsing a sqlx value to JSON #[derive(Debug, Error)] pub enum ParseError { #[error("incompatible json type")] BadJsonType, #[error("field not found in row")] FieldNotFound, #[error("unknown postgres type")] UnknownType, } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(tag = "t", content = "c")] pub enum Encodable { Integer(Option), Text(Option), Timestamptz(Option>), Uuid(Option), } impl Encodable { pub fn bind_onto<'a>( &'a self, query: sqlx::query::Query<'a, Postgres, ::Arguments<'a>>, ) -> sqlx::query::Query<'a, Postgres, ::Arguments<'a>> { match self { Self::Integer(value) => query.bind(value), Self::Text(value) => query.bind(value), Self::Timestamptz(value) => query.bind(value), Self::Uuid(value) => query.bind(value), } } } // impl<'a> Encode<'a, Postgres> for Encodable { // fn encode_by_ref( // &self, // buf: &mut ::ArgumentBuffer<'a>, // ) -> Result { // match self { // Self::Integer(value) => Encode::<'a, Postgres>::encode(value, buf), // Self::Text(value) => Encode::<'a, Postgres>::encode(value, buf), // Self::Timestamptz(value) => Encode::<'a, Postgres>::encode(value, buf), // Self::Uuid(value) => Encode::<'a, Postgres>::encode(value, buf), // } // } // } // // impl<'a> Decode<'a, Postgres> for FieldType { // fn decode( // value: ::ValueRef<'a>, // ) -> Result { // let value: String = Decode::<'a, Postgres>::decode(value)?; // Ok(serde_json::from_str(&value)?) // } // }