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.
- {pyodcs-0.2.0 → pyodcs-0.3.0}/CHANGELOG.md +18 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/Cargo.lock +1 -1
- {pyodcs-0.2.0 → pyodcs-0.3.0}/Cargo.toml +1 -1
- {pyodcs-0.2.0 → pyodcs-0.3.0}/PKG-INFO +3 -3
- {pyodcs-0.2.0 → pyodcs-0.3.0}/README.md +2 -2
- {pyodcs-0.2.0 → pyodcs-0.3.0}/ROADMAP.md +15 -16
- pyodcs-0.3.0/docs/implementation/model-guide.md +35 -0
- {pyodcs-0.2.0/tests/fixtures → pyodcs-0.3.0/examples}/minimal.odcs.json +10 -8
- {pyodcs-0.2.0 → pyodcs-0.3.0}/examples/minimal.odcs.yaml +7 -6
- {pyodcs-0.2.0 → pyodcs-0.3.0}/pyproject.toml +1 -1
- {pyodcs-0.2.0 → pyodcs-0.3.0}/python/tests/test_pyodcs.py +5 -1
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/cli/mod.rs +3 -1
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/mod.rs +5 -3
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/lib.rs +2 -1
- pyodcs-0.3.0/src/model/contract.rs +111 -0
- pyodcs-0.3.0/src/model/custom.rs +3 -0
- pyodcs-0.3.0/src/model/fundamentals.rs +3 -0
- pyodcs-0.3.0/src/model/mod.rs +36 -0
- pyodcs-0.3.0/src/model/pricing.rs +19 -0
- pyodcs-0.3.0/src/model/quality.rs +76 -0
- pyodcs-0.3.0/src/model/relationships.rs +44 -0
- pyodcs-0.3.0/src/model/roles.rs +24 -0
- pyodcs-0.3.0/src/model/schema.rs +98 -0
- pyodcs-0.3.0/src/model/servers.rs +31 -0
- pyodcs-0.3.0/src/model/shared.rs +85 -0
- pyodcs-0.3.0/src/model/sla.rs +37 -0
- pyodcs-0.3.0/src/model/stakeholders.rs +4 -0
- pyodcs-0.3.0/src/model/support.rs +29 -0
- pyodcs-0.3.0/src/model/team.rs +73 -0
- pyodcs-0.3.0/src/model/versioning.rs +12 -0
- pyodcs-0.3.0/src/parser/json.rs +21 -0
- pyodcs-0.3.0/src/parser/mod.rs +159 -0
- pyodcs-0.3.0/src/parser/yaml.rs +15 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/mod.rs +32 -3
- pyodcs-0.3.0/tests/fixtures/invalid-empty-name.yaml +6 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/tests/fixtures/invalid-kind.yaml +2 -0
- {pyodcs-0.2.0/examples → pyodcs-0.3.0/tests/fixtures}/minimal.odcs.json +10 -8
- {pyodcs-0.2.0 → pyodcs-0.3.0}/tests/fixtures/minimal.odcs.yaml +7 -6
- pyodcs-0.3.0/tests/fixtures/odcs-json-schema-v3.1.0.json +2929 -0
- pyodcs-0.3.0/tests/fixtures/unknown-field.yaml +7 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/tests/fixtures/unsupported-version.yaml +2 -0
- pyodcs-0.3.0/tests/fixtures/with-custom-properties.yaml +29 -0
- pyodcs-0.3.0/tests/fixtures/with-extensions.yaml +22 -0
- pyodcs-0.3.0/tests/fixtures/with-pricing.yaml +18 -0
- pyodcs-0.3.0/tests/fixtures/with-roles.yaml +19 -0
- pyodcs-0.3.0/tests/fixtures/with-schema-properties.yaml +30 -0
- pyodcs-0.3.0/tests/fixtures/with-schema-quality.yaml +29 -0
- pyodcs-0.3.0/tests/fixtures/with-servers.yaml +21 -0
- pyodcs-0.3.0/tests/fixtures/with-sla.yaml +21 -0
- pyodcs-0.3.0/tests/fixtures/with-support.yaml +20 -0
- pyodcs-0.3.0/tests/fixtures/with-team-legacy-array.yaml +18 -0
- pyodcs-0.3.0/tests/fixtures/with-team.yaml +22 -0
- pyodcs-0.3.0/tests/skeleton.rs +228 -0
- pyodcs-0.2.0/docs/implementation/model-guide.md +0 -36
- pyodcs-0.2.0/src/model/contract.rs +0 -41
- pyodcs-0.2.0/src/model/custom.rs +0 -3
- pyodcs-0.2.0/src/model/field.rs +0 -20
- pyodcs-0.2.0/src/model/fundamentals.rs +0 -3
- pyodcs-0.2.0/src/model/mod.rs +0 -21
- pyodcs-0.2.0/src/model/pricing.rs +0 -3
- pyodcs-0.2.0/src/model/quality.rs +0 -20
- pyodcs-0.2.0/src/model/roles.rs +0 -3
- pyodcs-0.2.0/src/model/schema.rs +0 -19
- pyodcs-0.2.0/src/model/servers.rs +0 -3
- pyodcs-0.2.0/src/model/sla.rs +0 -3
- pyodcs-0.2.0/src/model/stakeholders.rs +0 -3
- pyodcs-0.2.0/src/model/support.rs +0 -3
- pyodcs-0.2.0/src/model/team.rs +0 -3
- pyodcs-0.2.0/src/model/versioning.rs +0 -3
- pyodcs-0.2.0/src/parser/json.rs +0 -35
- pyodcs-0.2.0/src/parser/mod.rs +0 -86
- pyodcs-0.2.0/src/parser/yaml.rs +0 -35
- pyodcs-0.2.0/tests/fixtures/invalid-empty-name.yaml +0 -4
- pyodcs-0.2.0/tests/fixtures/with-extensions.yaml +0 -7
- pyodcs-0.2.0/tests/skeleton.rs +0 -141
- {pyodcs-0.2.0 → pyodcs-0.3.0}/.editorconfig +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/.github/workflows/checks.yml +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/.github/workflows/ci.yml +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/.github/workflows/release.yml +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/.gitignore +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/CONTRIBUTING.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/LICENSE +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/SPEC.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/README.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/README.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/architecture.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/cli-spec.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/crate-layout.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/diagnostics-guide.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/implementation-phases.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/non-goals.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/project-goal.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/public-api.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/relationship-to-dtcs.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/rust-dependencies.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/spec-usage.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/testing-plan.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/docs/implementation/validation-guide.md +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/python/pyodcs/__init__.py +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/python/pyodcs/__main__.py +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/bin/odcs.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/compatibility/mod.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/builders.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/category.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/codes.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/diagnostic.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/report.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/severity.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/diagnostics/stage.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/python.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/registry/mod.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/document.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/extensions.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/phases.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/quality.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/references.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/schema.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/src/validation/structural.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/tests/cli.rs +0 -0
- {pyodcs-0.2.0 → pyodcs-0.3.0}/tests/fixtures/malformed.json +0 -0
- {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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyodcs
|
|
3
|
-
Version: 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
|
|
29
|
+
**Status:** Pre-release
|
|
30
30
|
**Upstream ODCS version:** 3.1.0
|
|
31
|
-
**Reference implementation:** 0.
|
|
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
|
|
5
|
+
**Status:** Pre-release
|
|
6
6
|
**Upstream ODCS version:** 3.1.0
|
|
7
|
-
**Reference implementation:** 0.
|
|
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 |
|
|
15
|
-
| **3** | [Parsing](#phase-3--parsing) | YAML and JSON parsing with
|
|
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
|
-
|
|
59
|
+
**Target:** `0.3.0` — **Complete**
|
|
60
60
|
|
|
61
|
-
-
|
|
62
|
-
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
rule: "not_null"
|
|
22
|
-
field: "customer_id"
|
|
20
|
+
quality:
|
|
21
|
+
- name: "customer_id_not_null"
|
|
22
|
+
type: "library"
|
|
23
|
+
metric: "not_null"
|
|
@@ -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(
|
|
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.
|
|
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
|
-
"
|
|
26
|
-
contract.
|
|
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.
|
|
33
|
+
contract.quality_rules().len(),
|
|
32
34
|
)
|
|
33
35
|
}
|
|
@@ -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,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
|
+
}
|