data_modelling_core/models/
odps.rs

1//! ODPS (Open Data Product Standard) models
2//!
3//! Defines structures for ODPS Data Products that link to ODCS Tables.
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::str::FromStr;
8
9use super::tag::Tag;
10
11/// ODPS API version
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum ODPSApiVersion {
15    #[serde(rename = "v0.9.0")]
16    V0_9_0,
17    #[serde(rename = "v1.0.0")]
18    V1_0_0,
19}
20
21/// ODPS status
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23pub enum ODPSStatus {
24    #[serde(rename = "proposed")]
25    Proposed,
26    #[serde(rename = "draft")]
27    Draft,
28    #[serde(rename = "active")]
29    Active,
30    #[serde(rename = "deprecated")]
31    Deprecated,
32    #[serde(rename = "retired")]
33    Retired,
34}
35
36/// Authoritative definition
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
38pub struct ODPSAuthoritativeDefinition {
39    /// Type of definition
40    pub r#type: String,
41    /// URL to the authority
42    pub url: String,
43    /// Optional description
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub description: Option<String>,
46}
47
48/// Custom property
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50pub struct ODPSCustomProperty {
51    /// Property name
52    pub property: String,
53    /// Property value
54    pub value: serde_json::Value,
55    /// Optional description
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub description: Option<String>,
58}
59
60/// ODPS description
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
62pub struct ODPSDescription {
63    /// Intended purpose
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub purpose: Option<String>,
66    /// Limitations
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub limitations: Option<String>,
69    /// Recommended usage
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub usage: Option<String>,
72    /// Authoritative definitions
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub authoritative_definitions: Option<Vec<ODPSAuthoritativeDefinition>>,
75    /// Custom properties
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub custom_properties: Option<Vec<ODPSCustomProperty>>,
78}
79
80/// Input port
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
82pub struct ODPSInputPort {
83    /// Port name
84    pub name: String,
85    /// Port version
86    pub version: String,
87    /// Contract ID (links to ODCS Table)
88    pub contract_id: String,
89    /// Tags
90    #[serde(default, deserialize_with = "deserialize_tags")]
91    pub tags: Vec<Tag>,
92    /// Custom properties
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub custom_properties: Option<Vec<ODPSCustomProperty>>,
95    /// Authoritative definitions
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub authoritative_definitions: Option<Vec<ODPSAuthoritativeDefinition>>,
98}
99
100/// SBOM (Software Bill of Materials)
101#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
102pub struct ODPSSBOM {
103    /// SBOM type
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub r#type: Option<String>,
106    /// URL to SBOM
107    pub url: String,
108}
109
110/// Input contract dependency
111#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
112pub struct ODPSInputContract {
113    /// Contract ID
114    pub id: String,
115    /// Contract version
116    pub version: String,
117}
118
119/// Output port
120#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
121pub struct ODPSOutputPort {
122    /// Port name
123    pub name: String,
124    /// Port description
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub description: Option<String>,
127    /// Port type
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub r#type: Option<String>,
130    /// Port version
131    pub version: String,
132    /// Contract ID (links to ODCS Table)
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub contract_id: Option<String>,
135    /// SBOM array
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub sbom: Option<Vec<ODPSSBOM>>,
138    /// Input contracts (dependencies)
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub input_contracts: Option<Vec<ODPSInputContract>>,
141    /// Tags
142    #[serde(default, deserialize_with = "deserialize_tags")]
143    pub tags: Vec<Tag>,
144    /// Custom properties
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub custom_properties: Option<Vec<ODPSCustomProperty>>,
147    /// Authoritative definitions
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub authoritative_definitions: Option<Vec<ODPSAuthoritativeDefinition>>,
150}
151
152/// Management port
153#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
154pub struct ODPSManagementPort {
155    /// Port name
156    pub name: String,
157    /// Content type
158    pub content: String,
159    /// Port type (rest or topic)
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub r#type: Option<String>,
162    /// URL to access endpoint
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub url: Option<String>,
165    /// Channel name
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub channel: Option<String>,
168    /// Description
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub description: Option<String>,
171    /// Tags
172    #[serde(default, deserialize_with = "deserialize_tags")]
173    pub tags: Vec<Tag>,
174    /// Custom properties
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub custom_properties: Option<Vec<ODPSCustomProperty>>,
177    /// Authoritative definitions
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub authoritative_definitions: Option<Vec<ODPSAuthoritativeDefinition>>,
180}
181
182/// Support channel
183#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
184pub struct ODPSSupport {
185    /// Channel name
186    pub channel: String,
187    /// Access URL
188    pub url: String,
189    /// Description
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub description: Option<String>,
192    /// Tool name
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub tool: Option<String>,
195    /// Scope
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub scope: Option<String>,
198    /// Invitation URL
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub invitation_url: Option<String>,
201    /// Tags
202    #[serde(default, deserialize_with = "deserialize_tags")]
203    pub tags: Vec<Tag>,
204    /// Custom properties
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub custom_properties: Option<Vec<ODPSCustomProperty>>,
207    /// Authoritative definitions
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub authoritative_definitions: Option<Vec<ODPSAuthoritativeDefinition>>,
210}
211
212/// Team member
213#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
214pub struct ODPSTeamMember {
215    /// Username or email
216    pub username: String,
217    /// Member name
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub name: Option<String>,
220    /// Description
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub description: Option<String>,
223    /// Role
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub role: Option<String>,
226    /// Date joined
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub date_in: Option<String>,
229    /// Date left
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub date_out: Option<String>,
232    /// Replaced by username
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub replaced_by_username: Option<String>,
235    /// Tags
236    #[serde(default, deserialize_with = "deserialize_tags")]
237    pub tags: Vec<Tag>,
238    /// Custom properties
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub custom_properties: Option<Vec<ODPSCustomProperty>>,
241    /// Authoritative definitions
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub authoritative_definitions: Option<Vec<ODPSAuthoritativeDefinition>>,
244}
245
246/// Team
247#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
248pub struct ODPSTeam {
249    /// Team name
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub name: Option<String>,
252    /// Team description
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub description: Option<String>,
255    /// Team members
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub members: Option<Vec<ODPSTeamMember>>,
258    /// Tags
259    #[serde(default, deserialize_with = "deserialize_tags")]
260    pub tags: Vec<Tag>,
261    /// Custom properties
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub custom_properties: Option<Vec<ODPSCustomProperty>>,
264    /// Authoritative definitions
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub authoritative_definitions: Option<Vec<ODPSAuthoritativeDefinition>>,
267}
268
269/// Data Product - main structure
270#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
271pub struct ODPSDataProduct {
272    /// API version
273    #[serde(rename = "apiVersion")]
274    pub api_version: String,
275    /// Kind (always "DataProduct")
276    pub kind: String,
277    /// Unique identifier
278    pub id: String,
279    /// Product name
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub name: Option<String>,
282    /// Product version
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub version: Option<String>,
285    /// Status
286    pub status: ODPSStatus,
287    /// Business domain
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub domain: Option<String>,
290    /// Tenant/organization
291    #[serde(skip_serializing_if = "Option::is_none")]
292    pub tenant: Option<String>,
293    /// Authoritative definitions
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub authoritative_definitions: Option<Vec<ODPSAuthoritativeDefinition>>,
296    /// Description
297    #[serde(skip_serializing_if = "Option::is_none")]
298    pub description: Option<ODPSDescription>,
299    /// Custom properties
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub custom_properties: Option<Vec<ODPSCustomProperty>>,
302    /// Tags
303    #[serde(default, deserialize_with = "deserialize_tags")]
304    pub tags: Vec<Tag>,
305    /// Input ports
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub input_ports: Option<Vec<ODPSInputPort>>,
308    /// Output ports
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub output_ports: Option<Vec<ODPSOutputPort>>,
311    /// Management ports
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub management_ports: Option<Vec<ODPSManagementPort>>,
314    /// Support channels
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub support: Option<Vec<ODPSSupport>>,
317    /// Team
318    #[serde(skip_serializing_if = "Option::is_none")]
319    pub team: Option<ODPSTeam>,
320    /// Product creation timestamp
321    #[serde(skip_serializing_if = "Option::is_none")]
322    pub product_created_ts: Option<String>,
323    /// Creation timestamp (internal)
324    #[serde(skip_serializing_if = "Option::is_none")]
325    pub created_at: Option<DateTime<Utc>>,
326    /// Last update timestamp (internal)
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub updated_at: Option<DateTime<Utc>>,
329}
330
331/// Deserialize tags with backward compatibility (supports Vec<String> and Vec<Tag>)
332fn deserialize_tags<'de, D>(deserializer: D) -> Result<Vec<Tag>, D::Error>
333where
334    D: serde::Deserializer<'de>,
335{
336    // Accept either Vec<String> (backward compatibility) or Vec<Tag>
337    struct TagVisitor;
338
339    impl<'de> serde::de::Visitor<'de> for TagVisitor {
340        type Value = Vec<Tag>;
341
342        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
343            formatter.write_str("a vector of tags (strings or Tag objects)")
344        }
345
346        fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
347        where
348            A: serde::de::SeqAccess<'de>,
349        {
350            let mut tags = Vec::new();
351            while let Some(item) = seq.next_element::<serde_json::Value>()? {
352                match item {
353                    serde_json::Value::String(s) => {
354                        // Backward compatibility: parse string as Tag
355                        if let Ok(tag) = Tag::from_str(&s) {
356                            tags.push(tag);
357                        }
358                    }
359                    _ => {
360                        // Try to deserialize as Tag directly (if it's a string in JSON)
361                        if let serde_json::Value::String(s) = item
362                            && let Ok(tag) = Tag::from_str(&s)
363                        {
364                            tags.push(tag);
365                        }
366                    }
367                }
368            }
369            Ok(tags)
370        }
371    }
372
373    deserializer.deserialize_seq(TagVisitor)
374}