Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/scripts/validate_shacl.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file does not need the api spec (yaml) to run. It checks sample data against shacl.ttl (which is included as git submodule). Is shacl.ttl derivation of the openapi specs? Am I missing some context?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenAPI check and SHACL check are two separate jobs in this workflow. I added the SHACL step leaving the preexisting OpenAPI job untouched

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import json
import sys
from pathlib import Path

from pyshacl import validate
from rdflib import Graph

OPENAPI_VER_DIR = Path("openapi/ver")
SHACL_DIR = Path("data-model/shacl")


def load_shapes(version):
shapes_path = SHACL_DIR / version / "shacl.ttl"
if not shapes_path.exists():
return None
g = Graph()
g.parse(str(shapes_path), format="turtle")
return g


def validate_file(json_path, shapes_graph):
with open(json_path, encoding="utf-8") as f:
data = json.load(f)
data_graph = Graph()
data_graph.parse(data=json.dumps(data), format="json-ld")
if len(data_graph) == 0:
return False, "Parsed RDF graph is empty (context resolution may have failed)"
conforms, _, results_text = validate(
data_graph=data_graph, shacl_graph=shapes_graph, debug=False
)
return conforms, results_text


def main():
failures = []
versions_found = 0

for version_dir in sorted(OPENAPI_VER_DIR.iterdir()):
sample_data_dir = version_dir / "sample_data"
json_files = sorted(sample_data_dir.glob("**/*.json")) if sample_data_dir.is_dir() else []
if not json_files:
continue

shapes_graph = load_shapes(version_dir.name)

versions_found += 1
for json_path in json_files:
conforms, results_text = validate_file(json_path, shapes_graph)
if conforms:
print(f"PASS {json_path}")
else:
print(f"FAIL {json_path}\n{results_text}")
failures.append(json_path)

print(f"\n{versions_found} versions validated, {len(failures)} failures")
if failures:
sys.exit(1)


if __name__ == "__main__":
main()
19 changes: 19 additions & 0 deletions .github/workflows/validate_specs_and_jsons.yml

@vicding-mi vicding-mi Jun 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure whether the submodule can trigger the action pipeline. Please kindly elaborate. The push or pr would update the pointer in the parent repository, but files in sub-folder might not be enough to trigger the pipeline.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. What about filtering on the gitlink path itself, data-model, instead of the sub-folder? A push/PR that bumps the pointer changes exactly that path, so the workflow fires (tested here https://github.com/orgs/community/discussions/66060).

That means validation runs whenever we bump the submodule here, which is the moment we actually want to re-check the sample data against the new shapes. It won't run if the shapes change upstream without a pointer bump.

Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ on:
paths:
- 'openapi/ver/**/*.json'
- 'openapi/ver/**/skg-if-openapi.yaml'
- 'data-model'
pull_request:
paths:
- 'openapi/ver/**/*.json'
- 'openapi/ver/**/skg-if-openapi.yaml'
- 'data-model'

jobs:
validate_yaml_files:
Expand All @@ -26,6 +28,23 @@ jobs:
- if: ${{ failure() }}
run: exit()

validate_shacl:
runs-on: ubuntu-latest
name: Validate SHACL Shapes
needs: validate_yaml_files

steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
submodules: true

- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8

- name: Validate sample data against SHACL shapes
run: uv run python .github/scripts/validate_shacl.py

validate_json_files:
runs-on: ubuntu-latest
name: Validate JSON Files
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "data-model"]
path = data-model
url = https://github.com/skg-if/data-model
1 change: 1 addition & 0 deletions data-model
Submodule data-model added at 2d98ae
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ dependencies = [

[tool.uv.sources]
plain-text-markdown-extention = { git = "https://github.com/kostyachum/python-markdown-plain-text.git" }

[dependency-groups]
dev = [
"pyshacl>=0.31.0",
]
102 changes: 101 additions & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.