data_modelling_sdk/models/
column.rs

1//! Column model for the SDK
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Foreign key reference to another table's column
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct ForeignKey {
9    /// Target table ID (UUID as string)
10    pub table_id: String,
11    /// Column name in the target table
12    pub column_name: String,
13}
14
15/// Column model representing a field in a table
16///
17/// A column defines a single field with a data type, constraints, and optional metadata.
18/// Columns can be primary keys, foreign keys, nullable, and have various constraints.
19///
20/// # Example
21///
22/// ```rust
23/// use data_modelling_sdk::models::Column;
24///
25/// let column = Column::new("id".to_string(), "INT".to_string());
26/// ```
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
28pub struct Column {
29    /// Column name
30    pub name: String,
31    /// Data type (e.g., "INT", "VARCHAR(100)", "TIMESTAMP")
32    pub data_type: String,
33    /// Whether the column allows NULL values (default: true)
34    #[serde(default = "default_true")]
35    pub nullable: bool,
36    /// Whether this column is part of the primary key (default: false)
37    #[serde(default)]
38    pub primary_key: bool,
39    /// Whether this column is a secondary key (default: false)
40    #[serde(default)]
41    pub secondary_key: bool,
42    /// Composite key name if this column is part of a composite key
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub composite_key: Option<String>,
45    /// Foreign key reference if this column references another table
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub foreign_key: Option<ForeignKey>,
48    /// Additional constraints (e.g., "CHECK", "UNIQUE")
49    #[serde(default)]
50    pub constraints: Vec<String>,
51    /// Column description/documentation
52    #[serde(default)]
53    pub description: String,
54    /// Validation errors and warnings
55    #[serde(default)]
56    pub errors: Vec<HashMap<String, serde_json::Value>>,
57    /// Quality rules and checks
58    #[serde(default)]
59    pub quality: Vec<HashMap<String, serde_json::Value>>,
60    /// JSON Schema $ref reference path
61    #[serde(skip_serializing_if = "Option::is_none", rename = "$ref")]
62    pub ref_path: Option<String>,
63    /// Enum values if this column is an enumeration type
64    #[serde(default)]
65    pub enum_values: Vec<String>,
66    /// Display order for UI rendering
67    #[serde(default)]
68    pub column_order: i32,
69}
70
71fn default_true() -> bool {
72    true
73}
74
75impl Column {
76    /// Create a new column with the given name and data type
77    ///
78    /// # Arguments
79    ///
80    /// * `name` - The column name (must be valid according to naming conventions)
81    /// * `data_type` - The data type string (e.g., "INT", "VARCHAR(100)")
82    ///
83    /// # Returns
84    ///
85    /// A new `Column` instance with default values (nullable=true, primary_key=false).
86    ///
87    /// # Example
88    ///
89    /// ```rust
90    /// use data_modelling_sdk::models::Column;
91    ///
92    /// let col = Column::new("user_id".to_string(), "BIGINT".to_string());
93    /// ```
94    pub fn new(name: String, data_type: String) -> Self {
95        Self {
96            name,
97            data_type: normalize_data_type(&data_type),
98            nullable: true,
99            primary_key: false,
100            secondary_key: false,
101            composite_key: None,
102            foreign_key: None,
103            constraints: Vec::new(),
104            description: String::new(),
105            errors: Vec::new(),
106            quality: Vec::new(),
107            ref_path: None,
108            enum_values: Vec::new(),
109            column_order: 0,
110        }
111    }
112}
113
114fn normalize_data_type(data_type: &str) -> String {
115    if data_type.is_empty() {
116        return data_type.to_string();
117    }
118
119    let upper = data_type.to_uppercase();
120
121    // Handle STRUCT<...>, ARRAY<...>, MAP<...> preserving inner content
122    if upper.starts_with("STRUCT") {
123        if let Some(start) = data_type.find('<')
124            && let Some(end) = data_type.rfind('>')
125        {
126            let inner = &data_type[start + 1..end];
127            return format!("STRUCT<{}>", inner);
128        }
129        return format!("STRUCT{}", &data_type[6..]);
130    } else if upper.starts_with("ARRAY") {
131        if let Some(start) = data_type.find('<')
132            && let Some(end) = data_type.rfind('>')
133        {
134            let inner = &data_type[start + 1..end];
135            return format!("ARRAY<{}>", inner);
136        }
137        return format!("ARRAY{}", &data_type[5..]);
138    } else if upper.starts_with("MAP") {
139        if let Some(start) = data_type.find('<')
140            && let Some(end) = data_type.rfind('>')
141        {
142            let inner = &data_type[start + 1..end];
143            return format!("MAP<{}>", inner);
144        }
145        return format!("MAP{}", &data_type[3..]);
146    }
147
148    upper
149}