Skip to content

Commit

Permalink
[OpenDataServices/cove#895] Better oneOf validation messages for records
Browse files Browse the repository at this point in the history
Make an educated guess at the correct subschema, and use the validation
messages from that.
  • Loading branch information
Bjwebb committed Sep 19, 2019
1 parent 142683c commit e14e4a9
Show file tree
Hide file tree
Showing 7 changed files with 5,211 additions and 1 deletion.
36 changes: 36 additions & 0 deletions libcove/lib/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,42 @@ def oneOf_draft4(validator, oneOf, instance, schema):
validator='required',
)
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":
# If instance if 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 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:
yield err
return
# Assume instance is an array of Embedded 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:
yield err
return
else:
yield 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."
)
break

all_errors.extend(errs)
else:
if validStatementTypes:
Expand Down
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
[flake8]
max-line-length=119
# extend-ignore uses flake8's default ignore list, and adds to it
extend-ignore = E501
225 changes: 225 additions & 0 deletions tests/lib/fixtures/common/record-package-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
{
"id": "https://standard.open-contracting.org/schema/1__1__4/record-package-schema.json",
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Schema for an Open Contracting Record package",
"description": "The record package contains a list of records along with some publishing metadata. The records pull together all the releases under a single Open Contracting ID and compile them into the latest version of the information along with the history of any data changes.",
"type": "object",
"properties": {
"uri": {
"title": "Package identifier",
"description": "The URI of this package that identifies it uniquely in the world.",
"type": "string",
"format": "uri"
},
"version": {
"title": "OCDS schema version",
"description": "The version of the OCDS schema used in this package, expressed as major.minor For example: 1.0 or 1.1",
"type": "string",
"pattern": "^(\\d+\\.)(\\d+)$"
},
"extensions": {
"title": "OCDS extensions",
"description": "An array of OCDS extensions used in this package, in which each array item is the URL of an extension.json file.",
"type": "array",
"items": {
"type": "string",
"format": "uri"
}
},
"publisher": {
"title": "Publisher",
"description": "Information to uniquely identify the publisher of this package.",
"type": "object",
"properties": {
"name": {
"title": "Name",
"description": "The name of the organization or department responsible for publishing this data.",
"type": "string"
},
"scheme": {
"title": "Scheme",
"description": "The scheme that holds the unique identifiers used to identify the item being identified.",
"type": [
"string",
"null"
]
},
"uid": {
"title": "uid",
"description": "The unique ID for this entity under the given ID scheme.",
"type": [
"string",
"null"
]
},
"uri": {
"title": "URI",
"description": "A URI to identify the publisher.",
"type": [
"string",
"null"
],
"format": "uri"
}
},
"required": [
"name"
]
},
"license": {
"title": "License",
"description": "A link to the license that applies to the data in this package. A Public Domain Dedication or [Open Definition Conformant](http://opendefinition.org/licenses/) license is recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions.",
"type": [
"string",
"null"
],
"format": "uri"
},
"publicationPolicy": {
"title": "Publication policy",
"description": "A link to a document describing the publishers publication policy.",
"type": [
"string",
"null"
],
"format": "uri"
},
"publishedDate": {
"title": "Published date",
"description": "The date that this package was published. If this package is generated 'on demand', this date should reflect the date of the last change to the underlying contents of the package.",
"type": "string",
"format": "date-time"
},
"packages": {
"title": "Packages",
"description": "A list of URIs of all the release packages that were used to create this record package.",
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"format": "uri"
},
"uniqueItems": true
},
"records": {
"title": "Records",
"description": "The records for this data package.",
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/record"
},
"uniqueItems": true
}
},
"required": [
"uri",
"publisher",
"publishedDate",
"records",
"version"
],
"definitions": {
"record": {
"title": "Record",
"description": "An OCDS record must provide a list of all the existing OCDS releases relating to a single contracting process and should provide a compiled release containing the current state of all fields in the OCDS schema. An OCDS record may also provide a versioned history of all changes to the data in the compiled release.",
"type": "object",
"properties": {
"ocid": {
"title": "Open Contracting ID",
"description": "A unique identifier that identifies the unique Open Contracting Process. For more information see: https://standard.open-contracting.org/1.1/en/getting_started/contracting_process/",
"type": "string"
},
"releases": {
"title": "Releases",
"description": "An array of linking identifiers or releases",
"oneOf": [
{
"title": "Linked releases",
"description": "A list of objects that identify the releases associated with this Open Contracting ID. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).",
"type": "array",
"items": {
"description": "Information to uniquely identify the release.",
"type": "object",
"properties": {
"url": {
"title": "Release URL",
"description": "The URL of the release which contains the URL of the package with the release `id` appended using a fragment identifier e.g. https://standard.open-contracting.org/1.1/en/examples/tender.json#ocds-213czf-000-00001",
"type": [
"string",
"null"
],
"format": "uri"
},
"date": {
"title": "Release Date",
"description": "The date of the release. It should match the value of the `date` field of the release. This is used to sort the releases in the list in chronological order.",
"type": "string",
"format": "date-time"
},
"tag": {
"title": "Release Tag",
"description": "The tags of the release. It should match the value of the `tag` field of the release. This provides additional context when reviewing a record to see what types of releases are included for this ocid.",
"type": "array",
"items": {
"type": "string",
"enum": [
"planning",
"planningUpdate",
"tender",
"tenderAmendment",
"tenderUpdate",
"tenderCancellation",
"award",
"awardUpdate",
"awardCancellation",
"contract",
"contractUpdate",
"contractAmendment",
"implementation",
"implementationUpdate",
"contractTermination",
"compiled"
]
},
"codelist": "releaseTag.csv",
"openCodelist": false,
"minItems": 1
}
},
"required": [
"url",
"date"
]
},
"minItems": 1
},
{
"title": "Embedded releases",
"description": "A list of releases, with all the data. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).",
"type": "array",
"items": {
"$ref": "https://standard.open-contracting.org/schema/1__1__4/release-schema.json"
},
"minItems": 1
}
]
},
"compiledRelease": {
"title": "Compiled release",
"description": "This is the latest version of all the contracting data, it has the same schema as an open contracting release.",
"$ref": "https://standard.open-contracting.org/schema/1__1__4/release-schema.json"
},
"versionedRelease": {
"title": "Versioned release",
"description": "This contains the history of the data in the compiledRecord. With all versions of the information and the release they came from.",
"$ref": "https://standard.open-contracting.org/schema/1__1__4/versioned-release-validation-schema.json"
}
},
"required": [
"ocid",
"releases"
]
}
}
}
58 changes: 58 additions & 0 deletions tests/lib/fixtures/common/records_invalid_releases.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"uri": "http://example.org/invalid_record.json",
"version": "1.1",
"publishedDate": "2019-09-18T17:56:21.078Z",
"publisher": {
"name": "A Publisher Name"
},
"records": [
{
"ocid": "EXAMPLE-1",
"releases": []
},
{
"ocid": "EXAMPLE-2",
"releases": [
{}
]
},
{
"ocid": "EXAMPLE-3",
"releases": [
{"id": "EXAMPLE-3-1"}
]
},
{
"ocid": "EXAMPLE-4",
"releases": [
{"url": "http://example.org/releases.json#EXAMPLE-4-1"}
]
},
{
"ocid": "EXAMPLE-5",
"releases": [
{"id": "EXAMPLE-5-1"},
{}
]
},
{
"ocid": "EXAMPLE-6",
"releases": [
{"id": "EXAMPLE-5-1"},
{"url": "http://example.org/releases.json#EXAMPLE-4-1"}
]
},
{
"ocid": "EXAMPLE-97",
"releases": "a string"
},
{
"ocid": "EXAMPLE-98",
"releases": null
},
{
"ocid": "EXAMPLE-99",
"releases": {}
}
]
}
Loading

0 comments on commit e14e4a9

Please sign in to comment.