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();