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    /// Enum values if this column is an enumeration type
61    #[serde(default)]
62    pub enum_values: Vec<String>,
63    /// Display order for UI rendering
64    #[serde(default)]
65    pub column_order: i32,
66}
67
68fn default_true() -> bool {
69    true
70}
71
72impl Column {
73    /// Create a new column with the given name and data type
74    ///
75    /// # Arguments
76    ///
77    /// * `name` - The column name (must be valid according to naming conventions)
78    /// * `data_type` - The data type string (e.g., "INT", "VARCHAR(100)")
79    ///
80    /// # Returns
81    ///
82    /// A new `Column` instance with default values (nullable=true, primary_key=false).
83    ///
84    /// # Example
85    ///
86    /// ```rust
87    /// use data_modelling_sdk::models::Column;
88    ///
89    /// let col = Column::new("user_id".to_string(), "BIGINT".to_string());
90    /// ```
91    pub fn new(name: String, data_type: String) -> Self {
92        Self {
93            name,
94            data_type: normalize_data_type(&data_type),
95            nullable: true,
96            primary_key: false,
97            secondary_key: false,
98            composite_key: None,
99            foreign_key: None,
100            constraints: Vec::new(),
101            description: String::new(),
102            errors: Vec::new(),
103            quality: Vec::new(),
104            enum_values: Vec::new(),
105            column_order: 0,
106        }
107    }
108}
109
110fn normalize_data_type(data_type: &str) -> String {
111    if data_type.is_empty() {
112        return data_type.to_string();
113    }
114
115    let upper = data_type.to_uppercase();
116
117    // Handle STRUCT<...>, ARRAY<...>, MAP<...> preserving inner content
118    if upper.starts_with("STRUCT") {
119        if let Some(start) = data_type.find('<')
120            && let Some(end) = data_type.rfind('>')
121        {
122            let inner = &data_type[start + 1..end];
123            return format!("STRUCT<{}>", inner);
124        }
125        return format!("STRUCT{}", &data_type[6..]);
126    } else if upper.starts_with("ARRAY") {
127        if let Some(start) = data_type.find('<')
128            && let Some(end) = data_type.rfind('>')
129        {
130            let inner = &data_type[start + 1..end];
131            return format!("ARRAY<{}>", inner);
132        }
133        return format!("ARRAY{}", &data_type[5..]);
134    } else if upper.starts_with("MAP") {
135        if let Some(start) = data_type.find('<')
136            && let Some(end) = data_type.rfind('>')
137        {
138            let inner = &data_type[start + 1..end];
139            return format!("MAP<{}>", inner);
140        }
141        return format!("MAP{}", &data_type[3..]);
142    }
143
144    upper
145}