Skip to content

Commit ac771a6

Browse files
committed
🐛 fix(schema): declare prefix in the labeled factor group
SchemaStore compiles the published schema with ajv strict mode, which refuses a `required` naming a property the schema never declares. The labeled factor group has said `not: {required: [prefix]}` since April to keep a bare range dict from reading as a label, and ajv had been satisfied by `prefix` reaching it from an inlined sibling. Adding the range and values shapes to its `additionalProperties` in 4.61 changed how ajv inlines, the property stopped arriving, and the release sync PR failed to compile. Declaring `prefix` inside the `not` with an empty schema names it for strict mode while constraining nothing, so validation is unchanged. The new test walks the committed schema for the same defect, since neither the freshness check nor the tombi lint compiles the schema this way.
1 parent aa8f46c commit ac771a6

4 files changed

Lines changed: 32 additions & 1 deletion

File tree

docs/changelog/4051.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Declare ``prefix`` inside the labeled factor group's ``not`` clause of the generated JSON Schema, so the published
2+
schema compiles under the strict mode SchemaStore validates with - by :user:`gaborbernat`.

src/tox/session/cmd/schema.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ def gen_schema(state: State) -> int:
223223
"type": "object",
224224
"minProperties": 1,
225225
"maxProperties": 1,
226-
"not": {"required": ["prefix"]},
226+
# ``properties`` declares the key so ajv strict mode can see it; ``{}`` adds no constraint of its own
227+
"not": {"properties": {"prefix": {}}, "required": ["prefix"]},
227228
"additionalProperties": {
228229
"oneOf": [
229230
{"type": "array", "items": {"type": "string"}},

src/tox/tox.schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,9 @@
804804
"minProperties": 1,
805805
"maxProperties": 1,
806806
"not": {
807+
"properties": {
808+
"prefix": {}
809+
},
807810
"required": ["prefix"]
808811
},
809812
"additionalProperties": {

tests/session/cmd/test_schema.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import pytest
1212

1313
if TYPE_CHECKING:
14+
from collections.abc import Iterator
15+
1416
from tox.pytest import MonkeyPatch, ToxProjectCreator
1517

1618

@@ -143,6 +145,29 @@ def test_schema_freshness(
143145
)
144146

145147

148+
def _subschemas(node: Any, pointer: str = "#") -> Iterator[tuple[str, dict[str, Any]]]:
149+
if isinstance(node, dict):
150+
yield pointer, node
151+
for key, value in node.items():
152+
yield from _subschemas(value, f"{pointer}/{key}")
153+
elif isinstance(node, list):
154+
for index, value in enumerate(node):
155+
yield from _subschemas(value, f"{pointer}/{index}")
156+
157+
158+
def test_schema_required_properties_are_declared(committed_schema: dict[str, Any]) -> None:
159+
# SchemaStore compiles the published schema with ajv strict mode, which rejects a required property that the
160+
# schema never declares, so catch it here rather than in a release-time sync PR (see tox-dev/tox#4051)
161+
undeclared = [
162+
f"{pointer} requires {name!r}"
163+
for pointer, schema in _subschemas(committed_schema)
164+
if isinstance(schema.get("required"), list)
165+
for name in schema["required"]
166+
if name not in schema.get("properties", {})
167+
]
168+
assert undeclared == []
169+
170+
146171
def test_schema_allows_deps_array(committed_schema: dict[str, Any]) -> None:
147172
deps_schema = committed_schema["properties"]["env_run_base"]["properties"]["deps"]
148173

0 commit comments

Comments
 (0)