Skip to content
Open
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
79 changes: 79 additions & 0 deletions presentation_validator/unique_ids.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import sys
import json
from typing import List, Set, Tuple
from presentation_validator.model import ErrorDetail
from presentation_validator.v3.schemavalidator import create_snippet

IGNORE: Set[str] = {
"target",
"lookAt",
"range",
"structures",
"first",
"last",
"start",
"source",
"body",
"scope",
"thumbnail",
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add thumbnail?

MAX_DEPTH: int = 1000


class MaxDepthExceeded(Exception):
pass


def check(manifest) -> List[ErrorDetail]:
"""
Checks that all values associated with the key 'id' are globally unique.

Args:
manifest: the root JSON dict

Returns:
A generator of ErrorDetail objects for each duplicate ID found.
The generator is empty if no duplicates are found.

Raises:
MaxDepthExceeded: If MAX_DEPTH is exceeded in the search.
"""
seen_ids = []
# stores tuples of (search depth, path, node)
stack: List[Tuple[int, str, dict]] = [(0, "", manifest)]
while stack:
depth, path, node = stack.pop()
if depth > MAX_DEPTH:
raise MaxDepthExceeded(f"Max search depth {MAX_DEPTH} exceeded at {node}")
for key, value in filter(lambda x: x[0] not in IGNORE, node.items()):
if key == "id" or key == "@id":
if value in seen_ids:
yield ErrorDetail(
f"Duplicate id found",
"The id field must be unique",
f"Duplicate id: {value}",
path + "/" + key,
create_snippet(node),
None,
)
seen_ids.append(value)
elif isinstance(value, list):
for i, item in enumerate(value):
# only dicts can contain IDs
if isinstance(item, dict):
stack.append((depth + 1, f"{path}/{key}[{i}]", item))


def main():
# pass in manifest by command line argument
# load json from file
with open(sys.argv[1], "r") as f:
manifest = json.load(f)

errors = check(manifest)
for err in errors:
print(err)


if __name__ == "__main__":
main()
61 changes: 0 additions & 61 deletions presentation_validator/v4/unique_ids.py

This file was deleted.

10 changes: 1 addition & 9 deletions presentation_validator/v4/validation4.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import sys
from presentation_validator.model import ValidationResult
from presentation_validator.v3.schemavalidator import convertValidationError
from presentation_validator.v4.unique_ids import check

from jsonschema import Draft202012Validator
from jsonschema.exceptions import relevance
Expand Down Expand Up @@ -51,18 +50,11 @@ def validate(instance):
# Now create some useful messsages to pass on
for err in errors:
result.errorList.append(convertValidationError(err, errorCount, len(errors)))

errorCount += 1
else:
result.passed = True

duplicate_ids = check(instance)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to remove the check from v4? I think it should be in both v3 and v4

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the callsite to validator.py, a place that at first glance looked version-independent, because the check doesn't rely on any behaviour of any particular version. Did I make a mistake?

if duplicate_ids:
result.passed = False

# Add all of the examples of duplicated ids
result.errorList.extend(duplicate_ids)

return result

def main():
Expand Down
4 changes: 4 additions & 0 deletions presentation_validator/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from presentation_validator.v3 import schemavalidator
from presentation_validator.v4 import validation4
from presentation_validator.enum import IIIFVersion
from presentation_validator import unique_ids

import requests
from urllib.parse import urlparse
Expand Down Expand Up @@ -102,6 +103,9 @@ def check_manifest(
result.error = str(err)
result.url = url

# Check for duplicate ID's in the manifest
result.errorList.extend(unique_ids.check(manifest))

return result

def fetch_manifest(url, accept, version):
Expand Down