Skip to content
Merged
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
60 changes: 60 additions & 0 deletions .github/workflows/publish-schema.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Build schema

on:
push:
branches:
- master
paths:
- 'standard/schema/**'
- 'tools/**'

jobs:
build-and-commit:
runs-on: ubuntu-latest

permissions:
contents: write

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Install dependencies
run: pip install -r requirements.txt

- name: Bundle schema fragments
run: python -m tools.bundle_schema --schema-dir standard/schema --root /schemas/coveragejson --out build/bundle.json

- name: Convert to draft-07
run: |
python -m tools.convert_dialect build/bundle.json --dialect draft-07 --out standard/schema/build/draft-07/coveragejson.json
python -m tools.patch_schema standard/schema/build/draft-07/coveragejson.json --set-id "https://schemas.opengis.net/covjson/1.0/coveragejson.json"

- name: Convert to 2019-09
run: |
python -m tools.convert_dialect build/bundle.json --dialect 2019-09 --out standard/schema/build/2019-09/coveragejson.json
python -m tools.patch_schema standard/schema/build/2019-09/coveragejson.json --set-id "https://schemas.opengis.net/covjson/1.0/coveragejson.json"

- name: Convert to 2020-12
run: |
python -m tools.convert_dialect build/bundle.json --dialect 2020-12 --out standard/schema/build/2020-12/coveragejson.json
python -m tools.patch_schema standard/schema/build/2020-12/coveragejson.json --set-id "https://schemas.opengis.net/covjson/1.0/coveragejson.json"

- name: Remove intermediate bundle
run: rm -rf build

- name: Commit generated schemas
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add standard/schema/build/
if git diff --cached --quiet; then
echo "No changes to generated schemas"
else
git commit -m "build: regenerate bundled schemas from fragments"
git push
fi
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
jsonschema
81 changes: 81 additions & 0 deletions standard/schema/root.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"$id": "/schemas/coveragejson",
"description": "A CoverageJSON object",
"type": "object",
"properties": {
"type": {
"enum": [
"Domain",
"NdArray",
"TiledNdArray",
"Coverage",
"CoverageCollection"
]
}
},
"required": [
"type"
],
"allOf": [
{
"if": {
"properties": {
"type": {
"const": "Domain"
}
}
},
"then": {
"$ref": "/schemas/domain"
}
},
{
"if": {
"properties": {
"type": {
"const": "NdArray"
}
}
},
"then": {
"$ref": "/schemas/ndArray"
}
},
{
"if": {
"properties": {
"type": {
"const": "TiledNdArray"
}
}
},
"then": {
"$ref": "/schemas/tiledNdArray"
}
},
{
"if": {
"properties": {
"type": {
"const": "Coverage"
}
}
},
"then": {
"$ref": "/schemas/coverage"
}
},
{
"if": {
"properties": {
"type": {
"const": "CoverageCollection"
}
}
},
"then": {
"$ref": "/schemas/coverageCollection"
}
}
]
}
Empty file added tools/__init__.py
Empty file.
87 changes: 87 additions & 0 deletions tools/bundle_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Bundles all referenced schema fragments into a single root schema.
# Adapted from https://github.com/covjson/covjson-validator/blob/main/tools/bundle_schema.py
#
# Follows the method described in:
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-00#section-9.3.1

import argparse
import json
import copy
import os


def walk_dict(obj, match_key, fn):
"""Recursively walk a dict and call fn(obj, key, value) for every match_key found."""
for key, value in list(obj.items()):
if key == match_key:
fn(obj, key, value)
elif isinstance(value, dict):
walk_dict(value, match_key, fn)
elif isinstance(value, list):
for item in value:
if isinstance(item, dict):
walk_dict(item, match_key, fn)


def create_schema_store(schema_dir):
"""Load all JSON schema fragments from a directory into a dict keyed by $id."""
schema_store = {}
for entry in os.scandir(schema_dir):
if entry.is_file() and entry.path.endswith(".json"):
with open(entry.path) as f:
schema = json.load(f)
if "$id" in schema:
schema_store[schema["$id"]] = schema
return schema_store


def bundle_schema(schema_store, root_schema_id):
"""Bundle all referenced schemas into the root schema using $defs."""
root_schema = copy.deepcopy(schema_store[root_schema_id])

# Collect all referenced schema IDs recursively
refs = set()

def record_ref(obj, key, value):
if value in schema_store:
refs.add(value)

done = set()
todo = {root_schema_id}
while todo:
for schema_id in todo:
walk_dict(schema_store[schema_id], "$ref", record_ref)
done.add(schema_id)
todo = refs - done

# Remove self-reference if present
refs.discard(root_schema_id)

# Embed referenced schemas under $defs
if refs:
defs = root_schema.setdefault("$defs", {})
for schema_id in sorted(refs):
defs[schema_id] = copy.deepcopy(schema_store[schema_id])

return root_schema


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Bundle CoverageJSON schema fragments into a single file")
parser.add_argument("--schema-dir", default="standard/schema",
help="Directory containing schema fragment JSON files")
parser.add_argument("--root", default="/schemas/coveragejson",
help="$id of the root schema fragment")
parser.add_argument("--out", default="dist/coveragejson.json",
help="Output file path")
args = parser.parse_args()

store = create_schema_store(args.schema_dir)
schema = bundle_schema(store, args.root)

os.makedirs(os.path.dirname(args.out), exist_ok=True)
with open(args.out, "w") as f:
json.dump(schema, f, indent=2)
f.write("\n")

print(f"Bundled schema written to {args.out}")
Loading
Loading