data_modelling_core/models/
cads.rs

1//! CADS (Compute Asset Description Specification) models
2//!
3//! Defines structures for CADS v1.0 assets including AI/ML models, applications, pipelines, and systems.
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::str::FromStr;
9
10use super::tag::Tag;
11
12/// CADS asset kinds
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "PascalCase")]
15pub enum CADSKind {
16    AIModel,
17    MLPipeline,
18    Application,
19    DataPipeline,
20    ETLProcess,
21    ETLPipeline,
22    SourceSystem,
23    DestinationSystem,
24}
25
26/// CADS asset status
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28pub enum CADSStatus {
29    #[serde(rename = "draft")]
30    Draft,
31    #[serde(rename = "validated")]
32    Validated,
33    #[serde(rename = "production")]
34    Production,
35    #[serde(rename = "deprecated")]
36    Deprecated,
37}
38
39/// CADS description object
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
41#[serde(rename_all = "camelCase")]
42pub struct CADSDescription {
43    /// Purpose of the asset
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub purpose: Option<String>,
46    /// Usage instructions
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub usage: Option<String>,
49    /// Limitations and constraints
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub limitations: Option<String>,
52    /// External links and references
53    #[serde(skip_serializing_if = "Option::is_none", alias = "external_links")]
54    pub external_links: Option<Vec<CADSExternalLink>>,
55}
56
57/// External link in CADS description
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
59pub struct CADSExternalLink {
60    /// URL of the external link
61    pub url: String,
62    /// Description of the link
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub description: Option<String>,
65}
66
67/// CADS runtime configuration
68#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
69pub struct CADSRuntime {
70    /// Runtime environment
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub environment: Option<String>,
73    /// Service endpoints
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub endpoints: Option<Vec<String>>,
76    /// Container configuration
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub container: Option<CADSRuntimeContainer>,
79    /// Resource requirements
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub resources: Option<CADSRuntimeResources>,
82}
83
84/// Container configuration
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
86pub struct CADSRuntimeContainer {
87    /// Container image
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub image: Option<String>,
90}
91
92/// Resource requirements
93#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
94pub struct CADSRuntimeResources {
95    /// CPU requirements
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub cpu: Option<String>,
98    /// Memory requirements
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub memory: Option<String>,
101    /// GPU requirements
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub gpu: Option<String>,
104}
105
106/// CADS SLA properties
107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
108pub struct CADSSLA {
109    /// SLA properties array
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub properties: Option<Vec<CADSSLAProperty>>,
112}
113
114/// SLA property
115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
116pub struct CADSSLAProperty {
117    /// SLA element name
118    pub element: String,
119    /// Value (number or string)
120    pub value: serde_json::Value,
121    /// Unit of measurement
122    pub unit: String,
123    /// Driver (e.g., "operational", "compliance")
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub driver: Option<String>,
126}
127
128/// CADS pricing model
129#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
130#[serde(rename_all = "camelCase")]
131pub struct CADSPricing {
132    /// Pricing model type
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub model: Option<CADSPricingModel>,
135    /// Currency code
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub currency: Option<String>,
138    /// Unit cost
139    #[serde(skip_serializing_if = "Option::is_none", alias = "unit_cost")]
140    pub unit_cost: Option<f64>,
141    /// Billing unit
142    #[serde(skip_serializing_if = "Option::is_none", alias = "billing_unit")]
143    pub billing_unit: Option<String>,
144    /// Additional notes
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub notes: Option<String>,
147}
148
149/// Pricing model enum
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
151#[serde(rename_all = "snake_case")]
152pub enum CADSPricingModel {
153    PerRequest,
154    PerHour,
155    PerBatch,
156    Subscription,
157    Internal,
158}
159
160/// Team member
161#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
162pub struct CADSTeamMember {
163    /// Role of the team member
164    pub role: String,
165    /// Name of the team member
166    pub name: String,
167    /// Contact information
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub contact: Option<String>,
170}
171
172/// Risk classification
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
174pub enum CADSRiskClassification {
175    #[serde(rename = "minimal")]
176    Minimal,
177    #[serde(rename = "low")]
178    Low,
179    #[serde(rename = "medium")]
180    Medium,
181    #[serde(rename = "high")]
182    High,
183}
184
185/// Impact area
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
187#[serde(rename_all = "lowercase")]
188pub enum CADSImpactArea {
189    Fairness,
190    Privacy,
191    Safety,
192    Security,
193    Financial,
194    Operational,
195    Reputational,
196}
197
198/// Risk assessment
199#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
200pub struct CADSRiskAssessment {
201    /// Assessment methodology
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub methodology: Option<String>,
204    /// Assessment date
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub date: Option<String>,
207    /// Assessor name
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub assessor: Option<String>,
210}
211
212/// Risk mitigation
213#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
214pub struct CADSRiskMitigation {
215    /// Mitigation description
216    pub description: String,
217    /// Mitigation status
218    pub status: CADSMitigationStatus,
219}
220
221/// Mitigation status
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
223#[serde(rename_all = "lowercase")]
224pub enum CADSMitigationStatus {
225    Planned,
226    Implemented,
227    Verified,
228}
229
230/// CADS risk management
231#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
232#[serde(rename_all = "camelCase")]
233pub struct CADSRisk {
234    /// Risk classification
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub classification: Option<CADSRiskClassification>,
237    /// Impact areas
238    #[serde(skip_serializing_if = "Option::is_none", alias = "impact_areas")]
239    pub impact_areas: Option<Vec<CADSImpactArea>>,
240    /// Intended use
241    #[serde(skip_serializing_if = "Option::is_none", alias = "intended_use")]
242    pub intended_use: Option<String>,
243    /// Out of scope use
244    #[serde(skip_serializing_if = "Option::is_none", alias = "out_of_scope_use")]
245    pub out_of_scope_use: Option<String>,
246    /// Risk assessment
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub assessment: Option<CADSRiskAssessment>,
249    /// Risk mitigations
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub mitigations: Option<Vec<CADSRiskMitigation>>,
252}
253
254/// Compliance framework
255#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
256pub struct CADSComplianceFramework {
257    /// Framework name
258    pub name: String,
259    /// Framework category
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub category: Option<String>,
262    /// Compliance status
263    pub status: CADSComplianceStatus,
264}
265
266/// Compliance status
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
268#[serde(rename_all = "snake_case")]
269pub enum CADSComplianceStatus {
270    NotApplicable,
271    Assessed,
272    Compliant,
273    NonCompliant,
274}
275
276/// Compliance control
277#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
278pub struct CADSComplianceControl {
279    /// Control ID
280    pub id: String,
281    /// Control description
282    pub description: String,
283    /// Evidence
284    #[serde(skip_serializing_if = "Option::is_none")]
285    pub evidence: Option<String>,
286}
287
288/// CADS compliance
289#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
290pub struct CADSCompliance {
291    /// Compliance frameworks
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub frameworks: Option<Vec<CADSComplianceFramework>>,
294    /// Compliance controls
295    #[serde(skip_serializing_if = "Option::is_none")]
296    pub controls: Option<Vec<CADSComplianceControl>>,
297}
298
299/// Validation profile applies to
300#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
301#[serde(rename_all = "camelCase")]
302pub struct CADSValidationProfileAppliesTo {
303    /// Asset kind
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub kind: Option<String>,
306    /// Risk classification
307    #[serde(skip_serializing_if = "Option::is_none", alias = "risk_classification")]
308    pub risk_classification: Option<String>,
309}
310
311/// Validation profile
312#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
313#[serde(rename_all = "camelCase")]
314pub struct CADSValidationProfile {
315    /// Profile name
316    pub name: String,
317    /// Applies to criteria
318    #[serde(skip_serializing_if = "Option::is_none", alias = "applies_to")]
319    pub applies_to: Option<CADSValidationProfileAppliesTo>,
320    /// Required checks
321    #[serde(alias = "required_checks")]
322    pub required_checks: Vec<String>,
323}
324
325/// BPMN model format
326#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
327#[serde(rename_all = "kebab-case")]
328pub enum CADSBPMNFormat {
329    #[serde(rename = "bpmn20-xml")]
330    Bpmn20Xml,
331    #[serde(rename = "json")]
332    Json,
333}
334
335/// BPMN model reference
336#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
337pub struct CADSBPMNModel {
338    /// Model name
339    pub name: String,
340    /// Reference to BPMN model
341    pub reference: String,
342    /// Format
343    pub format: CADSBPMNFormat,
344    /// Description
345    #[serde(skip_serializing_if = "Option::is_none")]
346    pub description: Option<String>,
347}
348
349/// DMN model format
350#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
351#[serde(rename_all = "kebab-case")]
352pub enum CADSDMNFormat {
353    #[serde(rename = "dmn13-xml")]
354    Dmn13Xml,
355    #[serde(rename = "json")]
356    Json,
357}
358
359/// DMN model reference
360#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
361pub struct CADSDMNModel {
362    /// Model name
363    pub name: String,
364    /// Reference to DMN model
365    pub reference: String,
366    /// Format
367    pub format: CADSDMNFormat,
368    /// Description
369    #[serde(skip_serializing_if = "Option::is_none")]
370    pub description: Option<String>,
371}
372
373/// OpenAPI spec format
374#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
375#[serde(rename_all = "kebab-case")]
376pub enum CADSOpenAPIFormat {
377    #[serde(rename = "openapi-3.0")]
378    Openapi30,
379    #[serde(rename = "openapi-3.1")]
380    Openapi31,
381    #[serde(rename = "swagger-2.0")]
382    Swagger20,
383}
384
385/// OpenAPI spec reference
386#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
387pub struct CADSOpenAPISpec {
388    /// Spec name
389    pub name: String,
390    /// Reference to OpenAPI spec
391    pub reference: String,
392    /// Format
393    pub format: CADSOpenAPIFormat,
394    /// Description
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub description: Option<String>,
397}
398
399/// CADS Asset - main structure
400#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
401#[serde(rename_all = "camelCase")]
402pub struct CADSAsset {
403    /// API version
404    #[serde(alias = "api_version")]
405    pub api_version: String,
406    /// Asset kind
407    pub kind: CADSKind,
408    /// Unique identifier (UUID or URN)
409    pub id: String,
410    /// Asset name
411    pub name: String,
412    /// Version
413    pub version: String,
414    /// Status
415    pub status: CADSStatus,
416    /// Domain
417    #[serde(skip_serializing_if = "Option::is_none")]
418    pub domain: Option<String>,
419    /// Tags
420    #[serde(default, deserialize_with = "deserialize_tags")]
421    pub tags: Vec<Tag>,
422    /// Description
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub description: Option<CADSDescription>,
425    /// Runtime configuration
426    #[serde(skip_serializing_if = "Option::is_none")]
427    pub runtime: Option<CADSRuntime>,
428    /// SLA
429    #[serde(skip_serializing_if = "Option::is_none")]
430    pub sla: Option<CADSSLA>,
431    /// Pricing
432    #[serde(skip_serializing_if = "Option::is_none")]
433    pub pricing: Option<CADSPricing>,
434    /// Team
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub team: Option<Vec<CADSTeamMember>>,
437    /// Risk management
438    #[serde(skip_serializing_if = "Option::is_none")]
439    pub risk: Option<CADSRisk>,
440    /// Compliance
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub compliance: Option<CADSCompliance>,
443    /// Validation profiles
444    #[serde(skip_serializing_if = "Option::is_none", alias = "validation_profiles")]
445    pub validation_profiles: Option<Vec<CADSValidationProfile>>,
446    /// BPMN models
447    #[serde(skip_serializing_if = "Option::is_none", alias = "bpmn_models")]
448    pub bpmn_models: Option<Vec<CADSBPMNModel>>,
449    /// DMN models
450    #[serde(skip_serializing_if = "Option::is_none", alias = "dmn_models")]
451    pub dmn_models: Option<Vec<CADSDMNModel>>,
452    /// OpenAPI specifications
453    #[serde(skip_serializing_if = "Option::is_none", alias = "openapi_specs")]
454    pub openapi_specs: Option<Vec<CADSOpenAPISpec>>,
455    /// Custom properties
456    #[serde(skip_serializing_if = "Option::is_none", alias = "custom_properties")]
457    pub custom_properties: Option<HashMap<String, serde_json::Value>>,
458    /// Creation timestamp
459    #[serde(skip_serializing_if = "Option::is_none", alias = "created_at")]
460    pub created_at: Option<DateTime<Utc>>,
461    /// Last update timestamp
462    #[serde(skip_serializing_if = "Option::is_none", alias = "updated_at")]
463    pub updated_at: Option<DateTime<Utc>>,
464}
465
466/// Deserialize tags with backward compatibility (supports Vec<String> and Vec<Tag>)
467fn deserialize_tags<'de, D>(deserializer: D) -> Result<Vec<Tag>, D::Error>
468where
469    D: serde::Deserializer<'de>,
470{
471    // Accept either Vec<String> (backward compatibility) or Vec<Tag>
472    struct TagVisitor;
473
474    impl<'de> serde::de::Visitor<'de> for TagVisitor {
475        type Value = Vec<Tag>;
476
477        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
478            formatter.write_str("a vector of tags (strings or Tag objects)")
479        }
480
481        fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
482        where
483            A: serde::de::SeqAccess<'de>,
484        {
485            let mut tags = Vec::new();
486            while let Some(item) = seq.next_element::<serde_json::Value>()? {
487                match item {
488                    serde_json::Value::String(s) => {
489                        // Backward compatibility: parse string as Tag
490                        if let Ok(tag) = Tag::from_str(&s) {
491                            tags.push(tag);
492                        }
493                    }
494                    _ => {
495                        // Try to deserialize as Tag directly (if it's a string in JSON)
496                        if let serde_json::Value::String(s) = item
497                            && let Ok(tag) = Tag::from_str(&s)
498                        {
499                            tags.push(tag);
500                        }
501                    }
502                }
503            }
504            Ok(tags)
505        }
506    }
507
508    deserializer.deserialize_seq(TagVisitor)
509}