-
Notifications
You must be signed in to change notification settings - Fork 6
Strengthen JSON Schema validation, reference traversal, and version-cast compatibility handling. #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Strengthen JSON Schema validation, reference traversal, and version-cast compatibility handling. #26
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
739cc55
fix(x-gts-ref): traverse implicit and local reference schemas
Artifizer 90304b2
fix(validation): enforce standard formats and preserve unknown cast v…
Artifizer 8da119a
chore(spec): update gts-spec to v0.13.2
Artifizer 1718708
fix(cast): preserve composed schema constraints
Artifizer 9ec7f4b
test(validation): cover standard JSON Schema formats
Artifizer 7fc8650
style: format compatibility and store tests
Artifizer 5ea8bcf
fix(validation): bound JSON Schema pattern evaluation
Artifizer 50faf82
refactor(ids): centralize type identity parsing
Artifizer 706366a
refactor(naming): centralize GTS scheme, marker and JSON-pointer hand…
Artifizer 624b86a
fix(cast): intersect composed schema constraints and report unknown c…
Artifizer ef5ad21
fix(validation): enforce correct RFC 3339 time/date-time formats
Artifizer f4525c9
chore(release): bump version to 0.13.2
Artifizer 3d9c6c8
fix(deps): require pydantic v2 for the server venv
Artifizer fa749af
style: apply ruff formatting to gts and schema_cast
Artifizer 9ddfcd8
fix: enforce JSON Pointer syntax
Artifizer 5b5380b
chore: update gts-spec version to 0.13.3
Artifizer d001f0f
fix: preserve plain JSON Pointer escapes
Artifizer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Submodule .gts-spec
updated
33 files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| """RFC 6901 JSON Pointer resolution. | ||
|
|
||
| A JSON Pointer (RFC 6901) addresses a single value inside a JSON document, e.g. | ||
| ``/properties/type``. Because ``/`` separates reference tokens and ``~`` begins | ||
| an escape sequence, those two characters are escaped *inside* a token: | ||
|
|
||
| - ``~1`` denotes a literal ``/`` | ||
| - ``~0`` denotes a literal ``~`` | ||
|
|
||
| Unescaping MUST replace ``~1`` before ``~0``; otherwise an encoded ``~01`` would | ||
| be corrupted. This module is the single home for that logic, which was | ||
| previously duplicated (with the same ``~1``/``~0`` magic) across ``traits.py`` | ||
| and ``x_gts_ref.py``. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any | ||
| from urllib.parse import unquote | ||
|
|
||
| # Sentinel distinguishing "pointer resolved to a real ``None``" from | ||
| # "pointer could not be resolved". Callers that care should pass this (or their | ||
| # own default) and compare identity against the returned value. | ||
| MISSING: Any = object() | ||
|
|
||
|
|
||
| def unescape_token(token: str) -> str: | ||
| """Decode a single RFC 6901 reference token (``~1`` -> ``/``, ``~0`` -> ``~``).""" | ||
| return token.replace("~1", "/").replace("~0", "~") | ||
|
|
||
|
|
||
| def resolve(document: Any, pointer: str, default: Any = None) -> Any: | ||
| """Resolve an RFC 6901 JSON Pointer against ``document``. | ||
|
|
||
| ``pointer`` accepts three equivalent spellings: | ||
|
|
||
| - the empty string ``""`` - the whole document; | ||
| - a pointer beginning with ``/`` - ``/a/b``; | ||
| - a same-document URI fragment - ``#`` or ``#/a/b``. | ||
|
|
||
| Returns ``default`` if any reference token cannot be resolved (missing key, | ||
| non-integer/out-of-range array index, or descending into a scalar). | ||
| """ | ||
| if pointer.startswith("#"): | ||
| pointer = unquote(pointer[1:]) | ||
| if pointer == "": | ||
| return document | ||
| if not pointer.startswith("/"): | ||
| return default | ||
|
|
||
| current = document | ||
| for raw_token in pointer.split("/")[1:]: | ||
| token = unescape_token(raw_token) | ||
| if isinstance(current, dict): | ||
| if token not in current: | ||
| return default | ||
| current = current[token] | ||
| elif isinstance(current, list): | ||
| if not ( | ||
| token.isascii() | ||
| and token.isdecimal() | ||
| and (token == "0" or not token.startswith("0")) | ||
| ): | ||
| return default | ||
| try: | ||
| current = current[int(token)] | ||
| except IndexError: | ||
| return default | ||
| else: | ||
| return default | ||
| return current |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| """Internal naming primitives for GTS identifiers. | ||
|
|
||
| This module is the single, **internal** home for the low-level string handling | ||
| of GTS identifiers: | ||
|
|
||
| - the ``gts://`` URI scheme, | ||
| - the bare ``gts.`` prefix, and | ||
| - the ``~`` type marker. | ||
|
|
||
| It is deliberately private (underscore-prefixed and absent from the public | ||
| package exports). Consumers of the SDK should never reach for these primitives: | ||
| they work with the :class:`~gts.gts.GtsID` value object and high-level | ||
| operations (validate, cast, resolve, store lookups, ...), all of which normalize | ||
| identifiers internally. Keeping this logic in one private place stops it from | ||
| leaking across the library and onto the public API surface. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| # Distinguishes GTS identifiers from other strings; also used for URI encoding. | ||
| GTS_PREFIX = "gts." | ||
| GTS_URI_PREFIX = "gts://" | ||
| # Separates the segments of a chained identifier and, at the very end, marks a | ||
| # type identifier (e.g. ``gts.acme.pkg._.user.v1~``). | ||
| GTS_TYPE_MARKER = "~" | ||
|
|
||
|
|
||
| def strip_scheme(value: str) -> str: | ||
| """Return the canonical bare form, dropping any ``gts://`` scheme. | ||
|
|
||
| Non-GTS strings are returned unchanged, so this is safe to call on arbitrary | ||
| registry keys or ``$ref`` targets at a boundary. | ||
| """ | ||
| return value.removeprefix(GTS_URI_PREFIX) | ||
|
|
||
|
|
||
| def has_scheme(value: str) -> bool: | ||
| """True if ``value`` carries the ``gts://`` URI scheme.""" | ||
| return value.startswith(GTS_URI_PREFIX) | ||
|
|
||
|
|
||
| def with_scheme(value: str) -> str: | ||
| """Return the ``gts://`` URI encoding of ``value`` (idempotent).""" | ||
| return value if has_scheme(value) else GTS_URI_PREFIX + value | ||
|
|
||
|
|
||
| def looks_like_gts(value: str) -> bool: | ||
| """True if ``value`` looks like a GTS identifier in either encoding. | ||
|
|
||
| A cheap prefix check (bare ``gts.`` or ``gts://``); it does not fully | ||
| validate the identifier. | ||
| """ | ||
| return value.startswith((GTS_URI_PREFIX, GTS_PREFIX)) | ||
|
|
||
|
|
||
| def is_type_ref(value: str) -> bool: | ||
| """True if the (scheme-stripped) identifier denotes a type. | ||
|
|
||
| Type identifiers end with the type marker ``~``; instance identifiers do | ||
| not. | ||
| """ | ||
| return strip_scheme(value).endswith(GTS_TYPE_MARKER) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.