1use indexmap::IndexMap;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
15#[serde(rename_all = "camelCase")]
16pub struct QualityRule {
17 #[serde(skip_serializing_if = "Option::is_none")]
20 pub id: Option<String>,
21 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
23 pub rule_type: Option<String>,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub name: Option<String>,
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub dimension: Option<String>,
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub description: Option<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub business_impact: Option<String>,
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub severity: Option<String>,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub method: Option<String>,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub unit: Option<String>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
49 pub metric: Option<String>,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub rule: Option<String>,
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub arguments: Option<serde_json::Value>,
56
57 #[serde(skip_serializing_if = "Option::is_none")]
60 pub must_be: Option<serde_json::Value>,
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub must_not_be: Option<serde_json::Value>,
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub must_be_greater_than: Option<serde_json::Value>,
67 #[serde(
69 rename = "mustBeGreaterOrEqualTo",
70 alias = "mustBeGreaterThanOrEqual",
71 alias = "mustBeGreaterThanOrEqualTo",
72 skip_serializing_if = "Option::is_none"
73 )]
74 pub must_be_greater_or_equal_to: Option<serde_json::Value>,
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub must_be_less_than: Option<serde_json::Value>,
78 #[serde(
80 rename = "mustBeLessOrEqualTo",
81 alias = "mustBeLessThanOrEqual",
82 alias = "mustBeLessThanOrEqualTo",
83 skip_serializing_if = "Option::is_none"
84 )]
85 pub must_be_less_or_equal_to: Option<serde_json::Value>,
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub must_be_between: Option<Vec<serde_json::Value>>,
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub must_not_be_between: Option<Vec<serde_json::Value>>,
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub must_be_in: Option<Vec<serde_json::Value>>,
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub must_not_be_in: Option<Vec<serde_json::Value>>,
98
99 #[serde(skip_serializing_if = "Option::is_none")]
102 pub query: Option<String>,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
107 pub engine: Option<String>,
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub implementation: Option<serde_json::Value>,
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub url: Option<String>,
114
115 #[serde(skip_serializing_if = "Option::is_none")]
118 pub scheduler: Option<String>,
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub schedule: Option<String>,
122
123 #[serde(default, skip_serializing_if = "Vec::is_empty")]
126 pub authoritative_definitions: Vec<AuthoritativeDefinition>,
127 #[serde(default, skip_serializing_if = "Vec::is_empty")]
129 pub tags: Vec<String>,
130 #[serde(default, skip_serializing_if = "Vec::is_empty")]
132 pub custom_properties: Vec<CustomProperty>,
133
134 #[serde(flatten)]
136 pub extra: IndexMap<String, serde_json::Value>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
144#[serde(rename_all = "camelCase")]
145pub struct CustomProperty {
146 pub property: String,
148 pub value: serde_json::Value,
150}
151
152impl CustomProperty {
153 pub fn new(property: impl Into<String>, value: serde_json::Value) -> Self {
155 Self {
156 property: property.into(),
157 value,
158 }
159 }
160
161 pub fn string(property: impl Into<String>, value: impl Into<String>) -> Self {
163 Self {
164 property: property.into(),
165 value: serde_json::Value::String(value.into()),
166 }
167 }
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
174#[serde(rename_all = "camelCase")]
175pub struct AuthoritativeDefinition {
176 #[serde(rename = "type")]
178 pub definition_type: String,
179 pub url: String,
181}
182
183impl AuthoritativeDefinition {
184 pub fn new(definition_type: impl Into<String>, url: impl Into<String>) -> Self {
186 Self {
187 definition_type: definition_type.into(),
188 url: url.into(),
189 }
190 }
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
197#[serde(rename_all = "camelCase")]
198pub struct SchemaRelationship {
199 #[serde(rename = "type")]
201 pub relationship_type: String,
202 #[serde(default, skip_serializing_if = "Vec::is_empty")]
204 pub from_properties: Vec<String>,
205 pub to_schema: String,
207 #[serde(default, skip_serializing_if = "Vec::is_empty")]
209 pub to_properties: Vec<String>,
210 #[serde(skip_serializing_if = "Option::is_none")]
212 pub description: Option<String>,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
219#[serde(rename_all = "camelCase")]
220pub struct PropertyRelationship {
221 #[serde(rename = "type")]
223 pub relationship_type: String,
224 pub to: String,
226}
227
228impl PropertyRelationship {
229 pub fn new(relationship_type: impl Into<String>, to: impl Into<String>) -> Self {
231 Self {
232 relationship_type: relationship_type.into(),
233 to: to.into(),
234 }
235 }
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
240#[serde(rename_all = "camelCase")]
241pub struct LogicalTypeOptions {
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub min_length: Option<i64>,
245 #[serde(skip_serializing_if = "Option::is_none")]
247 pub max_length: Option<i64>,
248 #[serde(skip_serializing_if = "Option::is_none")]
250 pub pattern: Option<String>,
251 #[serde(skip_serializing_if = "Option::is_none")]
253 pub format: Option<String>,
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub minimum: Option<serde_json::Value>,
257 #[serde(skip_serializing_if = "Option::is_none")]
259 pub maximum: Option<serde_json::Value>,
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub exclusive_minimum: Option<serde_json::Value>,
263 #[serde(skip_serializing_if = "Option::is_none")]
265 pub exclusive_maximum: Option<serde_json::Value>,
266 #[serde(skip_serializing_if = "Option::is_none")]
268 pub precision: Option<i32>,
269 #[serde(skip_serializing_if = "Option::is_none")]
271 pub scale: Option<i32>,
272}
273
274impl LogicalTypeOptions {
275 pub fn is_empty(&self) -> bool {
277 self.min_length.is_none()
278 && self.max_length.is_none()
279 && self.pattern.is_none()
280 && self.format.is_none()
281 && self.minimum.is_none()
282 && self.maximum.is_none()
283 && self.exclusive_minimum.is_none()
284 && self.exclusive_maximum.is_none()
285 && self.precision.is_none()
286 && self.scale.is_none()
287 }
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
292#[serde(rename_all = "camelCase")]
293pub struct Team {
294 #[serde(skip_serializing_if = "Option::is_none")]
296 pub name: Option<String>,
297 #[serde(default, skip_serializing_if = "Vec::is_empty")]
299 pub members: Vec<TeamMember>,
300 #[serde(flatten)]
302 pub extra: HashMap<String, serde_json::Value>,
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
307#[serde(rename_all = "camelCase")]
308pub struct TeamMember {
309 #[serde(skip_serializing_if = "Option::is_none")]
311 pub name: Option<String>,
312 #[serde(skip_serializing_if = "Option::is_none")]
314 pub email: Option<String>,
315 #[serde(skip_serializing_if = "Option::is_none")]
317 pub role: Option<String>,
318 #[serde(flatten)]
320 pub extra: HashMap<String, serde_json::Value>,
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
325#[serde(rename_all = "camelCase")]
326pub struct Support {
327 #[serde(skip_serializing_if = "Option::is_none")]
329 pub channel: Option<String>,
330 #[serde(skip_serializing_if = "Option::is_none")]
332 pub url: Option<String>,
333 #[serde(skip_serializing_if = "Option::is_none")]
335 pub email: Option<String>,
336 #[serde(flatten)]
338 pub extra: HashMap<String, serde_json::Value>,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
343#[serde(rename_all = "camelCase")]
344pub struct Server {
345 #[serde(skip_serializing_if = "Option::is_none")]
347 pub server: Option<String>,
348 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
350 pub server_type: Option<String>,
351 #[serde(skip_serializing_if = "Option::is_none")]
353 pub environment: Option<String>,
354 #[serde(skip_serializing_if = "Option::is_none")]
356 pub description: Option<String>,
357 #[serde(skip_serializing_if = "Option::is_none")]
359 pub database: Option<String>,
360 #[serde(skip_serializing_if = "Option::is_none")]
362 pub project: Option<String>,
363 #[serde(skip_serializing_if = "Option::is_none")]
365 pub schema: Option<String>,
366 #[serde(skip_serializing_if = "Option::is_none")]
368 pub catalog: Option<String>,
369 #[serde(skip_serializing_if = "Option::is_none")]
371 pub dataset: Option<String>,
372 #[serde(skip_serializing_if = "Option::is_none")]
374 pub account: Option<String>,
375 #[serde(skip_serializing_if = "Option::is_none")]
377 pub host: Option<String>,
378 #[serde(skip_serializing_if = "Option::is_none")]
380 pub location: Option<String>,
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub format: Option<String>,
384 #[serde(skip_serializing_if = "Option::is_none")]
386 pub delimiter: Option<String>,
387 #[serde(skip_serializing_if = "Option::is_none")]
389 pub topic: Option<String>,
390 #[serde(flatten)]
392 pub extra: HashMap<String, serde_json::Value>,
393}
394
395#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
397#[serde(rename_all = "camelCase")]
398pub struct Role {
399 #[serde(skip_serializing_if = "Option::is_none")]
401 pub role: Option<String>,
402 #[serde(skip_serializing_if = "Option::is_none")]
404 pub description: Option<String>,
405 #[serde(skip_serializing_if = "Option::is_none")]
407 pub principal: Option<String>,
408 #[serde(skip_serializing_if = "Option::is_none")]
410 pub access: Option<String>,
411 #[serde(flatten)]
413 pub extra: HashMap<String, serde_json::Value>,
414}
415
416#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
418#[serde(rename_all = "camelCase")]
419pub struct ServiceLevel {
420 #[serde(skip_serializing_if = "Option::is_none")]
422 pub property: Option<String>,
423 #[serde(skip_serializing_if = "Option::is_none")]
425 pub value: Option<serde_json::Value>,
426 #[serde(skip_serializing_if = "Option::is_none")]
428 pub unit: Option<String>,
429 #[serde(skip_serializing_if = "Option::is_none")]
431 pub element: Option<String>,
432 #[serde(skip_serializing_if = "Option::is_none")]
434 pub driver: Option<String>,
435 #[serde(skip_serializing_if = "Option::is_none")]
437 pub description: Option<String>,
438 #[serde(skip_serializing_if = "Option::is_none")]
440 pub scheduler: Option<String>,
441 #[serde(skip_serializing_if = "Option::is_none")]
443 pub schedule: Option<String>,
444 #[serde(flatten)]
446 pub extra: HashMap<String, serde_json::Value>,
447}
448
449#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
451#[serde(rename_all = "camelCase")]
452pub struct Price {
453 #[serde(skip_serializing_if = "Option::is_none")]
455 pub amount: Option<serde_json::Value>,
456 #[serde(skip_serializing_if = "Option::is_none")]
458 pub currency: Option<String>,
459 #[serde(skip_serializing_if = "Option::is_none")]
461 pub billing_frequency: Option<String>,
462 #[serde(skip_serializing_if = "Option::is_none")]
464 pub price_model: Option<String>,
465 #[serde(flatten)]
467 pub extra: HashMap<String, serde_json::Value>,
468}
469
470#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
472#[serde(rename_all = "camelCase")]
473pub struct Terms {
474 #[serde(skip_serializing_if = "Option::is_none")]
476 pub description: Option<String>,
477 #[serde(skip_serializing_if = "Option::is_none")]
479 pub limitations: Option<String>,
480 #[serde(skip_serializing_if = "Option::is_none")]
482 pub url: Option<String>,
483 #[serde(flatten)]
485 pub extra: HashMap<String, serde_json::Value>,
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
490#[serde(rename_all = "camelCase")]
491pub struct Link {
492 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
494 pub link_type: Option<String>,
495 #[serde(skip_serializing_if = "Option::is_none")]
497 pub url: Option<String>,
498 #[serde(skip_serializing_if = "Option::is_none")]
500 pub description: Option<String>,
501 #[serde(flatten)]
503 pub extra: HashMap<String, serde_json::Value>,
504}
505
506#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
508#[serde(untagged)]
509pub enum Description {
510 Simple(String),
512 Structured(StructuredDescription),
514}
515
516impl Default for Description {
517 fn default() -> Self {
518 Description::Simple(String::new())
519 }
520}
521
522impl Description {
523 pub fn as_string(&self) -> String {
525 match self {
526 Description::Simple(s) => s.clone(),
527 Description::Structured(d) => d.purpose.clone().unwrap_or_default(),
528 }
529 }
530}
531
532#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
534#[serde(rename_all = "camelCase")]
535pub struct StructuredDescription {
536 #[serde(skip_serializing_if = "Option::is_none")]
538 pub purpose: Option<String>,
539 #[serde(skip_serializing_if = "Option::is_none")]
541 pub limitations: Option<String>,
542 #[serde(skip_serializing_if = "Option::is_none")]
544 pub usage: Option<String>,
545 #[serde(flatten)]
547 pub extra: HashMap<String, serde_json::Value>,
548}
549
550#[cfg(test)]
551mod tests {
552 use super::*;
553
554 #[test]
555 fn test_quality_rule_serialization() {
556 let rule = QualityRule {
557 dimension: Some("accuracy".to_string()),
558 must_be: Some(serde_json::json!(true)),
559 ..Default::default()
560 };
561 let json = serde_json::to_string(&rule).unwrap();
562 assert!(json.contains("dimension"));
563 assert!(json.contains("accuracy"));
564 }
565
566 #[test]
567 fn test_custom_property() {
568 let prop = CustomProperty::string("source_format", "avro");
569 assert_eq!(prop.property, "source_format");
570 assert_eq!(prop.value, serde_json::json!("avro"));
571 }
572
573 #[test]
574 fn test_description_variants() {
575 let simple: Description = serde_json::from_str(r#""A simple description""#).unwrap();
576 assert_eq!(simple.as_string(), "A simple description");
577
578 let structured: Description =
579 serde_json::from_str(r#"{"purpose": "Data analysis", "usage": "Read-only"}"#).unwrap();
580 assert_eq!(structured.as_string(), "Data analysis");
581 }
582
583 #[test]
584 fn test_logical_type_options_is_empty() {
585 let empty = LogicalTypeOptions::default();
586 assert!(empty.is_empty());
587
588 let with_length = LogicalTypeOptions {
589 max_length: Some(100),
590 ..Default::default()
591 };
592 assert!(!with_length.is_empty());
593 }
594}