1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
13#[serde(rename_all = "camelCase")]
14pub struct QualityRule {
15 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
17 pub rule_type: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none")]
20 pub dimension: Option<String>,
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub business_impact: Option<String>,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub metric: Option<String>,
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub description: Option<String>,
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub must_be: Option<serde_json::Value>,
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub must_not_be: Option<serde_json::Value>,
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub must_be_greater_than: Option<serde_json::Value>,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub must_be_less_than: Option<serde_json::Value>,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub must_be_greater_than_or_equal: Option<serde_json::Value>,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub must_be_less_than_or_equal: Option<serde_json::Value>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub must_be_in: Option<Vec<serde_json::Value>>,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub must_not_be_in: Option<Vec<serde_json::Value>>,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub query: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub scheduler: Option<String>,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub schedule: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub engine: Option<String>,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub url: Option<String>,
69 #[serde(flatten)]
71 pub extra: HashMap<String, serde_json::Value>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
79#[serde(rename_all = "camelCase")]
80pub struct CustomProperty {
81 pub property: String,
83 pub value: serde_json::Value,
85}
86
87impl CustomProperty {
88 pub fn new(property: impl Into<String>, value: serde_json::Value) -> Self {
90 Self {
91 property: property.into(),
92 value,
93 }
94 }
95
96 pub fn string(property: impl Into<String>, value: impl Into<String>) -> Self {
98 Self {
99 property: property.into(),
100 value: serde_json::Value::String(value.into()),
101 }
102 }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
109#[serde(rename_all = "camelCase")]
110pub struct AuthoritativeDefinition {
111 #[serde(rename = "type")]
113 pub definition_type: String,
114 pub url: String,
116}
117
118impl AuthoritativeDefinition {
119 pub fn new(definition_type: impl Into<String>, url: impl Into<String>) -> Self {
121 Self {
122 definition_type: definition_type.into(),
123 url: url.into(),
124 }
125 }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
132#[serde(rename_all = "camelCase")]
133pub struct SchemaRelationship {
134 #[serde(rename = "type")]
136 pub relationship_type: String,
137 #[serde(default, skip_serializing_if = "Vec::is_empty")]
139 pub from_properties: Vec<String>,
140 pub to_schema: String,
142 #[serde(default, skip_serializing_if = "Vec::is_empty")]
144 pub to_properties: Vec<String>,
145 #[serde(skip_serializing_if = "Option::is_none")]
147 pub description: Option<String>,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
154#[serde(rename_all = "camelCase")]
155pub struct PropertyRelationship {
156 #[serde(rename = "type")]
158 pub relationship_type: String,
159 pub to: String,
161}
162
163impl PropertyRelationship {
164 pub fn new(relationship_type: impl Into<String>, to: impl Into<String>) -> Self {
166 Self {
167 relationship_type: relationship_type.into(),
168 to: to.into(),
169 }
170 }
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
175#[serde(rename_all = "camelCase")]
176pub struct LogicalTypeOptions {
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub min_length: Option<i64>,
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub max_length: Option<i64>,
183 #[serde(skip_serializing_if = "Option::is_none")]
185 pub pattern: Option<String>,
186 #[serde(skip_serializing_if = "Option::is_none")]
188 pub format: Option<String>,
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub minimum: Option<serde_json::Value>,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub maximum: Option<serde_json::Value>,
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub exclusive_minimum: Option<serde_json::Value>,
198 #[serde(skip_serializing_if = "Option::is_none")]
200 pub exclusive_maximum: Option<serde_json::Value>,
201 #[serde(skip_serializing_if = "Option::is_none")]
203 pub precision: Option<i32>,
204 #[serde(skip_serializing_if = "Option::is_none")]
206 pub scale: Option<i32>,
207}
208
209impl LogicalTypeOptions {
210 pub fn is_empty(&self) -> bool {
212 self.min_length.is_none()
213 && self.max_length.is_none()
214 && self.pattern.is_none()
215 && self.format.is_none()
216 && self.minimum.is_none()
217 && self.maximum.is_none()
218 && self.exclusive_minimum.is_none()
219 && self.exclusive_maximum.is_none()
220 && self.precision.is_none()
221 && self.scale.is_none()
222 }
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
227#[serde(rename_all = "camelCase")]
228pub struct Team {
229 #[serde(skip_serializing_if = "Option::is_none")]
231 pub name: Option<String>,
232 #[serde(default, skip_serializing_if = "Vec::is_empty")]
234 pub members: Vec<TeamMember>,
235 #[serde(flatten)]
237 pub extra: HashMap<String, serde_json::Value>,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
242#[serde(rename_all = "camelCase")]
243pub struct TeamMember {
244 #[serde(skip_serializing_if = "Option::is_none")]
246 pub name: Option<String>,
247 #[serde(skip_serializing_if = "Option::is_none")]
249 pub email: Option<String>,
250 #[serde(skip_serializing_if = "Option::is_none")]
252 pub role: Option<String>,
253 #[serde(flatten)]
255 pub extra: HashMap<String, serde_json::Value>,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
260#[serde(rename_all = "camelCase")]
261pub struct Support {
262 #[serde(skip_serializing_if = "Option::is_none")]
264 pub channel: Option<String>,
265 #[serde(skip_serializing_if = "Option::is_none")]
267 pub url: Option<String>,
268 #[serde(skip_serializing_if = "Option::is_none")]
270 pub email: Option<String>,
271 #[serde(flatten)]
273 pub extra: HashMap<String, serde_json::Value>,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
278#[serde(rename_all = "camelCase")]
279pub struct Server {
280 #[serde(skip_serializing_if = "Option::is_none")]
282 pub server: Option<String>,
283 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
285 pub server_type: Option<String>,
286 #[serde(skip_serializing_if = "Option::is_none")]
288 pub environment: Option<String>,
289 #[serde(skip_serializing_if = "Option::is_none")]
291 pub description: Option<String>,
292 #[serde(skip_serializing_if = "Option::is_none")]
294 pub database: Option<String>,
295 #[serde(skip_serializing_if = "Option::is_none")]
297 pub project: Option<String>,
298 #[serde(skip_serializing_if = "Option::is_none")]
300 pub schema: Option<String>,
301 #[serde(skip_serializing_if = "Option::is_none")]
303 pub catalog: Option<String>,
304 #[serde(skip_serializing_if = "Option::is_none")]
306 pub dataset: Option<String>,
307 #[serde(skip_serializing_if = "Option::is_none")]
309 pub account: Option<String>,
310 #[serde(skip_serializing_if = "Option::is_none")]
312 pub host: Option<String>,
313 #[serde(skip_serializing_if = "Option::is_none")]
315 pub location: Option<String>,
316 #[serde(skip_serializing_if = "Option::is_none")]
318 pub format: Option<String>,
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub delimiter: Option<String>,
322 #[serde(skip_serializing_if = "Option::is_none")]
324 pub topic: Option<String>,
325 #[serde(flatten)]
327 pub extra: HashMap<String, serde_json::Value>,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
332#[serde(rename_all = "camelCase")]
333pub struct Role {
334 #[serde(skip_serializing_if = "Option::is_none")]
336 pub role: Option<String>,
337 #[serde(skip_serializing_if = "Option::is_none")]
339 pub description: Option<String>,
340 #[serde(skip_serializing_if = "Option::is_none")]
342 pub principal: Option<String>,
343 #[serde(skip_serializing_if = "Option::is_none")]
345 pub access: Option<String>,
346 #[serde(flatten)]
348 pub extra: HashMap<String, serde_json::Value>,
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
353#[serde(rename_all = "camelCase")]
354pub struct ServiceLevel {
355 #[serde(skip_serializing_if = "Option::is_none")]
357 pub property: Option<String>,
358 #[serde(skip_serializing_if = "Option::is_none")]
360 pub value: Option<serde_json::Value>,
361 #[serde(skip_serializing_if = "Option::is_none")]
363 pub unit: Option<String>,
364 #[serde(skip_serializing_if = "Option::is_none")]
366 pub element: Option<String>,
367 #[serde(skip_serializing_if = "Option::is_none")]
369 pub driver: Option<String>,
370 #[serde(skip_serializing_if = "Option::is_none")]
372 pub description: Option<String>,
373 #[serde(skip_serializing_if = "Option::is_none")]
375 pub scheduler: Option<String>,
376 #[serde(skip_serializing_if = "Option::is_none")]
378 pub schedule: Option<String>,
379 #[serde(flatten)]
381 pub extra: HashMap<String, serde_json::Value>,
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
386#[serde(rename_all = "camelCase")]
387pub struct Price {
388 #[serde(skip_serializing_if = "Option::is_none")]
390 pub amount: Option<serde_json::Value>,
391 #[serde(skip_serializing_if = "Option::is_none")]
393 pub currency: Option<String>,
394 #[serde(skip_serializing_if = "Option::is_none")]
396 pub billing_frequency: Option<String>,
397 #[serde(skip_serializing_if = "Option::is_none")]
399 pub price_model: Option<String>,
400 #[serde(flatten)]
402 pub extra: HashMap<String, serde_json::Value>,
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
407#[serde(rename_all = "camelCase")]
408pub struct Terms {
409 #[serde(skip_serializing_if = "Option::is_none")]
411 pub description: Option<String>,
412 #[serde(skip_serializing_if = "Option::is_none")]
414 pub limitations: Option<String>,
415 #[serde(skip_serializing_if = "Option::is_none")]
417 pub url: Option<String>,
418 #[serde(flatten)]
420 pub extra: HashMap<String, serde_json::Value>,
421}
422
423#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
425#[serde(rename_all = "camelCase")]
426pub struct Link {
427 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
429 pub link_type: Option<String>,
430 #[serde(skip_serializing_if = "Option::is_none")]
432 pub url: Option<String>,
433 #[serde(skip_serializing_if = "Option::is_none")]
435 pub description: Option<String>,
436 #[serde(flatten)]
438 pub extra: HashMap<String, serde_json::Value>,
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
443#[serde(untagged)]
444pub enum Description {
445 Simple(String),
447 Structured(StructuredDescription),
449}
450
451impl Default for Description {
452 fn default() -> Self {
453 Description::Simple(String::new())
454 }
455}
456
457impl Description {
458 pub fn as_string(&self) -> String {
460 match self {
461 Description::Simple(s) => s.clone(),
462 Description::Structured(d) => d.purpose.clone().unwrap_or_default(),
463 }
464 }
465}
466
467#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
469#[serde(rename_all = "camelCase")]
470pub struct StructuredDescription {
471 #[serde(skip_serializing_if = "Option::is_none")]
473 pub purpose: Option<String>,
474 #[serde(skip_serializing_if = "Option::is_none")]
476 pub limitations: Option<String>,
477 #[serde(skip_serializing_if = "Option::is_none")]
479 pub usage: Option<String>,
480 #[serde(flatten)]
482 pub extra: HashMap<String, serde_json::Value>,
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488
489 #[test]
490 fn test_quality_rule_serialization() {
491 let rule = QualityRule {
492 dimension: Some("accuracy".to_string()),
493 must_be: Some(serde_json::json!(true)),
494 ..Default::default()
495 };
496 let json = serde_json::to_string(&rule).unwrap();
497 assert!(json.contains("dimension"));
498 assert!(json.contains("accuracy"));
499 }
500
501 #[test]
502 fn test_custom_property() {
503 let prop = CustomProperty::string("source_format", "avro");
504 assert_eq!(prop.property, "source_format");
505 assert_eq!(prop.value, serde_json::json!("avro"));
506 }
507
508 #[test]
509 fn test_description_variants() {
510 let simple: Description = serde_json::from_str(r#""A simple description""#).unwrap();
511 assert_eq!(simple.as_string(), "A simple description");
512
513 let structured: Description =
514 serde_json::from_str(r#"{"purpose": "Data analysis", "usage": "Read-only"}"#).unwrap();
515 assert_eq!(structured.as_string(), "Data analysis");
516 }
517
518 #[test]
519 fn test_logical_type_options_is_empty() {
520 let empty = LogicalTypeOptions::default();
521 assert!(empty.is_empty());
522
523 let with_length = LogicalTypeOptions {
524 max_length: Some(100),
525 ..Default::default()
526 };
527 assert!(!with_length.is_empty());
528 }
529}