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