data_modelling_sdk/models/
relationship.rs

1//! Relationship model for the SDK
2
3use super::enums::{Cardinality, InfrastructureType, RelationshipType};
4use super::table::{ContactDetails, SlaProperty};
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9/// Foreign key column mapping details
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11pub struct ForeignKeyDetails {
12    /// Column name in the source table
13    pub source_column: String,
14    /// Column name in the target table
15    pub target_column: String,
16}
17
18/// ETL job metadata for data flow relationships
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
20pub struct ETLJobMetadata {
21    /// Name of the ETL job that creates this relationship
22    pub job_name: String,
23    /// Optional notes about the ETL job
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub notes: Option<String>,
26    /// Job execution frequency (e.g., "daily", "hourly")
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub frequency: Option<String>,
29}
30
31/// Connection point coordinates for relationship visualization
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
33pub struct ConnectionPoint {
34    /// X coordinate
35    pub x: f64,
36    /// Y coordinate
37    pub y: f64,
38}
39
40/// Visual metadata for relationship rendering on canvas
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
42pub struct VisualMetadata {
43    /// Connection point identifier on source table
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub source_connection_point: Option<String>,
46    /// Connection point identifier on target table
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub target_connection_point: Option<String>,
49    /// Waypoints for routing the relationship line
50    #[serde(default)]
51    pub routing_waypoints: Vec<ConnectionPoint>,
52    /// Position for the relationship label
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub label_position: Option<ConnectionPoint>,
55}
56
57/// Relationship model representing a connection between two tables
58///
59/// Relationships can represent foreign keys, data flows, dependencies, or ETL transformations.
60/// They connect a source table to a target table with optional metadata about cardinality,
61/// foreign key details, and ETL job information.
62///
63/// # Example
64///
65/// ```rust
66/// use data_modelling_sdk::models::Relationship;
67///
68/// let source_id = uuid::Uuid::new_v4();
69/// let target_id = uuid::Uuid::new_v4();
70/// let relationship = Relationship::new(source_id, target_id);
71/// ```
72///
73/// # Example with Metadata (Data Flow Relationship)
74///
75/// ```rust
76/// use data_modelling_sdk::models::{Relationship, InfrastructureType, ContactDetails, SlaProperty};
77/// use serde_json::json;
78/// use uuid::Uuid;
79///
80/// let source_id = Uuid::new_v4();
81/// let target_id = Uuid::new_v4();
82/// let mut relationship = Relationship::new(source_id, target_id);
83/// relationship.owner = Some("Data Engineering Team".to_string());
84/// relationship.infrastructure_type = Some(InfrastructureType::Kafka);
85/// relationship.contact_details = Some(ContactDetails {
86///     email: Some("team@example.com".to_string()),
87///     phone: None,
88///     name: Some("Data Team".to_string()),
89///     role: Some("Data Owner".to_string()),
90///     other: None,
91/// });
92/// relationship.sla = Some(vec![SlaProperty {
93///     property: "latency".to_string(),
94///     value: json!(2),
95///     unit: "hours".to_string(),
96///     description: Some("Data flow must complete within 2 hours".to_string()),
97///     element: None,
98///     driver: Some("operational".to_string()),
99///     scheduler: None,
100///     schedule: None,
101/// }]);
102/// relationship.notes = Some("ETL pipeline from source to target".to_string());
103/// ```
104#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
105pub struct Relationship {
106    /// Unique identifier for the relationship (UUIDv4)
107    pub id: Uuid,
108    /// ID of the source table
109    pub source_table_id: Uuid,
110    /// ID of the target table
111    pub target_table_id: Uuid,
112    /// Cardinality (OneToOne, OneToMany, ManyToMany)
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub cardinality: Option<Cardinality>,
115    /// Whether the source side is optional (nullable foreign key)
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub source_optional: Option<bool>,
118    /// Whether the target side is optional
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub target_optional: Option<bool>,
121    /// Foreign key column mapping details
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub foreign_key_details: Option<ForeignKeyDetails>,
124    /// ETL job metadata for data flow relationships
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub etl_job_metadata: Option<ETLJobMetadata>,
127    /// Type of relationship (ForeignKey, DataFlow, Dependency, ETL)
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub relationship_type: Option<RelationshipType>,
130    /// Optional notes about the relationship
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub notes: Option<String>,
133    /// Owner information (person, team, or organization name) for Data Flow relationships
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub owner: Option<String>,
136    /// SLA (Service Level Agreement) information (ODCS-inspired but lightweight format)
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub sla: Option<Vec<SlaProperty>>,
139    /// Contact details for responsible parties
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub contact_details: Option<ContactDetails>,
142    /// Infrastructure type (hosting platform, service, or tool) for Data Flow relationships
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub infrastructure_type: Option<InfrastructureType>,
145    /// Visual metadata for canvas rendering
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub visual_metadata: Option<VisualMetadata>,
148    /// Draw.io edge ID for diagram integration
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub drawio_edge_id: Option<String>,
151    /// Creation timestamp
152    pub created_at: DateTime<Utc>,
153    /// Last update timestamp
154    pub updated_at: DateTime<Utc>,
155}
156
157impl Relationship {
158    /// Create a new relationship between two tables
159    ///
160    /// # Arguments
161    ///
162    /// * `source_table_id` - UUID of the source table
163    /// * `target_table_id` - UUID of the target table
164    ///
165    /// # Returns
166    ///
167    /// A new `Relationship` instance with a generated UUIDv4 ID and current timestamps.
168    ///
169    /// # Example
170    ///
171    /// ```rust
172    /// use data_modelling_sdk::models::Relationship;
173    ///
174    /// let source_id = uuid::Uuid::new_v4();
175    /// let target_id = uuid::Uuid::new_v4();
176    /// let rel = Relationship::new(source_id, target_id);
177    /// ```
178    pub fn new(source_table_id: Uuid, target_table_id: Uuid) -> Self {
179        let now = Utc::now();
180        let id = Self::generate_id(source_table_id, target_table_id);
181        Self {
182            id,
183            source_table_id,
184            target_table_id,
185            cardinality: None,
186            source_optional: None,
187            target_optional: None,
188            foreign_key_details: None,
189            etl_job_metadata: None,
190            relationship_type: None,
191            notes: None,
192            owner: None,
193            sla: None,
194            contact_details: None,
195            infrastructure_type: None,
196            visual_metadata: None,
197            drawio_edge_id: None,
198            created_at: now,
199            updated_at: now,
200        }
201    }
202
203    /// Generate a UUIDv4 for a new relationship id.
204    ///
205    /// Note: params are retained for backward-compatibility with previous deterministic-v5 API.
206    pub fn generate_id(_source_table_id: Uuid, _target_table_id: Uuid) -> Uuid {
207        Uuid::new_v4()
208    }
209}