schema: additionalKeysAreSpecs (#49221)

Currently we validate all keys as specs, but it's meant to validate only additional keys in all cases.
This commit is contained in:
Harmen Stoppels 2025-02-27 12:17:25 +01:00 committed by GitHub
parent 12fe7aef65
commit 7363047b82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 55 additions and 56 deletions

View File

@ -7,8 +7,7 @@
import warnings import warnings
import jsonschema import jsonschema
import jsonschema.validators
import llnl.util.lang
from spack.error import SpecSyntaxError from spack.error import SpecSyntaxError
@ -18,59 +17,59 @@ class DeprecationMessage(typing.NamedTuple):
error: bool error: bool
# jsonschema is imported lazily as it is heavy to import def _validate_spec(validator, is_spec, instance, schema):
# and increases the start-up time """Check if all additional keys are valid specs."""
def _make_validator(): import spack.spec_parser
def _validate_spec(validator, is_spec, instance, schema):
"""Check if the attributes on instance are valid specs."""
import spack.spec_parser
if not validator.is_type(instance, "object"): if not validator.is_type(instance, "object"):
return return
for spec_str in instance: properties = schema.get("properties") or {}
try:
spack.spec_parser.parse(spec_str)
except SpecSyntaxError:
yield jsonschema.ValidationError(f"the key '{spec_str}' is not a valid spec")
def _deprecated_properties(validator, deprecated, instance, schema): for spec_str in instance:
if not (validator.is_type(instance, "object") or validator.is_type(instance, "array")): if spec_str in properties:
return continue
try:
if not deprecated: spack.spec_parser.parse(spec_str)
return except SpecSyntaxError:
yield jsonschema.ValidationError(f"the key '{spec_str}' is not a valid spec")
deprecations = {
name: DeprecationMessage(message=x["message"], error=x["error"])
for x in deprecated
for name in x["names"]
}
# Get a list of the deprecated properties, return if there is none
issues = [entry for entry in instance if entry in deprecations]
if not issues:
return
# Process issues
errors = []
for name in issues:
msg = deprecations[name].message.format(name=name)
if deprecations[name].error:
errors.append(msg)
else:
warnings.warn(msg)
if errors:
yield jsonschema.ValidationError("\n".join(errors))
return jsonschema.validators.extend(
jsonschema.Draft7Validator,
{"validate_spec": _validate_spec, "deprecatedProperties": _deprecated_properties},
)
Validator = llnl.util.lang.Singleton(_make_validator) def _deprecated_properties(validator, deprecated, instance, schema):
if not (validator.is_type(instance, "object") or validator.is_type(instance, "array")):
return
if not deprecated:
return
deprecations = {
name: DeprecationMessage(message=x["message"], error=x["error"])
for x in deprecated
for name in x["names"]
}
# Get a list of the deprecated properties, return if there is none
issues = [entry for entry in instance if entry in deprecations]
if not issues:
return
# Process issues
errors = []
for name in issues:
msg = deprecations[name].message.format(name=name)
if deprecations[name].error:
errors.append(msg)
else:
warnings.warn(msg)
if errors:
yield jsonschema.ValidationError("\n".join(errors))
Validator = jsonschema.validators.extend(
jsonschema.Draft7Validator,
{"additionalKeysAreSpecs": _validate_spec, "deprecatedProperties": _deprecated_properties},
)
def _append(string: str) -> bool: def _append(string: str) -> bool:

View File

@ -39,7 +39,7 @@
"load": array_of_strings, "load": array_of_strings,
"suffixes": { "suffixes": {
"type": "object", "type": "object",
"validate_spec": True, "additionalKeysAreSpecs": True,
"additionalProperties": {"type": "string"}, # key "additionalProperties": {"type": "string"}, # key
}, },
"environment": spack.schema.environment.definition, "environment": spack.schema.environment.definition,
@ -64,7 +64,7 @@
tcl_configuration = { tcl_configuration = {
"type": "object", "type": "object",
"default": {}, "default": {},
"validate_spec": True, "additionalKeysAreSpecs": True,
"properties": {**common_props}, "properties": {**common_props},
"additionalProperties": module_file_configuration, "additionalProperties": module_file_configuration,
} }
@ -72,7 +72,7 @@
lmod_configuration = { lmod_configuration = {
"type": "object", "type": "object",
"default": {}, "default": {},
"validate_spec": True, "additionalKeysAreSpecs": True,
"properties": { "properties": {
**common_props, **common_props,
"core_compilers": array_of_strings, "core_compilers": array_of_strings,
@ -80,7 +80,7 @@
"core_specs": array_of_strings, "core_specs": array_of_strings,
"filter_hierarchy_specs": { "filter_hierarchy_specs": {
"type": "object", "type": "object",
"validate_spec": True, "additionalKeysAreSpecs": True,
"additionalProperties": array_of_strings, "additionalProperties": array_of_strings,
}, },
}, },

View File

@ -17,7 +17,7 @@
def validate_spec_schema(): def validate_spec_schema():
return { return {
"type": "object", "type": "object",
"validate_spec": True, "additionalKeysAreSpecs": True,
"patternProperties": {r"\w[\w-]*": {"type": "string"}}, "patternProperties": {r"\w[\w-]*": {"type": "string"}},
} }
@ -34,7 +34,7 @@ def module_suffixes_schema():
"type": "object", "type": "object",
"properties": { "properties": {
"suffixes": { "suffixes": {
"validate_spec": True, "additionalKeysAreSpecs": True,
"patternProperties": {r"\w[\w-]*": {"type": "string"}}, "patternProperties": {r"\w[\w-]*": {"type": "string"}},
} }
}, },