diff --git a/example/responseWithVariants.json b/example/responseWithVariants.json
new file mode 100644
index 0000000..6a2b95d
--- /dev/null
+++ b/example/responseWithVariants.json
@@ -0,0 +1,182 @@
+{
+ "request": {
+ "query": "Blubergurken",
+ "first": 0,
+ "count": 24,
+ "serviceId": "F53ABAB42D7931BE13532AFCA1A95CCE",
+ "usergroup": "foo",
+ "userId": "cd7984ec-e0c5-4bfb-a925-d607668153cd",
+ "order": {
+ "field": "salesfrequency",
+ "relevanceBased": true,
+ "direction": "DESC"
+ }
+ },
+ "result": {
+ "metadata": {
+ "effectiveQuery": "Blubbergurken",
+ "totalResults": 1337,
+ "requestId": "9cd42225-90d0-4858-bcb6-b05f33d8ec5e",
+ "searchConcept": "Seeds",
+ "currencySymbol": "€",
+ "landingpage": {
+ "name": "New arrivals",
+ "url": "https://example.org/new_stuff.html"
+ },
+ "promotion": {
+ "name": "Blubbergurken Brand",
+ "url": "https://example.org/top_brands/Blubbergurken_International_Inc.html",
+ "imageUrl": "https://example.org/top_brands/blubbergurken_international.png"
+ }
+ },
+ "variant": {
+ "name": "sdym",
+ "correctedQuery": "Blubbergurken"
+ },
+ "items": [
+ {
+ "id": "123ab",
+ "url": "https://example.org/product.html",
+ "imageUrl": "https://example.org/product.png",
+ "name": "Blubbergurken Seeds",
+ "highlightedName": "Blubbergurken Seeds",
+ "price": 13.37,
+ "ordernumbers": ["0012BLUB-42"],
+ "matchingOrdernumber": "34567",
+ "score": 4.667,
+ "summary": "These are some very nice seeds.",
+ "properties": {
+ "overriddenPrice": "15.00",
+ "taxRate": "20"
+ },
+ "productPlacement": "Seeds spring 2020",
+ "pushRules": [
+ "Seeds",
+ "Cucumbers"
+ ],
+ "attributes": {
+ "cat": [
+ "Gardening"
+ ],
+ "vendor": [
+ "Blubbergurken International Inc."
+ ]
+ },
+ "variants": [
+ {
+ "id": "123ab-A",
+ "url": "https://example.org/product-a.html",
+ "imageUrl": "https://example.org/product-a.png",
+ "name": "Blubbergurken Seeds - Class A",
+ "price": 15.00,
+ "ordernumbers": ["0012BLUB-42-A"],
+ "matchingOrdernumber": "",
+ "score": 4.667,
+ "summary": "These are some very nice seeds.",
+ "properties": {
+ "overriddenPrice": "15.00",
+ "taxRate": "20"
+ },
+ "attributes": {
+ "cat": [
+ "Gardening"
+ ],
+ "vendor": [
+ "Blubbergurken International Inc."
+ ]
+ }
+ },
+ {
+ "id": "123ab-A",
+ "url": null,
+ "imageUrl": null,
+ "name": null,
+ "price": null,
+ "ordernumbers": ["0012BLUB-42-A"],
+ "matchingOrdernumber": "",
+ "score": 0,
+ "summary": null,
+ "properties": {},
+ "attributes": {}
+ }
+ ]
+ }
+ ],
+ "filters": {
+ "main": [
+ {
+ "name": "cat",
+ "displayName": "Category",
+ "type": "select",
+ "selectMode": "single",
+ "values": [
+ {
+ "displayName": "Spring",
+ "value": "Gardening_Spring",
+ "weight": 1.2,
+ "frequency": 13
+ }
+ ],
+ "pinnedFilterValueCount": 6
+ },
+ {
+ "type": "range-slider",
+ "totalRange": {
+ "min": 2.37,
+ "max": 10106.09
+ },
+ "selectedRange": {
+ "min": 2.37,
+ "max": 10106.09
+ },
+ "stepSize": 0.1,
+ "unit": "€",
+ "name": "price",
+ "displayName": "Preis",
+ "selectMode": "single",
+ "values": [
+ {
+ "value": {
+ "min": 2.37,
+ "max": 30.75
+ },
+ "weight": 0.3948,
+ "frequency": null
+ }
+ ]
+ },
+ {
+ "name": "vendor",
+ "displayName": "Brand",
+ "type": "select",
+ "selectMode": "multiple",
+ "noAvailableFiltersText": "Sorry, no more filters for you!",
+ "values": [
+ {
+ "value": "Blubbergurken International Inc.",
+ "weight": 0.8,
+ "frequency": 5,
+ "selected": true,
+ "frequencyType" : "additive"
+ }
+ ]
+ }
+ ],
+ "other": [
+ {
+ "name": "color",
+ "displayName": "Color",
+ "type": "color",
+ "selectMode": "multiple",
+ "cssClass": "my-colors",
+ "values": [
+ {
+ "value": "Green",
+ "color": "#00FF00"
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/resources/schema.json b/resources/schema.json
index 8ad0871..332e80b 100644
--- a/resources/schema.json
+++ b/resources/schema.json
@@ -9,7 +9,7 @@
"properties": {
"value": {
"description": "The filter value, as selected or suitable for visualization.",
- "type": ["string", "object"],
+ "type": ["string", "object", "number"],
"minLength": 1
},
"min": {
@@ -176,6 +176,161 @@
"displayName",
"type"
]
+ },
+ "requiredUrl": {
+ "type": "string",
+ "pattern": "^(https?://.*)?$"
+ },
+ "nullableUrl": {
+ "type": ["string", "null"],
+ "pattern": "^https?://.*$"
+ },
+ "baseItem": {
+ "description": "Properties shared by parent- and child items.",
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "Item ID, as exported.",
+ "type": "string",
+ "minLength": 1
+ },
+ "ordernumbers": {
+ "description": "The item's ordernumbers. Currently, only the first exported one is available.",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "matchingOrdernumber": {
+ "type": "string",
+ "minLength": 0
+ },
+ "score": {
+ "description": "Search score.",
+ "type": "number",
+ "min": 0
+ },
+ "properties": {
+ "description": "Non-searchable value exported as properties. Includes additional images, if applicable. The desired values have to be requested with the 'properties[]' parameter.",
+ "type": "object",
+ "properties": {
+ ".*": {
+ "type": "string"
+ }
+ }
+ },
+ "attributes": {
+ "type": "object",
+ "description": "Attribute values that apply to an item, if the attribute was requested via outputAttrib.",
+ "patternProperties": {
+ "^.*$": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ },
+ "minLength": 0
+ }
+ }
+ }
+ },
+ "additionalProperties": true,
+ "required": [
+ "id",
+ "score",
+ "ordernumbers",
+ "matchingOrdernumber",
+ "properties",
+ "attributes"
+ ]
+ },
+ "parentItem": {
+ "description": "Top level item. In case the service supports variants, this may contain variants. Without variant support, this is just a regular item.",
+ "allOf": [
+ {"$ref": "#/definitions/baseItem"},
+ {
+ "properties": {
+ "name": {
+ "description": "Name of the item without any query-based highlighting.",
+ "type": "string",
+ "minLength": 0
+ },
+ "price": {
+ "description": "The item's price.",
+ "type": "number"
+ },
+ "summary": {
+ "description": "Exported short summary.",
+ "type": "string",
+ "minLength": 0
+ },
+ "url": {
+ "description": "Detail page URL.",
+ "$ref": "#/definitions/requiredUrl"
+ },
+ "imageUrl": {
+ "description": "Primary image URL. Additionally exported images can be accessed via the item's properties.",
+ "$ref": "#/definitions/requiredUrl"
+ },
+ "highlightedName": {
+ "description": "Name of the item with portions of it highlighted if matching the query. The matching part is wrapped in a tag.",
+ "type": "string",
+ "minLength": 0
+ },
+ "productPlacement": {
+ "type": ["string", "null"],
+ "minLength": 1,
+ "description": "In case a Product Placement matches the product, this is its name."
+ },
+ "pushRules": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ },
+ "description": "In one or more Push Rules match a product, these are the names."
+ },
+ "variants": {
+ "type": "array",
+ "items": {"$ref": "#/definitions/variantItem"}
+ }
+ },
+ "required": ["name", "price", "summary", "url", "imageUrl", "highlightedName", "productPlacement", "pushRules"]
+ }
+ ]
+ },
+ "variantItem": {
+ "description": "Variant item of a parent in case variants are supported by the service.",
+ "allOf": [
+ {"$ref": "#/definitions/baseItem"},
+ {
+ "properties": {
+ "name": {
+ "description": "Name of the item without any query-based highlighting.",
+ "type": ["string", "null"],
+ "minLength": 0
+ },
+ "price": {
+ "description": "The item's price.",
+ "type": ["number", "null"]
+ },
+ "summary": {
+ "description": "Exported short summary.",
+ "type": ["string", "null"],
+ "minLength": 0
+ },
+ "url": {
+ "description": "Detail page URL.",
+ "$ref": "#/definitions/nullableUrl"
+ },
+ "imageUrl": {
+ "description": "Primary image URL. Additionally exported images can be accessed via the item's properties.",
+ "$ref": "#/definitions/nullableUrl"
+ }
+ },
+ "required": ["name", "price", "summary", "url", "imageUrl"]
+ }
+ ]
}
},
"type": "object",
@@ -380,114 +535,7 @@
"description": "The matching items, constraint by the pagination parameters. The values being shown respect the specified usergroup, if applicable.",
"type": "array",
"minLength": 0,
- "items": {
- "type": "object",
- "properties": {
- "id": {
- "description": "Item ID, as exported.",
- "type": "string",
- "minLength": 1
- },
- "url": {
- "description": "Detail page URL.",
- "type": "string",
- "pattern": "^https?://.*$"
- },
- "imageUrl": {
- "description": "Primary image URL. Additionally exported images can be accessed via the item's properties.",
- "type": "string",
- "pattern": "^https?://.*$"
- },
- "name": {
- "description": "Name of the item without any query-based highlighting.",
- "type": "string",
- "minLength": 0
- },
- "highlightedName": {
- "description": "Name of the item with portions of it highlighted if matching the query. The matching part is wrapped in a tag.",
- "type": "string",
- "minLength": 0
- },
- "price": {
- "description": "The item's price.",
- "type": "number"
- },
- "ordernumbers": {
- "description": "The item's ordernumbers. Currently, only the first exported one is available.",
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "matchingOrdernumber": {
- "type": "string",
- "minLength": 0
- },
- "score": {
- "description": "Search score.",
- "type": "number",
- "min": 0
- },
- "summary": {
- "description": "Exported short summary.",
- "type": "string",
- "minLength": 0
- },
- "properties": {
- "description": "Non-searchable value exported as properties. Includes additional images, if applicable. The desired values have to be requested with the 'properties[]' parameter.",
- "type": "object",
- "properties": {
- ".*": {
- "type": "string"
- }
- }
- },
- "productPlacement": {
- "type": ["string", "null"],
- "minLength": 1,
- "description": "In case a Product Placement matches the product, this is its name."
- },
- "pushRules": {
- "type": "array",
- "items": {
- "type": "string",
- "minLength": 1
- },
- "description": "In one or more Push Rules match a product, these are the names."
- },
- "attributes": {
- "type": "object",
- "description": "Attribute values that apply to an item, if the attribute was requested via outputAttrib.",
- "patternProperties": {
- "^.*$": {
- "type": "array",
- "items": {
- "type": "string",
- "minLength": 1
- },
- "minLength": 0
- }
- }
- }
- },
- "additionalProperties": false,
- "required": [
- "id",
- "score",
- "url",
- "name",
- "highlightedName",
- "ordernumbers",
- "matchingOrdernumber",
- "summary",
- "price",
- "properties",
- "productPlacement",
- "pushRules",
- "attributes",
- "imageUrl"
- ]
- }
+ "items": {"$ref": "#/definitions/parentItem"}
},
"filters": {
"description": "Filters available based on the query, and as configured in the filter configuration. Does not include inactive filters.",
diff --git a/test/validate.js b/test/validate.js
index 3e29c72..fa5d49c 100644
--- a/test/validate.js
+++ b/test/validate.js
@@ -1,8 +1,10 @@
const Validator = require('jsonschema').Validator;
-const fs = require('fs').promises;
+const fs = require('fs');
+const exampleFolderPath = __dirname + '/../example/';
+const schemaPath = __dirname + '/../resources/schema.json';
async function parseJsonWithErrorHandling(path) {
- const rawBuffer = await fs.readFile(path);
+ const rawBuffer = await fs.promises.readFile(path);
try {
return JSON.parse(rawBuffer.toString('utf8'));
@@ -12,16 +14,16 @@ async function parseJsonWithErrorHandling(path) {
}
}
-async function main() {
+async function validateFile(fileName) {
const validator = new Validator();
- const instance = await parseJsonWithErrorHandling(__dirname + '/../example/response.json');
- const schema = await parseJsonWithErrorHandling(__dirname + '/../resources/schema.json');
+ const instance = await parseJsonWithErrorHandling(exampleFolderPath + fileName);
+ const schema = await parseJsonWithErrorHandling(schemaPath);
const result = validator.validate(instance, schema);
if (!result.valid) {
- console.error('Schema or example are not valid. Errors:');
+ console.error(fileName + ': Schema or example are not valid. Errors:');
result.errors.forEach((error) => {
console.error(error.property + ' ' + error.message);
@@ -29,8 +31,14 @@ async function main() {
process.exit(1);
} else {
- console.log('Schema and example are valid.');
+ console.log(fileName + ': Schema and example are valid.');
}
}
+function main() {
+ fs.readdir(exampleFolderPath, (err, files) => {
+ files.forEach(fileName => validateFile(fileName));
+ });
+}
+
main();