def unique_ids(validator, ui, instance, schema, id_name="id"): if ui and validator.is_type(instance, "array"): non_unique_ids = set() all_ids = set() for item in instance: try: item_id = item.get(id_name) except AttributeError: # if item is not a dict item_id = None if (item_id and not isinstance(item_id, list) and not isinstance(item_id, dict)): if item_id in all_ids: non_unique_ids.add(item_id) all_ids.add(item_id) else: if not uniq(instance): msg = "Array has non-unique elements" err = ValidationError(msg, instance=instance) err.error_id = "uniqueItems_no_ids" yield err return for non_unique_id in sorted(non_unique_ids): msg = "Non-unique {} values".format(id_name) err = ValidationError(msg, instance=non_unique_id) err.error_id = "uniqueItems_with_{}".format(id_name) yield err
def oneOf_draft4(validator, oneOf, instance, schema): """ oneOf_draft4 validator from https://github.com/Julian/jsonschema/blob/d16713a4296663f3d62c50b9f9a2893cb380b7af/jsonschema/_validators.py#L337 Modified to: - sort the instance JSON, so we get a reproducible output that we can can test more easily - Yield all the individual errors for linked or embedded releases within a record. - Return more information on the ValidationError object, to allow us to replace the translation with a message in cove-ocds """ subschemas = enumerate(oneOf) all_errors = [] for index, subschema in subschemas: errs = list(validator.descend(instance, subschema, schema_path=index)) if not errs: first_valid = subschema break # We check the title, because we don't have access to the field name, # as it lives in the parent. # It will not match the releases array in a release package, because # there is no oneOf. if (schema.get("title") == "Releases" or schema.get("description") == "An array of linking identifiers or releases"): # If instance is not a list, or is a list of zero length, then # validating against either subschema will work. # Assume instance is an array of Linked releases, if there are no # "id"s in any of the releases. if type(instance) is not list or all("id" not in release for release in instance): if "properties" in subschema.get( "items", {}) and "id" not in subschema["items"]["properties"]: for err in errs: err.assumption = "linked_releases" yield err return # Assume instance is an array of Embedded releases, if there is an # "id" in each of the releases elif all("id" in release for release in instance): if "id" in subschema.get("items", {}).get( "properties", {}) or subschema.get("items", {}).get( "$ref", "").endswith("release-schema.json"): for err in errs: err.assumption = "embedded_releases" yield err return else: err = ValidationError( "This array should contain either entirely embedded releases or " "linked releases. Embedded releases contain an 'id' whereas linked " "releases do not. Your releases contain a mixture.") err.error_id = "releases_both_embedded_and_linked" yield err return all_errors.extend(errs) else: err = ValidationError( f"{json.dumps(instance, sort_keys=True, default=decimal_default)} " "is not valid under any of the given schemas", context=all_errors, ) err.error_id = "oneOf_any" yield err more_valid = [s for i, s in subschemas if validator.is_valid(instance, s)] if more_valid: more_valid.append(first_valid) reprs = ", ".join(repr(schema) for schema in more_valid) err = ValidationError(f"{instance!r} is valid under each of {reprs}") err.error_id = "oneOf_each" err.reprs = reprs yield err