use super::column::Column;
use super::enums::{
DataVaultClassification, DatabaseType, InfrastructureType, MedallionLayer, ModelingLevel,
SCDPattern,
};
use super::tag::Tag;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json;
use std::collections::HashMap;
use std::str::FromStr;
use uuid::Uuid;
fn deserialize_tags<'de, D>(deserializer: D) -> Result<Vec<Tag>, D::Error>
where
D: Deserializer<'de>,
{
struct TagVisitor;
impl<'de> serde::de::Visitor<'de> for TagVisitor {
type Value = Vec<Tag>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a vector of tags (strings or Tag objects)")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut tags = Vec::new();
while let Some(item) = seq.next_element::<serde_json::Value>()? {
match item {
serde_json::Value::String(s) => {
if let Ok(tag) = Tag::from_str(&s) {
tags.push(tag);
}
}
_ => {
if let serde_json::Value::String(s) = item
&& let Ok(tag) = Tag::from_str(&s)
{
tags.push(tag);
}
}
}
}
Ok(tags)
}
}
deserializer.deserialize_seq(TagVisitor)
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Position {
pub x: f64,
pub y: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SlaProperty {
pub property: String,
pub value: serde_json::Value,
pub unit: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub element: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub driver: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scheduler: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schedule: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ContactDetails {
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub other: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Table {
pub id: Uuid,
pub name: String,
pub columns: Vec<Column>,
#[serde(skip_serializing_if = "Option::is_none", alias = "database_type")]
pub database_type: Option<DatabaseType>,
#[serde(skip_serializing_if = "Option::is_none", alias = "catalog_name")]
pub catalog_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", alias = "schema_name")]
pub schema_name: Option<String>,
#[serde(default, alias = "medallion_layers")]
pub medallion_layers: Vec<MedallionLayer>,
#[serde(skip_serializing_if = "Option::is_none", alias = "scd_pattern")]
pub scd_pattern: Option<SCDPattern>,
#[serde(
skip_serializing_if = "Option::is_none",
alias = "data_vault_classification"
)]
pub data_vault_classification: Option<DataVaultClassification>,
#[serde(skip_serializing_if = "Option::is_none", alias = "modeling_level")]
pub modeling_level: Option<ModelingLevel>,
#[serde(default, deserialize_with = "deserialize_tags")]
pub tags: Vec<Tag>,
#[serde(default, alias = "odcl_metadata")]
pub odcl_metadata: HashMap<String, serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub owner: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sla: Option<Vec<SlaProperty>>,
#[serde(skip_serializing_if = "Option::is_none", alias = "contact_details")]
pub contact_details: Option<ContactDetails>,
#[serde(skip_serializing_if = "Option::is_none", alias = "infrastructure_type")]
pub infrastructure_type: Option<InfrastructureType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub position: Option<Position>,
#[serde(skip_serializing_if = "Option::is_none", alias = "yaml_file_path")]
pub yaml_file_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", alias = "drawio_cell_id")]
pub drawio_cell_id: Option<String>,
#[serde(default)]
pub quality: Vec<HashMap<String, serde_json::Value>>,
#[serde(default)]
pub errors: Vec<HashMap<String, serde_json::Value>>,
#[serde(alias = "created_at")]
pub created_at: DateTime<Utc>,
#[serde(alias = "updated_at")]
pub updated_at: DateTime<Utc>,
}
impl Table {
pub fn new(name: String, columns: Vec<Column>) -> Self {
let now = Utc::now();
let id = Self::generate_id(&name, None, None, None);
Self {
id,
name,
columns,
database_type: None,
catalog_name: None,
schema_name: None,
medallion_layers: Vec::new(),
scd_pattern: None,
data_vault_classification: None,
modeling_level: None,
tags: Vec::new(),
odcl_metadata: HashMap::new(),
owner: None,
sla: None,
contact_details: None,
infrastructure_type: None,
notes: None,
position: None,
yaml_file_path: None,
drawio_cell_id: None,
quality: Vec::new(),
errors: Vec::new(),
created_at: now,
updated_at: now,
}
}
pub fn get_unique_key(&self) -> (Option<String>, String, Option<String>, Option<String>) {
(
self.database_type.as_ref().map(|dt| format!("{:?}", dt)),
self.name.clone(),
self.catalog_name.clone(),
self.schema_name.clone(),
)
}
pub fn generate_id(
_name: &str,
_database_type: Option<&DatabaseType>,
_catalog_name: Option<&str>,
_schema_name: Option<&str>,
) -> Uuid {
Uuid::new_v4()
}
pub fn from_table_data(table_data: &crate::import::TableData) -> Self {
use crate::models::Column;
let table_name = table_data
.name
.clone()
.unwrap_or_else(|| format!("table_{}", table_data.table_index));
let columns: Vec<Column> = table_data
.columns
.iter()
.map(|col_data| Column {
id: col_data.id.clone(),
name: col_data.name.clone(),
business_name: col_data.business_name.clone(),
description: col_data.description.clone().unwrap_or_default(),
data_type: col_data.data_type.clone(),
physical_type: col_data.physical_type.clone(),
physical_name: col_data.physical_name.clone(),
logical_type_options: col_data.logical_type_options.clone(),
primary_key: col_data.primary_key,
primary_key_position: col_data.primary_key_position,
unique: col_data.unique,
nullable: col_data.nullable,
partitioned: col_data.partitioned,
partition_key_position: col_data.partition_key_position,
clustered: col_data.clustered,
classification: col_data.classification.clone(),
critical_data_element: col_data.critical_data_element,
encrypted_name: col_data.encrypted_name.clone(),
transform_source_objects: col_data.transform_source_objects.clone(),
transform_logic: col_data.transform_logic.clone(),
transform_description: col_data.transform_description.clone(),
examples: col_data.examples.clone(),
default_value: col_data.default_value.clone(),
relationships: col_data.relationships.clone(),
authoritative_definitions: col_data.authoritative_definitions.clone(),
quality: col_data.quality.clone().unwrap_or_default(),
enum_values: col_data.enum_values.clone().unwrap_or_default(),
tags: col_data.tags.clone(),
custom_properties: col_data.custom_properties.clone(),
..Default::default()
})
.collect();
let mut table = Self::new(table_name, columns);
if let Some(ref domain) = table_data.domain {
table
.odcl_metadata
.insert("domain".to_string(), serde_json::json!(domain));
}
if let Some(ref version) = table_data.version {
table
.odcl_metadata
.insert("version".to_string(), serde_json::json!(version));
}
if let Some(ref status) = table_data.status {
table
.odcl_metadata
.insert("status".to_string(), serde_json::json!(status));
}
if let Some(ref description) = table_data.description {
table
.odcl_metadata
.insert("description".to_string(), serde_json::json!(description));
}
if let Some(ref team) = table_data.team {
table.odcl_metadata.insert(
"team".to_string(),
serde_json::to_value(team).unwrap_or_default(),
);
}
if let Some(ref support) = table_data.support {
table.odcl_metadata.insert(
"support".to_string(),
serde_json::to_value(support).unwrap_or_default(),
);
}
table
}
}