data_modelling_sdk/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")]
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")]
140    pub unit_cost: Option<f64>,
141    /// Billing unit
142    #[serde(skip_serializing_if = "Option::is_none")]
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")]
239    pub impact_areas: Option<Vec<CADSImpactArea>>,
240    /// Intended use
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub intended_use: Option<String>,
243    /// Out of scope use
244    #[serde(skip_serializing_if = "Option::is_none")]
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")]
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")]
319    pub applies_to: Option<CADSValidationProfileAppliesTo>,
320    /// Required checks
321    pub required_checks: Vec<String>,
322}
323
324/// BPMN model format
325#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
326#[serde(rename_all = "kebab-case")]
327pub enum CADSBPMNFormat {
328    #[serde(rename = "bpmn20-xml")]
329    Bpmn20Xml,
330    #[serde(rename = "json")]
331    Json,
332}
333
334/// BPMN model reference
335#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
336pub struct CADSBPMNModel {
337    /// Model name
338    pub name: String,
339    /// Reference to BPMN model
340    pub reference: String,
341    /// Format
342    pub format: CADSBPMNFormat,
343    /// Description
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub description: Option<String>,
346}
347
348/// DMN model format
349#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
350#[serde(rename_all = "kebab-case")]
351pub enum CADSDMNFormat {
352    #[serde(rename = "dmn13-xml")]
353    Dmn13Xml,
354    #[serde(rename = "json")]
355    Json,
356}
357
358/// DMN model reference
359#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
360pub struct CADSDMNModel {
361    /// Model name
362    pub name: String,
363    /// Reference to DMN model
364    pub reference: String,
365    /// Format
366    pub format: CADSDMNFormat,
367    /// Description
368    #[serde(skip_serializing_if = "Option::is_none")]
369    pub description: Option<String>,
370}
371
372/// OpenAPI spec format
373#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
374#[serde(rename_all = "kebab-case")]
375pub enum CADSOpenAPIFormat {
376    #[serde(rename = "openapi-3.0")]
377    Openapi30,
378    #[serde(rename = "openapi-3.1")]
379    Openapi31,
380    #[serde(rename = "swagger-2.0")]
381    Swagger20,
382}
383
384/// OpenAPI spec reference
385#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
386pub struct CADSOpenAPISpec {
387    /// Spec name
388    pub name: String,
389    /// Reference to OpenAPI spec
390    pub reference: String,
391    /// Format
392    pub format: CADSOpenAPIFormat,
393    /// Description
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub description: Option<String>,
396}
397
398/// CADS Asset - main structure
399#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
400#[serde(rename_all = "camelCase")]
401pub struct CADSAsset {
402    /// API version
403    pub api_version: String,
404    /// Asset kind
405    pub kind: CADSKind,
406    /// Unique identifier (UUID or URN)
407    pub id: String,
408    /// Asset name
409    pub name: String,
410    /// Version
411    pub version: String,
412    /// Status
413    pub status: CADSStatus,
414    /// Domain
415    #[serde(skip_serializing_if = "Option::is_none")]
416    pub domain: Option<String>,
417    /// Tags
418    #[serde(default, deserialize_with = "deserialize_tags")]
419    pub tags: Vec<Tag>,
420    /// Description
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub description: Option<CADSDescription>,
423    /// Runtime configuration
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub runtime: Option<CADSRuntime>,
426    /// SLA
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub sla: Option<CADSSLA>,
429    /// Pricing
430    #[serde(skip_serializing_if = "Option::is_none")]
431    pub pricing: Option<CADSPricing>,
432    /// Team
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub team: Option<Vec<CADSTeamMember>>,
435    /// Risk management
436    #[serde(skip_serializing_if = "Option::is_none")]
437    pub risk: Option<CADSRisk>,
438    /// Compliance
439    #[serde(skip_serializing_if = "Option::is_none")]
440    pub compliance: Option<CADSCompliance>,
441    /// Validation profiles
442    #[serde(skip_serializing_if = "Option::is_none")]
443    pub validation_profiles: Option<Vec<CADSValidationProfile>>,
444    /// BPMN models
445    #[serde(skip_serializing_if = "Option::is_none")]
446    pub bpmn_models: Option<Vec<CADSBPMNModel>>,
447    /// DMN models
448    #[serde(skip_serializing_if = "Option::is_none")]
449    pub dmn_models: Option<Vec<CADSDMNModel>>,
450    /// OpenAPI specifications
451    #[serde(skip_serializing_if = "Option::is_none")]
452    pub openapi_specs: Option<Vec<CADSOpenAPISpec>>,
453    /// Custom properties
454    #[serde(skip_serializing_if = "Option::is_none")]
455    pub custom_properties: Option<HashMap<String, serde_json::Value>>,
456    /// Creation timestamp
457    #[serde(skip_serializing_if = "Option::is_none")]
458    pub created_at: Option<DateTime<Utc>>,
459    /// Last update timestamp
460    #[serde(skip_serializing_if = "Option::is_none")]
461    pub updated_at: Option<DateTime<Utc>>,
462}
463
464/// Deserialize tags with backward compatibility (supports Vec<String> and Vec<Tag>)
465fn deserialize_tags<'de, D>(deserializer: D) -> Result<Vec<Tag>, D::Error>
466where
467    D: serde::Deserializer<'de>,
468{
469    // Accept either Vec<String> (backward compatibility) or Vec<Tag>
470    struct TagVisitor;
471
472    impl<'de> serde::de::Visitor<'de> for TagVisitor {
473        type Value = Vec<Tag>;
474
475        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
476            formatter.write_str("a vector of tags (strings or Tag objects)")
477        }
478
479        fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
480        where
481            A: serde::de::SeqAccess<'de>,
482        {
483            let mut tags = Vec::new();
484            while let Some(item) = seq.next_element::<serde_json::Value>()? {
485                match item {
486                    serde_json::Value::String(s) => {
487                        // Backward compatibility: parse string as Tag
488                        if let Ok(tag) = Tag::from_str(&s) {
489                            tags.push(tag);
490                        }
491                    }
492                    _ => {
493                        // Try to deserialize as Tag directly (if it's a string in JSON)
494                        if let serde_json::Value::String(s) = item
495                            && let Ok(tag) = Tag::from_str(&s)
496                        {
497                            tags.push(tag);
498                        }
499                    }
500                }
501            }
502            Ok(tags)
503        }
504    }
505
506    deserializer.deserialize_seq(TagVisitor)
507}