diff --git a/linter/testcases/return-objects/expected-output.txt b/linter/testcases/return-objects/expected-output.txt new file mode 100644 index 0000000..d116466 --- /dev/null +++ b/linter/testcases/return-objects/expected-output.txt @@ -0,0 +1,7 @@ + +/testcases/return-objects/openapi.json + 108:44 error nlgov:always-return-objects The content of all responses should be wrapped in an object paths./incorrect.get.responses[200].content.application/json.schema.type + 113:44 error nlgov:always-return-objects The content of all responses should be wrapped in an object paths./incorrect.get.responses[200].content.application/hal+json.schema.type + 118:44 error nlgov:always-return-objects The content of all responses should be wrapped in an object paths./incorrect.get.responses[200].content.application/xml.schema.type + +✖ 3 problems (3 errors, 0 warnings, 0 infos, 0 hints) diff --git a/linter/testcases/return-objects/openapi.json b/linter/testcases/return-objects/openapi.json new file mode 100644 index 0000000..fb13933 --- /dev/null +++ b/linter/testcases/return-objects/openapi.json @@ -0,0 +1,147 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Baseline", + "description": "Deze OpenAPI specification bevat het minimale om aan alle regels te voldoen.", + "contact": { + "name": "Beheerder", + "url": "https://www.example.com", + "email": "mail@example.com" + }, + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://example.com/api/v1" + } + ], + "security": [ + { + "default": [] + } + ], + "tags": [ + { + "name": "openapi" + } + ], + "paths": { + "/correct": { + "get": { + "tags": [ + "openapi" + ], + "description": "OpenAPI document", + "operationId": "getCorrect", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object" + } + }, + "application/hal+json": { + "schema": { + "type": "object" + } + }, + "application/xml": { + "schema": { + "type": "object" + } + }, + "text/plain": { + "schema": { + "type": "string" + } + }, + "text/html": { + "schema": { + "type": "string" + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + } + }, + "/incorrect": { + "get": { + "tags": [ + "openapi" + ], + "description": "OpenAPI document", + "operationId": "getIncorrect", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array" + } + }, + "application/hal+json": { + "schema": { + "type": "array" + } + }, + "application/xml": { + "schema": { + "type": "array" + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + } + } + }, + "components": { + "schemas": { + }, + "securitySchemes": { + "default": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://test.com", + "scopes": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/media/linter.yaml b/media/linter.yaml index 679a5aa..1723a5d 100644 --- a/media/linter.yaml +++ b/media/linter.yaml @@ -290,3 +290,14 @@ rules: function: pattern functionOptions: match: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ + + #/core/always-return-objects + nlgov:always-return-objects: + severity: error + message: "The content of all responses should be wrapped in an object" + given: $..[responses][*][content][?(@property.toString().match(/(json)|(xml)/))][schema] + then: + field: type + function: pattern + functionOptions: + match: "object" diff --git a/sections/designRules.md b/sections/designRules.md index abddf71..b60bc3b 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -192,6 +192,53 @@ https://api.example.org/v1/vergunningen/d285e05c-6b01-45c3-92d8-5e19a946b66f +
Always return a response with an object
+A JSON or XML response MUST have a top-level object, regardless of request method.
+ For collection resources, the object MUST contain a field (its key MAY be named items) with its value an array of items from that collection.
+
For singular resources, objects are returned as part of RESTful design. +
For collection resources, items can be retrieved all at once, or a subset thereof. + In both cases, wrapping the returned items in an object allows metadata to be attached to the response. + Examples of metadata are structured data to implement linked data concepts, or pagination data for performance purposes. +
If a collection resource directly returns an array of object information from the collection, such metadata can only be provided using HTTP headers. + While that is feasible, HTTP headers are generally more difficult to work with than content from a response body. +
Even if currently no metadata is attached to a response, that could be the case in the future. + Changing the response from an array to an object is a breaking change, hence it is future-proof to always return an object. + +
Hide irrelevant implementation details