Skip to content

Commit

Permalink
schema: Another take on checking additionalProperties/unevaluatedProp…
Browse files Browse the repository at this point in the history
…erties

Complete schemas need to have additionalProperties or
unevaluatedProperties constraint in order to prevent undefined
properties. Attempts to check this with meta-schemas resulted in corner
cases which couldn't be fixed or required redundant
unevaluatedProperties. The problem is we need to know if a referenced
schema has the constraint already or not. So let's solve this with code
to look into $ref's to see if the ref constrains the properties or not.

Signed-off-by: Rob Herring <robh@kernel.org>
  • Loading branch information
robherring committed May 9, 2024
1 parent 8733c90 commit a5bbb3e
Showing 1 changed file with 36 additions and 6 deletions.
42 changes: 36 additions & 6 deletions dtschema/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ def get_line_col(tree, path, obj=None):
return obj.lc.key(path[-1])
return -1, -1

def _is_node_schema(schema):
return isinstance(schema, dict) and \
(('type' in schema and schema['type'] == 'object') or
schema.keys() & {'properties', 'patternProperties'})


def _schema_allows_no_undefined_props(schema):
return not schema.get("additionalProperties", True) or \
not schema.get("unevaluatedProperties", True)

class DTSchema(dict):
DtValidator = jsonschema.validators.extend(
Expand Down Expand Up @@ -143,15 +152,36 @@ def fixup(self):
dtschema.fixups.fixup_schema(processed_schema)
return processed_schema

def _check_schema_refs(self, schema):
if isinstance(schema, dict) and '$ref' in schema:
self.resolver.resolve(schema['$ref'])
elif isinstance(schema, dict):
def _check_schema_refs(self, schema, parent=None, is_common=False, has_constraint=False):
if not parent:
is_common = not _schema_allows_no_undefined_props(schema)
if isinstance(schema, dict):
if parent in {'if', 'select', 'definitions', '$defs', 'then',
'else', 'dependencies', 'dependentSchemas'}:
return

if _is_node_schema(schema):
has_constraint = _schema_allows_no_undefined_props(schema)

if not is_common and _is_node_schema(schema) and \
(schema.keys() & {'properties', 'patternProperties', '$ref'}):
ref_has_constraint = False
if '$ref' in schema:
url, ref_sch = self.resolver.resolve(schema['$ref'])

ref_has_constraint = _schema_allows_no_undefined_props(ref_sch)
if not ref_has_constraint and \
not (has_constraint or (schema.keys() & {'additionalProperties', 'unevaluatedProperties'})):
print(f"{self.filename}: {parent}: Missing additionalProperties/unevaluatedProperties constraint\n",
file=sys.stderr)

for k, v in schema.items():
self._check_schema_refs(v)
self._check_schema_refs(v, parent=k, is_common=is_common,
has_constraint=has_constraint)
elif isinstance(schema, (list, tuple)):
for i in range(len(schema)):
self._check_schema_refs(schema[i])
self._check_schema_refs(schema[i], parent=parent, is_common=is_common,
has_constraint=has_constraint)

def check_schema_refs(self):
id = self['$id'].rstrip('#')
Expand Down

0 comments on commit a5bbb3e

Please sign in to comment.