pyodcs 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. {pyodcs-0.2.0 → pyodcs-0.3.0}/CHANGELOG.md +18 -0
  2. {pyodcs-0.2.0 → pyodcs-0.3.0}/Cargo.lock +1 -1
  3. {pyodcs-0.2.0 → pyodcs-0.3.0}/Cargo.toml +1 -1
  4. {pyodcs-0.2.0 → pyodcs-0.3.0}/PKG-INFO +3 -3
  5. {pyodcs-0.2.0 → pyodcs-0.3.0}/README.md +2 -2
  6. {pyodcs-0.2.0 → pyodcs-0.3.0}/ROADMAP.md +15 -16
  7. pyodcs-0.3.0/docs/implementation/model-guide.md +35 -0
  8. {pyodcs-0.2.0/tests/fixtures → pyodcs-0.3.0/examples}/minimal.odcs.json +10 -8
  9. {pyodcs-0.2.0 → pyodcs-0.3.0}/examples/minimal.odcs.yaml +7 -6
  10. {pyodcs-0.2.0 → pyodcs-0.3.0}/pyproject.toml +1 -1
  11. {pyodcs-0.2.0 → pyodcs-0.3.0}/python/tests/test_pyodcs.py +5 -1
  12. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/cli/mod.rs +3 -1
  13. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/mod.rs +5 -3
  14. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/lib.rs +2 -1
  15. pyodcs-0.3.0/src/model/contract.rs +111 -0
  16. pyodcs-0.3.0/src/model/custom.rs +3 -0
  17. pyodcs-0.3.0/src/model/fundamentals.rs +3 -0
  18. pyodcs-0.3.0/src/model/mod.rs +36 -0
  19. pyodcs-0.3.0/src/model/pricing.rs +19 -0
  20. pyodcs-0.3.0/src/model/quality.rs +76 -0
  21. pyodcs-0.3.0/src/model/relationships.rs +44 -0
  22. pyodcs-0.3.0/src/model/roles.rs +24 -0
  23. pyodcs-0.3.0/src/model/schema.rs +98 -0
  24. pyodcs-0.3.0/src/model/servers.rs +31 -0
  25. pyodcs-0.3.0/src/model/shared.rs +85 -0
  26. pyodcs-0.3.0/src/model/sla.rs +37 -0
  27. pyodcs-0.3.0/src/model/stakeholders.rs +4 -0
  28. pyodcs-0.3.0/src/model/support.rs +29 -0
  29. pyodcs-0.3.0/src/model/team.rs +73 -0
  30. pyodcs-0.3.0/src/model/versioning.rs +12 -0
  31. pyodcs-0.3.0/src/parser/json.rs +21 -0
  32. pyodcs-0.3.0/src/parser/mod.rs +159 -0
  33. pyodcs-0.3.0/src/parser/yaml.rs +15 -0
  34. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/mod.rs +32 -3
  35. pyodcs-0.3.0/tests/fixtures/invalid-empty-name.yaml +6 -0
  36. {pyodcs-0.2.0 → pyodcs-0.3.0}/tests/fixtures/invalid-kind.yaml +2 -0
  37. {pyodcs-0.2.0/examples → pyodcs-0.3.0/tests/fixtures}/minimal.odcs.json +10 -8
  38. {pyodcs-0.2.0 → pyodcs-0.3.0}/tests/fixtures/minimal.odcs.yaml +7 -6
  39. pyodcs-0.3.0/tests/fixtures/odcs-json-schema-v3.1.0.json +2929 -0
  40. pyodcs-0.3.0/tests/fixtures/unknown-field.yaml +7 -0
  41. {pyodcs-0.2.0 → pyodcs-0.3.0}/tests/fixtures/unsupported-version.yaml +2 -0
  42. pyodcs-0.3.0/tests/fixtures/with-custom-properties.yaml +29 -0
  43. pyodcs-0.3.0/tests/fixtures/with-extensions.yaml +22 -0
  44. pyodcs-0.3.0/tests/fixtures/with-pricing.yaml +18 -0
  45. pyodcs-0.3.0/tests/fixtures/with-roles.yaml +19 -0
  46. pyodcs-0.3.0/tests/fixtures/with-schema-properties.yaml +30 -0
  47. pyodcs-0.3.0/tests/fixtures/with-schema-quality.yaml +29 -0
  48. pyodcs-0.3.0/tests/fixtures/with-servers.yaml +21 -0
  49. pyodcs-0.3.0/tests/fixtures/with-sla.yaml +21 -0
  50. pyodcs-0.3.0/tests/fixtures/with-support.yaml +20 -0
  51. pyodcs-0.3.0/tests/fixtures/with-team-legacy-array.yaml +18 -0
  52. pyodcs-0.3.0/tests/fixtures/with-team.yaml +22 -0
  53. pyodcs-0.3.0/tests/skeleton.rs +228 -0
  54. pyodcs-0.2.0/docs/implementation/model-guide.md +0 -36
  55. pyodcs-0.2.0/src/model/contract.rs +0 -41
  56. pyodcs-0.2.0/src/model/custom.rs +0 -3
  57. pyodcs-0.2.0/src/model/field.rs +0 -20
  58. pyodcs-0.2.0/src/model/fundamentals.rs +0 -3
  59. pyodcs-0.2.0/src/model/mod.rs +0 -21
  60. pyodcs-0.2.0/src/model/pricing.rs +0 -3
  61. pyodcs-0.2.0/src/model/quality.rs +0 -20
  62. pyodcs-0.2.0/src/model/roles.rs +0 -3
  63. pyodcs-0.2.0/src/model/schema.rs +0 -19
  64. pyodcs-0.2.0/src/model/servers.rs +0 -3
  65. pyodcs-0.2.0/src/model/sla.rs +0 -3
  66. pyodcs-0.2.0/src/model/stakeholders.rs +0 -3
  67. pyodcs-0.2.0/src/model/support.rs +0 -3
  68. pyodcs-0.2.0/src/model/team.rs +0 -3
  69. pyodcs-0.2.0/src/model/versioning.rs +0 -3
  70. pyodcs-0.2.0/src/parser/json.rs +0 -35
  71. pyodcs-0.2.0/src/parser/mod.rs +0 -86
  72. pyodcs-0.2.0/src/parser/yaml.rs +0 -35
  73. pyodcs-0.2.0/tests/fixtures/invalid-empty-name.yaml +0 -4
  74. pyodcs-0.2.0/tests/fixtures/with-extensions.yaml +0 -7
  75. pyodcs-0.2.0/tests/skeleton.rs +0 -141
  76. {pyodcs-0.2.0 → pyodcs-0.3.0}/.editorconfig +0 -0
  77. {pyodcs-0.2.0 → pyodcs-0.3.0}/.github/workflows/checks.yml +0 -0
  78. {pyodcs-0.2.0 → pyodcs-0.3.0}/.github/workflows/ci.yml +0 -0
  79. {pyodcs-0.2.0 → pyodcs-0.3.0}/.github/workflows/release.yml +0 -0
  80. {pyodcs-0.2.0 → pyodcs-0.3.0}/.gitignore +0 -0
  81. {pyodcs-0.2.0 → pyodcs-0.3.0}/CONTRIBUTING.md +0 -0
  82. {pyodcs-0.2.0 → pyodcs-0.3.0}/LICENSE +0 -0
  83. {pyodcs-0.2.0 → pyodcs-0.3.0}/SPEC.md +0 -0
  84. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/README.md +0 -0
  85. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/README.md +0 -0
  86. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/architecture.md +0 -0
  87. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/cli-spec.md +0 -0
  88. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/crate-layout.md +0 -0
  89. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/diagnostics-guide.md +0 -0
  90. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/implementation-phases.md +0 -0
  91. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/non-goals.md +0 -0
  92. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/project-goal.md +0 -0
  93. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/public-api.md +0 -0
  94. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/relationship-to-dtcs.md +0 -0
  95. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/rust-dependencies.md +0 -0
  96. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/spec-usage.md +0 -0
  97. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/testing-plan.md +0 -0
  98. {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/validation-guide.md +0 -0
  99. {pyodcs-0.2.0 → pyodcs-0.3.0}/python/pyodcs/__init__.py +0 -0
  100. {pyodcs-0.2.0 → pyodcs-0.3.0}/python/pyodcs/__main__.py +0 -0
  101. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/bin/odcs.rs +0 -0
  102. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/compatibility/mod.rs +0 -0
  103. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/builders.rs +0 -0
  104. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/category.rs +0 -0
  105. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/codes.rs +0 -0
  106. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/diagnostic.rs +0 -0
  107. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/report.rs +0 -0
  108. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/severity.rs +0 -0
  109. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/stage.rs +0 -0
  110. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/python.rs +0 -0
  111. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/registry/mod.rs +0 -0
  112. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/document.rs +0 -0
  113. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/extensions.rs +0 -0
  114. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/phases.rs +0 -0
  115. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/quality.rs +0 -0
  116. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/references.rs +0 -0
  117. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/schema.rs +0 -0
  118. {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/structural.rs +0 -0
  119. {pyodcs-0.2.0 → pyodcs-0.3.0}/tests/cli.rs +0 -0
  120. {pyodcs-0.2.0 → pyodcs-0.3.0}/tests/fixtures/malformed.json +0 -0
  121. {pyodcs-0.2.0 → pyodcs-0.3.0}/tests/fixtures/malformed.yaml +0 -0
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ Phase 2 and Phase 3 milestone — full v3.1.0 Canonical Object Model and hardened parsing.
6
+
7
+ **Breaking changes:**
8
+
9
+ - Root-level `quality` removed; quality checks belong under `schema[]`
10
+ - Root `extensions` flatten removed; use `customProperties` arrays
11
+ - Required root fields: `version`, `apiVersion`, `kind`, `id`, `status`
12
+ - Unknown root fields are rejected at parse time (`deny_unknown_fields`)
13
+
14
+ **Added:**
15
+
16
+ - Typed COM for schema, quality, SLA, servers, team, roles, pricing, support
17
+ - Parse diagnostics with object references and `odcs:unknown-field`
18
+ - Section fixtures and YAML/JSON round-trip tests
19
+ - Upstream `odcs-json-schema-v3.1.0.json` pinned in `tests/fixtures/`
20
+
3
21
  ## 0.2.0
4
22
 
5
23
  First published release.
@@ -305,7 +305,7 @@ dependencies = [
305
305
 
306
306
  [[package]]
307
307
  name = "odcs"
308
- version = "0.2.0"
308
+ version = "0.3.0"
309
309
  dependencies = [
310
310
  "clap",
311
311
  "indexmap",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "odcs"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  edition = "2021"
5
5
  rust-version = "1.75"
6
6
  description = "Reference implementation of the Open Data Contract Standard (ODCS)"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyodcs
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Classifier: Development Status :: 2 - Pre-Alpha
5
5
  Classifier: License :: OSI Approved :: Apache Software License
6
6
  Classifier: Programming Language :: Python :: 3
@@ -26,9 +26,9 @@ Project-URL: Repository, https://github.com/odcs/odcs
26
26
 
27
27
  Reference Rust implementation for the [Open Data Contract Standard](https://github.com/bitol-io/open-data-contract-standard).
28
28
 
29
- **Status:** Pre-release skeleton
29
+ **Status:** Pre-release
30
30
  **Upstream ODCS version:** 3.1.0
31
- **Reference implementation:** 0.2.0 (Phase 1Skeleton)
31
+ **Reference implementation:** 0.3.0 (Phase 2 & 3 Canonical Object Model and Parsing)
32
32
 
33
33
  See [ROADMAP.md](ROADMAP.md) for milestone status.
34
34
 
@@ -2,9 +2,9 @@
2
2
 
3
3
  Reference Rust implementation for the [Open Data Contract Standard](https://github.com/bitol-io/open-data-contract-standard).
4
4
 
5
- **Status:** Pre-release skeleton
5
+ **Status:** Pre-release
6
6
  **Upstream ODCS version:** 3.1.0
7
- **Reference implementation:** 0.2.0 (Phase 1Skeleton)
7
+ **Reference implementation:** 0.3.0 (Phase 2 & 3 Canonical Object Model and Parsing)
8
8
 
9
9
  See [ROADMAP.md](ROADMAP.md) for milestone status.
10
10
 
@@ -11,8 +11,8 @@ The [upstream ODCS specification](https://github.com/bitol-io/open-data-contract
11
11
  | Phase | Name | Focus | Status |
12
12
  |-------|------|-------|--------|
13
13
  | **1** | [Skeleton](#phase-1--skeleton) | Crate layout, CLI entry point, examples, tests | **Complete** (`0.1.0`) |
14
- | **2** | [Canonical Object Model](#phase-2--canonical-object-model) | ODCS sections as Rust types | Planned |
15
- | **3** | [Parsing](#phase-3--parsing) | YAML and JSON parsing with extension preservation | Planned |
14
+ | **2** | [Canonical Object Model](#phase-2--canonical-object-model) | ODCS sections as Rust types | **Complete** (`0.3.0`) |
15
+ | **3** | [Parsing](#phase-3--parsing) | YAML and JSON parsing with diagnostics | **Complete** (`0.3.0`) |
16
16
  | **4** | [Diagnostics](#phase-4--diagnostics) | Structured diagnostics aligned with DTCS style | Planned |
17
17
  | **5** | [Validation](#phase-5--validation) | Phase-based validation pipeline | Planned |
18
18
  | **6** | [CLI](#phase-6--cli) | `validate`, `inspect`, `diagnostics`, `schema`, `version` | Planned |
@@ -56,24 +56,23 @@ Phase 1 Skeleton
56
56
 
57
57
  ## Phase 2 — Canonical Object Model
58
58
 
59
- Model ODCS sections:
59
+ **Target:** `0.3.0` — **Complete**
60
60
 
61
- - fundamentals
62
- - schema
63
- - quality
64
- - SLA
65
- - stakeholders
66
- - team
67
- - roles
68
- - servers
69
- - pricing
70
- - custom properties
61
+ - [x] Shared types (`StableId`, `Tags`, `CustomProperty`, `AuthoritativeDefinitions`, `ContractDescription`)
62
+ - [x] Root `DataContract` with v3.1.0 required fields
63
+ - [x] `SchemaObject` / `SchemaProperty` with nested quality
64
+ - [x] Section modules: SLA, servers, team (object + legacy array), roles, pricing, support
65
+ - [x] `stakeholders` documented as N/A for v3.1.0
71
66
 
72
67
  ## Phase 3 — Parsing
73
68
 
74
- - Parse YAML and JSON
75
- - Preserve unknown extension fields
76
- - Return structured errors and diagnostics
69
+ **Target:** `0.3.0` **Complete**
70
+
71
+ - [x] YAML and JSON parsing via serde
72
+ - [x] Parse helpers (`success` / `failure_from_serde`)
73
+ - [x] Parse diagnostics with paths and unknown-field detection
74
+ - [x] Fixture migration and round-trip tests
75
+ - [x] Upstream JSON Schema reference fixture pinned under `tests/fixtures/`
77
76
 
78
77
  ## Phase 4 — Diagnostics
79
78
 
@@ -0,0 +1,35 @@
1
+ # ODCS Canonical Object Model Guide
2
+
3
+ The root type is [`DataContract`](../../src/model/contract.rs), aligned with upstream ODCS v3.1.0.
4
+
5
+ ## Root document
6
+
7
+ Required fields: `version`, `apiVersion`, `kind`, `id`, `status`.
8
+
9
+ Optional sections: `name`, `tenant`, `tags`, `domain`, `description`, `servers`, `schema`, `support`, `price`, `team`, `roles`, `slaProperties`, `authoritativeDefinitions`, `customProperties`, `contractCreatedTs`.
10
+
11
+ Deprecated fields (parsed but not required): `dataProduct`, `slaDefaultElement`.
12
+
13
+ Quality rules are nested under `schema[]` objects and properties — not at the contract root.
14
+
15
+ ## Module layout
16
+
17
+ | Module | Types |
18
+ |--------|-------|
19
+ | `shared` | `StableId`, `Tags`, `CustomProperty`, `AuthoritativeDefinition`, `ContractDescription`, `SchemaElement` |
20
+ | `schema` | `SchemaObject`, `SchemaProperty` |
21
+ | `quality` | `DataQuality`, `DataQualityChecks` |
22
+ | `sla` | `ServiceLevelAgreementProperty` |
23
+ | `servers` | `Server` |
24
+ | `team` | `Team`, `TeamMember`, `TeamDeclaration` |
25
+ | `roles` | `Role` |
26
+ | `pricing` | `Pricing` |
27
+ | `support` | `SupportItem`, `Support` |
28
+ | `relationships` | `RelationshipSchemaLevel`, `RelationshipPropertyLevel` |
29
+
30
+ ## Design rules
31
+
32
+ - Prefer explicit structs mapped from the upstream JSON Schema.
33
+ - Use `customProperties` arrays for extensions (root `additionalProperties: false`).
34
+ - Separate model types from validation logic.
35
+ - Apply `#[serde(rename_all = "camelCase")]` on document structs.
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "version": "3.1.0",
3
+ "apiVersion": "v3.1.0",
3
4
  "kind": "DataContract",
5
+ "id": "customer-data-contract",
4
6
  "name": "customer_data_contract",
5
7
  "status": "draft",
6
8
  "schema": [
7
9
  {
8
10
  "name": "customers",
11
+ "logicalType": "object",
9
12
  "physicalName": "customers",
10
13
  "properties": [
11
14
  {
@@ -19,15 +22,14 @@
19
22
  "logicalType": "string",
20
23
  "required": false
21
24
  }
25
+ ],
26
+ "quality": [
27
+ {
28
+ "name": "customer_id_not_null",
29
+ "type": "library",
30
+ "metric": "not_null"
31
+ }
22
32
  ]
23
33
  }
24
- ],
25
- "quality": [
26
- {
27
- "name": "customer_id_not_null",
28
- "type": "library",
29
- "rule": "not_null",
30
- "field": "customer_id"
31
- }
32
34
  ]
33
35
  }
@@ -1,10 +1,13 @@
1
1
  version: "3.1.0"
2
+ apiVersion: "v3.1.0"
2
3
  kind: "DataContract"
4
+ id: "customer-data-contract"
3
5
  name: "customer_data_contract"
4
6
  status: "draft"
5
7
 
6
8
  schema:
7
9
  - name: "customers"
10
+ logicalType: "object"
8
11
  physicalName: "customers"
9
12
  properties:
10
13
  - name: "customer_id"
@@ -14,9 +17,7 @@ schema:
14
17
  - name: "email"
15
18
  logicalType: "string"
16
19
  required: false
17
-
18
- quality:
19
- - name: "customer_id_not_null"
20
- type: "library"
21
- rule: "not_null"
22
- field: "customer_id"
20
+ quality:
21
+ - name: "customer_id_not_null"
22
+ type: "library"
23
+ metric: "not_null"
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "pyodcs"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Reference implementation of the Open Data Contract Standard (ODCS)"
9
9
  readme = "README.md"
10
10
  license = { text = "Apache-2.0" }
@@ -41,7 +41,10 @@ def test_parse_file_repo_example() -> None:
41
41
 
42
42
 
43
43
  def test_validate_result_merges_parse_and_validation_diagnostics() -> None:
44
- result = pyodcs.parse(b"version: not-a-version\nkind: wrong\nname: ''\nstatus: draft\n", "yaml")
44
+ result = pyodcs.parse(
45
+ b"version: '3.1.0'\napiVersion: v9.9.9\nkind: wrong\nid: ''\nstatus: draft\n",
46
+ "yaml",
47
+ )
45
48
  report = pyodcs.validate_result(result)
46
49
  assert not pyodcs.is_valid(report)
47
50
 
@@ -52,3 +55,4 @@ def test_inspect_contract() -> None:
52
55
  assert contract is not None
53
56
  summary = pyodcs.inspect(contract)
54
57
  assert "customer_data_contract" in summary
58
+ assert "customer-data-contract" in summary
@@ -122,12 +122,14 @@ pub fn run(cli: Cli) -> i32 {
122
122
  };
123
123
  if json {
124
124
  let summary = serde_json::json!({
125
+ "id": contract.id,
125
126
  "name": contract.name,
126
127
  "version": contract.version,
128
+ "apiVersion": contract.api_version,
127
129
  "kind": contract.kind,
128
130
  "status": contract.status,
129
131
  "schemaCount": contract.schema.len(),
130
- "qualityCount": contract.quality.len(),
132
+ "qualityCount": contract.quality_rules().len(),
131
133
  });
132
134
  if let Err(error) = writeln!(
133
135
  io::stdout(),
@@ -22,12 +22,14 @@ pub(crate) use builders::{com_error, emit, validation_error};
22
22
  #[must_use]
23
23
  pub fn inspect_contract(contract: &DataContract) -> String {
24
24
  format!(
25
- "name: {}\nversion: {}\nkind: {}\nstatus: {}\nschema: {}\nquality: {}\n",
26
- contract.name,
25
+ "id: {}\nname: {}\nversion: {}\napiVersion: {}\nkind: {}\nstatus: {}\nschema: {}\nquality: {}\n",
26
+ contract.id,
27
+ contract.name.as_deref().unwrap_or("-"),
27
28
  contract.version,
29
+ contract.api_version,
28
30
  contract.kind,
29
31
  contract.status,
30
32
  contract.schema.len(),
31
- contract.quality.len(),
33
+ contract.quality_rules().len(),
32
34
  )
33
35
  }
@@ -14,8 +14,9 @@
14
14
  //!
15
15
  //! let yaml = br#"
16
16
  //! version: "3.1.0"
17
+ //! apiVersion: "v3.1.0"
17
18
  //! kind: "DataContract"
18
- //! name: "example"
19
+ //! id: "example"
19
20
  //! status: "draft"
20
21
  //! "#;
21
22
  //!
@@ -0,0 +1,111 @@
1
+ //! Root data contract document.
2
+
3
+ use serde::{Deserialize, Serialize};
4
+
5
+ use super::fundamentals::ContractDescription;
6
+ use super::pricing::Pricing;
7
+ use super::quality::DataQuality;
8
+ use super::roles::Role;
9
+ use super::schema::SchemaObject;
10
+ use super::servers::Server;
11
+ use super::shared::{AuthoritativeDefinitions, CustomProperties, Tags};
12
+ use super::sla::ServiceLevelAgreementProperty;
13
+ use super::support::Support;
14
+ use super::team::TeamDeclaration;
15
+ use super::versioning::is_supported_api_version;
16
+
17
+ /// Supported upstream ODCS document `version` values for this implementation.
18
+ pub const SUPPORTED_ODCS_VERSIONS: &[&str] = &["3.1.0"];
19
+
20
+ /// An ODCS Data Contract — the canonical root object.
21
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
23
+ pub struct DataContract {
24
+ /// ODCS document version.
25
+ pub version: String,
26
+ /// ODCS API version.
27
+ pub api_version: String,
28
+ /// Document kind (typically `DataContract`).
29
+ pub kind: String,
30
+ /// Stable contract identifier.
31
+ pub id: String,
32
+ /// Contract lifecycle status.
33
+ pub status: String,
34
+ #[serde(default, skip_serializing_if = "Option::is_none")]
35
+ pub name: Option<String>,
36
+ #[serde(default, skip_serializing_if = "Option::is_none")]
37
+ pub tenant: Option<String>,
38
+ #[serde(default, skip_serializing_if = "Option::is_none")]
39
+ pub tags: Option<Tags>,
40
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
41
+ pub servers: Vec<Server>,
42
+ /// Deprecated data product name.
43
+ #[serde(default, skip_serializing_if = "Option::is_none")]
44
+ pub data_product: Option<String>,
45
+ #[serde(default, skip_serializing_if = "Option::is_none")]
46
+ pub description: Option<ContractDescription>,
47
+ #[serde(default, skip_serializing_if = "Option::is_none")]
48
+ pub domain: Option<String>,
49
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
50
+ pub schema: Vec<SchemaObject>,
51
+ #[serde(default, skip_serializing_if = "Option::is_none")]
52
+ pub support: Option<Support>,
53
+ #[serde(default, skip_serializing_if = "Option::is_none")]
54
+ pub price: Option<Pricing>,
55
+ #[serde(default, skip_serializing_if = "Option::is_none")]
56
+ pub team: Option<TeamDeclaration>,
57
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
58
+ pub roles: Vec<Role>,
59
+ /// Deprecated SLA default element.
60
+ #[serde(default, skip_serializing_if = "Option::is_none")]
61
+ pub sla_default_element: Option<String>,
62
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
63
+ pub sla_properties: Vec<ServiceLevelAgreementProperty>,
64
+ #[serde(default, skip_serializing_if = "Option::is_none")]
65
+ pub authoritative_definitions: Option<AuthoritativeDefinitions>,
66
+ #[serde(default, skip_serializing_if = "Option::is_none")]
67
+ pub custom_properties: Option<CustomProperties>,
68
+ #[serde(default, skip_serializing_if = "Option::is_none")]
69
+ pub contract_created_ts: Option<String>,
70
+ }
71
+
72
+ impl DataContract {
73
+ /// Returns `true` when the document version is supported by this crate.
74
+ #[must_use]
75
+ pub fn is_supported_version(&self) -> bool {
76
+ SUPPORTED_ODCS_VERSIONS.contains(&self.version.as_str())
77
+ }
78
+
79
+ /// Returns `true` when the API version is supported by this crate.
80
+ #[must_use]
81
+ pub fn is_supported_api_version(&self) -> bool {
82
+ is_supported_api_version(&self.api_version)
83
+ }
84
+
85
+ /// Returns all nested quality rules declared on schema objects and properties.
86
+ #[must_use]
87
+ pub fn quality_rules(&self) -> Vec<&DataQuality> {
88
+ let mut rules = Vec::new();
89
+ for schema in &self.schema {
90
+ if let Some(ref quality) = schema.quality {
91
+ rules.extend(quality.iter());
92
+ }
93
+ collect_property_quality(&schema.properties, &mut rules);
94
+ }
95
+ rules
96
+ }
97
+ }
98
+
99
+ fn collect_property_quality<'a>(
100
+ properties: &'a [super::schema::SchemaProperty],
101
+ out: &mut Vec<&'a DataQuality>,
102
+ ) {
103
+ for property in properties {
104
+ if let Some(ref quality) = property.quality {
105
+ out.extend(quality.iter());
106
+ }
107
+ if !property.properties.is_empty() {
108
+ collect_property_quality(&property.properties, out);
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,3 @@
1
+ //! Custom property types.
2
+
3
+ pub use super::shared::{CustomProperties, CustomProperty};
@@ -0,0 +1,3 @@
1
+ //! Document identity and fundamentals helpers.
2
+
3
+ pub use super::shared::ContractDescription;
@@ -0,0 +1,36 @@
1
+ //! Canonical Object Model types derived from the upstream ODCS specification.
2
+
3
+ #![allow(missing_docs)]
4
+
5
+ mod contract;
6
+ mod custom;
7
+ mod fundamentals;
8
+ mod pricing;
9
+ mod quality;
10
+ mod relationships;
11
+ mod roles;
12
+ mod schema;
13
+ mod servers;
14
+ mod shared;
15
+ mod sla;
16
+ mod stakeholders;
17
+ mod support;
18
+ mod team;
19
+ mod versioning;
20
+
21
+ pub use contract::{DataContract, SUPPORTED_ODCS_VERSIONS};
22
+ pub use custom::{CustomProperties, CustomProperty};
23
+ pub use fundamentals::ContractDescription;
24
+ pub use pricing::Pricing;
25
+ pub use quality::{DataQuality, DataQualityChecks};
26
+ pub use relationships::{RelationshipEndpoint, RelationshipPropertyLevel, RelationshipSchemaLevel};
27
+ pub use roles::Role;
28
+ pub use schema::{Field, SchemaObject, SchemaProperty};
29
+ pub use servers::Server;
30
+ pub use shared::{
31
+ AuthoritativeDefinition, AuthoritativeDefinitions, SchemaElement, StableId, Tags,
32
+ };
33
+ pub use sla::ServiceLevelAgreementProperty;
34
+ pub use support::{Support, SupportItem};
35
+ pub use team::{Team, TeamDeclaration, TeamMember};
36
+ pub use versioning::{is_supported_api_version, SUPPORTED_API_VERSIONS};
@@ -0,0 +1,19 @@
1
+ //! Pricing types.
2
+
3
+ use serde::{Deserialize, Serialize};
4
+
5
+ use super::shared::StableId;
6
+
7
+ /// Pricing information for a data contract.
8
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9
+ #[serde(rename_all = "camelCase")]
10
+ pub struct Pricing {
11
+ #[serde(default, skip_serializing_if = "Option::is_none")]
12
+ pub id: Option<StableId>,
13
+ #[serde(default, skip_serializing_if = "Option::is_none")]
14
+ pub price_amount: Option<f64>,
15
+ #[serde(default, skip_serializing_if = "Option::is_none")]
16
+ pub price_currency: Option<String>,
17
+ #[serde(default, skip_serializing_if = "Option::is_none")]
18
+ pub price_unit: Option<String>,
19
+ }
@@ -0,0 +1,76 @@
1
+ //! Data quality rule types.
2
+
3
+ use serde::{Deserialize, Serialize};
4
+ use serde_json::Value;
5
+
6
+ use super::shared::{AuthoritativeDefinitions, CustomProperties, StableId, Tags};
7
+
8
+ /// Data quality checks attached to schema objects or properties.
9
+ pub type DataQualityChecks = Vec<DataQuality>;
10
+
11
+ /// A single data quality rule.
12
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13
+ #[serde(rename_all = "camelCase")]
14
+ pub struct DataQuality {
15
+ #[serde(default, skip_serializing_if = "Option::is_none")]
16
+ pub id: Option<StableId>,
17
+ #[serde(default, skip_serializing_if = "Option::is_none")]
18
+ pub authoritative_definitions: Option<AuthoritativeDefinitions>,
19
+ #[serde(default, skip_serializing_if = "Option::is_none")]
20
+ pub business_impact: Option<String>,
21
+ #[serde(default, skip_serializing_if = "Option::is_none")]
22
+ pub custom_properties: Option<CustomProperties>,
23
+ #[serde(default, skip_serializing_if = "Option::is_none")]
24
+ pub description: Option<String>,
25
+ #[serde(default, skip_serializing_if = "Option::is_none")]
26
+ pub dimension: Option<String>,
27
+ #[serde(default, skip_serializing_if = "Option::is_none")]
28
+ pub method: Option<String>,
29
+ #[serde(default, skip_serializing_if = "Option::is_none")]
30
+ pub name: Option<String>,
31
+ #[serde(default, skip_serializing_if = "Option::is_none")]
32
+ pub schedule: Option<String>,
33
+ #[serde(default, skip_serializing_if = "Option::is_none")]
34
+ pub scheduler: Option<String>,
35
+ #[serde(default, skip_serializing_if = "Option::is_none")]
36
+ pub severity: Option<String>,
37
+ #[serde(default, skip_serializing_if = "Option::is_none")]
38
+ pub tags: Option<Tags>,
39
+ /// Quality check type: `text`, `library`, `sql`, or `custom`.
40
+ #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
41
+ pub rule_type: Option<String>,
42
+ #[serde(default, skip_serializing_if = "Option::is_none")]
43
+ pub unit: Option<String>,
44
+ /// Library metric (v3.1.0).
45
+ #[serde(default, skip_serializing_if = "Option::is_none")]
46
+ pub metric: Option<String>,
47
+ /// Deprecated library rule name (pre-v3.1 compatibility in documents).
48
+ #[serde(default, skip_serializing_if = "Option::is_none")]
49
+ pub rule: Option<String>,
50
+ #[serde(default, skip_serializing_if = "Option::is_none")]
51
+ pub arguments: Option<Value>,
52
+ /// SQL query for `type: sql` rules.
53
+ #[serde(default, skip_serializing_if = "Option::is_none")]
54
+ pub query: Option<String>,
55
+ /// Custom engine for `type: custom` rules.
56
+ #[serde(default, skip_serializing_if = "Option::is_none")]
57
+ pub engine: Option<String>,
58
+ #[serde(default, skip_serializing_if = "Option::is_none")]
59
+ pub implementation: Option<String>,
60
+ #[serde(default, skip_serializing_if = "Option::is_none")]
61
+ pub must_be: Option<Value>,
62
+ #[serde(default, skip_serializing_if = "Option::is_none")]
63
+ pub must_not_be: Option<Value>,
64
+ #[serde(default, skip_serializing_if = "Option::is_none")]
65
+ pub must_be_greater_than: Option<f64>,
66
+ #[serde(default, skip_serializing_if = "Option::is_none")]
67
+ pub must_be_greater_or_equal_to: Option<f64>,
68
+ #[serde(default, skip_serializing_if = "Option::is_none")]
69
+ pub must_be_less_than: Option<f64>,
70
+ #[serde(default, skip_serializing_if = "Option::is_none")]
71
+ pub must_be_less_or_equal_to: Option<f64>,
72
+ #[serde(default, skip_serializing_if = "Option::is_none")]
73
+ pub must_be_between: Option<Vec<f64>>,
74
+ #[serde(default, skip_serializing_if = "Option::is_none")]
75
+ pub must_not_be_between: Option<Vec<f64>>,
76
+ }
@@ -0,0 +1,44 @@
1
+ //! Schema relationship types.
2
+
3
+ use serde::{Deserialize, Serialize};
4
+
5
+ use super::shared::CustomProperties;
6
+
7
+ /// Shared relationship fields.
8
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9
+ #[serde(rename_all = "camelCase")]
10
+ pub struct RelationshipBase {
11
+ #[serde(default, skip_serializing_if = "Option::is_none")]
12
+ pub relationship_type: Option<String>,
13
+ #[serde(default, skip_serializing_if = "Option::is_none")]
14
+ pub custom_properties: Option<CustomProperties>,
15
+ }
16
+
17
+ /// Relationship at schema-object level.
18
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19
+ #[serde(rename_all = "camelCase")]
20
+ pub struct RelationshipSchemaLevel {
21
+ #[serde(flatten)]
22
+ pub base: RelationshipBase,
23
+ pub from: RelationshipEndpoint,
24
+ pub to: RelationshipEndpoint,
25
+ }
26
+
27
+ /// Relationship at property level.
28
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29
+ #[serde(rename_all = "camelCase")]
30
+ pub struct RelationshipPropertyLevel {
31
+ #[serde(flatten)]
32
+ pub base: RelationshipBase,
33
+ pub to: RelationshipEndpoint,
34
+ }
35
+
36
+ /// Relationship endpoint (single column or composite key).
37
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38
+ #[serde(untagged)]
39
+ pub enum RelationshipEndpoint {
40
+ /// Single column reference.
41
+ Single(String),
42
+ /// Composite key reference.
43
+ Composite(Vec<String>),
44
+ }
@@ -0,0 +1,24 @@
1
+ //! Role types.
2
+
3
+ use serde::{Deserialize, Serialize};
4
+
5
+ use super::shared::{CustomProperties, StableId};
6
+
7
+ /// IAM role providing access to a dataset.
8
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9
+ #[serde(rename_all = "camelCase")]
10
+ pub struct Role {
11
+ #[serde(default, skip_serializing_if = "Option::is_none")]
12
+ pub id: Option<StableId>,
13
+ pub role: String,
14
+ #[serde(default, skip_serializing_if = "Option::is_none")]
15
+ pub description: Option<String>,
16
+ #[serde(default, skip_serializing_if = "Option::is_none")]
17
+ pub access: Option<String>,
18
+ #[serde(default, skip_serializing_if = "Option::is_none")]
19
+ pub first_level_approvers: Option<String>,
20
+ #[serde(default, skip_serializing_if = "Option::is_none")]
21
+ pub second_level_approvers: Option<String>,
22
+ #[serde(default, skip_serializing_if = "Option::is_none")]
23
+ pub custom_properties: Option<CustomProperties>,
24
+ }