Skip to main content

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