diff --git a/linter/spectral.yml b/linter/spectral.yml index da7089a..dd8570f 100644 --- a/linter/spectral.yml +++ b/linter/spectral.yml @@ -183,8 +183,9 @@ rules: functionOptions: match: ^https://.* + #/core/error-handling/problem-details nlgov:use-problem-schema: - severity: warn + severity: error message: "The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457." given: $..[responses][?(@property && @property.match(/(4|5)\d\d/))].content then: @@ -195,6 +196,77 @@ rules: - required: ["application/problem+json"] - required: ["application/problem+xml"] + nlgov:problem-schema-members: + severity: error + message: "{{error}}. These fields are required: status, title and detail." + given: $..[responses][?(@property && @property.match(/(4|5)\d\d/))].content[?(@property=="application/problem+json" || @property=="application/problem+xml")]..schema + then: + function: schema + functionOptions: + schema: + type: object + properties: + properties: + type: object + required: + - status + - title + - detail + + #/core/error-handling/invalid-input + nlgov:problem-invalid-input: + severity: error + message: "GET endpoints that have parameters and all other endpoints must be able to return a 400 response" + given: + - $.paths..[?( @property.match(/get/) && @.parameters && @.parameters.length > 0 )] + - $.paths..[?( @property.match(/(put)|(post)|(delete)|(patch)/))] + then: + function: schema + functionOptions: + schema: + type: object + properties: + responses: + type: object + required: + - "400" + + #/core/error-handling/bad-request + nlgov:problem-schema-members-bad-request: + severity: error + given: $..[responses][?(@property && @property.match(400))].content[?(@property=="application/problem+json" || @property=="application/problem+xml")]..schema.properties + then: + function: schema + functionOptions: + schema: + type: object + properties: + errors: + type: object + properties: + properties: + type: object + properties: + in: {} + detail: {} + location: + type: object + properties: + properties: + type: object + properties: + pointer: {} + name: {} + index: {} + additionalProperties: false + code: {} + required: + - in + - detail + additionalProperties: false + required: + - errors + nlgov:property-casing: severity: warn given: diff --git a/linter/testcases/cor-api/expected-output.txt b/linter/testcases/cor-api/expected-output.txt index e88c108..787dda0 100644 --- a/linter/testcases/cor-api/expected-output.txt +++ b/linter/testcases/cor-api/expected-output.txt @@ -1,28 +1,28 @@ /testcases/cor-api/openapi.json - 70:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./heartbeat.get.responses[429].content - 80:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./heartbeat.get.responses[503].content - 181:29 error nlgov:paths-kebab-case /laatsteWijziging is not kebab-case. paths./laatsteWijziging - 211:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./laatsteWijziging.get.responses[400].content - 221:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./laatsteWijziging.get.responses[404].content - 231:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./laatsteWijziging.get.responses[405].content - 241:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./laatsteWijziging.get.responses[406].content - 251:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./laatsteWijziging.get.responses[429].content - 261:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./laatsteWijziging.get.responses[500].content - 271:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./laatsteWijziging.get.responses[503].content - 506:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties.get.responses[400].content - 516:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties.get.responses[404].content - 526:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties.get.responses[405].content - 536:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties.get.responses[406].content - 546:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties.get.responses[429].content - 556:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties.get.responses[500].content - 566:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties.get.responses[503].content - 684:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties/{oin}.get.responses[400].content - 694:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties/{oin}.get.responses[404].content - 704:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties/{oin}.get.responses[405].content - 714:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties/{oin}.get.responses[406].content - 724:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties/{oin}.get.responses[429].content - 734:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties/{oin}.get.responses[500].content - 744:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties/{oin}.get.responses[503].content + 70:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./heartbeat.get.responses[429].content + 80:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./heartbeat.get.responses[503].content + 181:29 error nlgov:paths-kebab-case /laatsteWijziging is not kebab-case. paths./laatsteWijziging + 211:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./laatsteWijziging.get.responses[400].content + 221:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./laatsteWijziging.get.responses[404].content + 231:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./laatsteWijziging.get.responses[405].content + 241:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./laatsteWijziging.get.responses[406].content + 251:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./laatsteWijziging.get.responses[429].content + 261:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./laatsteWijziging.get.responses[500].content + 271:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./laatsteWijziging.get.responses[503].content + 506:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties.get.responses[400].content + 516:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties.get.responses[404].content + 526:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties.get.responses[405].content + 536:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties.get.responses[406].content + 546:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties.get.responses[429].content + 556:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties.get.responses[500].content + 566:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties.get.responses[503].content + 684:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties/{oin}.get.responses[400].content + 694:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties/{oin}.get.responses[404].content + 704:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties/{oin}.get.responses[405].content + 714:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties/{oin}.get.responses[406].content + 724:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties/{oin}.get.responses[429].content + 734:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties/{oin}.get.responses[500].content + 744:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./organisaties/{oin}.get.responses[503].content -✖ 24 problems (1 error, 23 warnings, 0 infos, 0 hints) +✖ 24 problems (24 errors, 0 warnings, 0 infos, 0 hints) diff --git a/linter/testcases/error-type-bad-invalid-input/expected-output.txt b/linter/testcases/error-type-bad-invalid-input/expected-output.txt new file mode 100644 index 0000000..249d395 --- /dev/null +++ b/linter/testcases/error-type-bad-invalid-input/expected-output.txt @@ -0,0 +1,7 @@ + +/testcases/error-type-bad-invalid-input/openapi.json + 119:29 error nlgov:problem-invalid-input GET endpoints that have parameters and all other endpoints must be able to return a 400 response paths./invalid-response-vereist.get.responses + 157:29 error nlgov:problem-invalid-input GET endpoints that have parameters and all other endpoints must be able to return a 400 response paths./invalid-response-vereist.put.responses + 195:29 error nlgov:problem-invalid-input GET endpoints that have parameters and all other endpoints must be able to return a 400 response paths./invalid-response-vereist.post.responses + +✖ 3 problems (3 errors, 0 warnings, 0 infos, 0 hints) diff --git a/linter/testcases/error-type-bad-invalid-input/openapi.json b/linter/testcases/error-type-bad-invalid-input/openapi.json new file mode 100644 index 0000000..c8ab926 --- /dev/null +++ b/linter/testcases/error-type-bad-invalid-input/openapi.json @@ -0,0 +1,232 @@ +{ + "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" + }, + { + "name": "resource" + } + ], + "paths": { + "/openapi.json": { + "get": { + "tags": [ + "openapi" + ], + "description": "OpenAPI document", + "operationId": "getOpenapiJSON", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + }, + "access-control-allow-origin": { + "description": "Alle origins mogen bij deze resource", + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "OK", + "content": { + "application/problem+json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "integer" }, + "title": { "type": "string" }, + "detail": { "type": "string" }, + "errors": { + "type": "object", + "properties": { + "in": { "type": "string" }, + "location": { + "type": "object", + "properties": { + "pointer": { "type": "string" }, + "name": { "type": "string" }, + "index": { "type": "integer" } + } + }, + "code": { "type": "string" }, + "detail": { "type": "string" } + } + } + }, + "required": ["status", "title", "detail", "errors"], + "additionalProperties": false + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + } + }, + "/invalid-response-vereist": { + "get": { + "tags": [ + "resource" + ], + "description": "Resource", + "operationId": "getPropertiesCorrect", + "parameters": [ + { + "name": "expand", + "in": "query", + "description": "Schakelaar om details van gekoppelde organisaties (subOIN of OINhouder) op te vragen (default false = geen details)", + "schema": { + "type": "boolean" + }, + "style": "form", + "explode": true + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + }, + "put": { + "tags": [ + "resource" + ], + "description": "Resource", + "operationId": "putPropertiesCorrect", + "parameters": [ + { + "name": "expand", + "in": "query", + "description": "Schakelaar om details van gekoppelde organisaties (subOIN of OINhouder) op te vragen (default false = geen details)", + "schema": { + "type": "boolean" + }, + "style": "form", + "explode": true + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + }, + "post": { + "tags": [ + "resource" + ], + "description": "Resource", + "operationId": "postPropertiesCorrect", + "parameters": [ + { + "name": "expand", + "in": "query", + "description": "Schakelaar om details van gekoppelde organisaties (subOIN of OINhouder) op te vragen (default false = geen details)", + "schema": { + "type": "boolean" + }, + "style": "form", + "explode": true + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + } + } + }, + "components": { + "schemas": { + }, + "securitySchemes": { + "default": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://test.com", + "scopes": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/linter/testcases/error-type-bad-request/expected-output.txt b/linter/testcases/error-type-bad-request/expected-output.txt new file mode 100644 index 0000000..7dbc0bd --- /dev/null +++ b/linter/testcases/error-type-bad-request/expected-output.txt @@ -0,0 +1,6 @@ + +/testcases/error-type-bad-request/openapi.json + 133:58 error nlgov:problem-schema-members-bad-request Property "extra" is not expected to be here paths./properties-correct.get.responses[400].content.application/problem+json.schema.properties.errors.properties + 254:66 error nlgov:problem-schema-members-bad-request Property "pointer2" is not expected to be here paths./properties-location-verkeerd-format.get.responses[400].content.application/problem+json.schema.properties.errors.properties.location.properties + +✖ 2 problems (2 errors, 0 warnings, 0 infos, 0 hints) diff --git a/linter/testcases/error-type-bad-request/openapi.json b/linter/testcases/error-type-bad-request/openapi.json new file mode 100644 index 0000000..1ee4a30 --- /dev/null +++ b/linter/testcases/error-type-bad-request/openapi.json @@ -0,0 +1,295 @@ +{ + "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" + }, + { + "name": "resource" + } + ], + "paths": { + "/openapi.json": { + "get": { + "tags": [ + "openapi" + ], + "description": "OpenAPI document", + "operationId": "getOpenapiJSON", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + }, + "access-control-allow-origin": { + "description": "Alle origins mogen bij deze resource", + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "OK", + "content": { + "application/problem+json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "integer" }, + "title": { "type": "string" }, + "detail": { "type": "string" }, + "errors": { + "type": "object", + "properties": { + "in": { "type": "string" }, + "location": { + "type": "object", + "properties": { + "pointer": { "type": "string" }, + "name": { "type": "string" }, + "index": { "type": "integer" } + } + }, + "code": { "type": "string" }, + "detail": { "type": "string" } + } + } + }, + "required": ["status", "title", "detail", "errors"], + "additionalProperties": false + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + } + }, + "/properties-correct": { + "get": { + "tags": [ + "resource" + ], + "description": "Resource", + "operationId": "getPropertiesCorrect", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "OK", + "content": { + "application/problem+json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "integer" }, + "title": { "type": "string" }, + "detail": { "type": "string" }, + "errors": { + "type": "object", + "properties": { + "in": { "type": "string" }, + "location": { + "type": "object", + "properties": { + "pointer": { "type": "string" }, + "name": { "type": "string" }, + "index": { "type": "integer" } + } + }, + "code": { "type": "string" }, + "detail": { "type": "string" }, + "extra": { "type": "string" } + } + } + }, + "required": ["status", "title", "detail", "errors"], + "additionalProperties": false + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + } + }, + "/properties-zonder-location": { + "get": { + "tags": [ + "resource" + ], + "description": "Resource", + "operationId": "getResource", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "OK", + "content": { + "application/problem+json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "integer" }, + "title": { "type": "string" }, + "detail": { "type": "string" }, + "errors": { + "type": "object", + "properties": { + "in": { "type": "string" }, + "code": { "type": "string" }, + "detail": { "type": "string" } + } + } + }, + "required": ["status", "title", "detail", "errors"], + "additionalProperties": false + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + } + }, + "/properties-location-verkeerd-format": { + "get": { + "tags": [ + "resource" + ], + "description": "Resource", + "operationId": "getPropertiesVerkeerdFormat", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "OK", + "content": { + "application/problem+json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "integer" }, + "title": { "type": "string" }, + "detail": { "type": "string" }, + "errors": { + "type": "object", + "properties": { + "in": { "type": "string" }, + "location": { + "type": "object", + "properties": { + "pointer2": { "type": "string" }, + "name": { "type": "string" }, + "index": { "type": "integer" } + } + }, + "code": { "type": "string" }, + "detail": { "type": "string" } + } + } + }, + "required": ["status", "title", "detail", "errors"], + "additionalProperties": false + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + } + } + }, + "components": { + "schemas": { + }, + "securitySchemes": { + "default": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://test.com", + "scopes": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/linter/testcases/error-type-extra-field/expected-output.txt b/linter/testcases/error-type-extra-field/expected-output.txt new file mode 100644 index 0000000..95cc954 --- /dev/null +++ b/linter/testcases/error-type-extra-field/expected-output.txt @@ -0,0 +1 @@ +No results with a severity of 'error' found! diff --git a/linter/testcases/error-type-extra-field/openapi.json b/linter/testcases/error-type-extra-field/openapi.json new file mode 100644 index 0000000..d1917c2 --- /dev/null +++ b/linter/testcases/error-type-extra-field/openapi.json @@ -0,0 +1,96 @@ +{ + "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": { + "/openapi.json": { + "get": { + "tags": [ + "openapi" + ], + "description": "OpenAPI document", + "operationId": "getOpenapiJSON", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + }, + "access-control-allow-origin": { + "description": "Alle origins mogen bij deze resource", + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "NOK", + "content": { + "application/problem+json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "integer" }, + "title": { "type": "string" }, + "detail": {"type": "string" }, + "extra": {"type": "string" } + } + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + } + } + }, + "components": { + "schemas": { + }, + "securitySchemes": { + "default": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://test.com", + "scopes": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/linter/testcases/error-type-missing-required/expected-output.txt b/linter/testcases/error-type-missing-required/expected-output.txt new file mode 100644 index 0000000..60a8b61 --- /dev/null +++ b/linter/testcases/error-type-missing-required/expected-output.txt @@ -0,0 +1,5 @@ + +/testcases/error-type-missing-required/openapi.json + 62:50 error nlgov:problem-schema-members "properties" property must have required property "detail". These fields are required: status, title and detail. paths./openapi.json.get.responses[404].content.application/problem+json.schema.properties + +✖ 1 problem (1 error, 0 warnings, 0 infos, 0 hints) diff --git a/linter/testcases/error-type-missing-required/openapi.json b/linter/testcases/error-type-missing-required/openapi.json new file mode 100644 index 0000000..bf748b3 --- /dev/null +++ b/linter/testcases/error-type-missing-required/openapi.json @@ -0,0 +1,96 @@ +{ + "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": { + "/openapi.json": { + "get": { + "tags": [ + "openapi" + ], + "description": "OpenAPI document", + "operationId": "getOpenapiJSON", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "headers": { + "API-Version": { + "description": "De huidige versie van de applicatie", + "style": "simple", + "schema": { + "type": "string" + } + }, + "access-control-allow-origin": { + "description": "Alle origins mogen bij deze resource", + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "NOK", + "content": { + "application/problem+json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "integer" }, + "title": { "type": "string" } + }, + "required": ["status", "title"], + "additionalProperties": false + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + } + } + }, + "components": { + "schemas": { + }, + "securitySchemes": { + "default": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://test.com", + "scopes": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/linter/testcases/error-type/expected-output.txt b/linter/testcases/error-type/expected-output.txt index f2893cd..c79b49a 100644 --- a/linter/testcases/error-type/expected-output.txt +++ b/linter/testcases/error-type/expected-output.txt @@ -1,5 +1,5 @@ /testcases/error-type/openapi.json - 58:35 warning nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./openapi.json.get.responses[400].content + 58:35 error nlgov:use-problem-schema The content type of an error response should be application/problem+json or application/problem+xml to match RFC 9457. paths./openapi.json.get.responses[404].content -✖ 1 problem (0 errors, 1 warning, 0 infos, 0 hints) +✖ 1 problem (1 error, 0 warnings, 0 infos, 0 hints) diff --git a/linter/testcases/error-type/openapi.json b/linter/testcases/error-type/openapi.json index 4f28a88..176c0d3 100644 --- a/linter/testcases/error-type/openapi.json +++ b/linter/testcases/error-type/openapi.json @@ -53,7 +53,7 @@ } } }, - "400": { + "404": { "description": "OK", "content": { "application/hal+json": { diff --git a/linter/testcases/paths-kebab-variables/expected-output.txt b/linter/testcases/paths-kebab-variables/expected-output.txt index 95cc954..2b6ff07 100644 --- a/linter/testcases/paths-kebab-variables/expected-output.txt +++ b/linter/testcases/paths-kebab-variables/expected-output.txt @@ -1 +1,7 @@ -No results with a severity of 'error' found! + +/testcases/paths-kebab-variables/openapi.json + 87:29 error nlgov:problem-invalid-input GET endpoints that have parameters and all other endpoints must be able to return a 400 response paths./organisaties/{id}.get.responses + 128:29 error nlgov:problem-invalid-input GET endpoints that have parameters and all other endpoints must be able to return a 400 response paths./organisaties/{id}/nested.get.responses + 169:29 error nlgov:problem-invalid-input GET endpoints that have parameters and all other endpoints must be able to return a 400 response paths./organisaties/{organisationId}/pad.get.responses + +✖ 3 problems (3 errors, 0 warnings, 0 infos, 0 hints) diff --git a/linter/testcases/paths-kebab-zoek-uitzondering/expected-output.txt b/linter/testcases/paths-kebab-zoek-uitzondering/expected-output.txt index d1c1822..951ad9d 100644 --- a/linter/testcases/paths-kebab-zoek-uitzondering/expected-output.txt +++ b/linter/testcases/paths-kebab-zoek-uitzondering/expected-output.txt @@ -1,5 +1,6 @@ /testcases/paths-kebab-zoek-uitzondering/openapi.json - 125:19 warning path-keys-no-trailing-slash Path must not end with slash. paths./_zoek/ + 125:19 warning path-keys-no-trailing-slash Path must not end with slash. paths./_zoek/ + 174:29 error nlgov:problem-invalid-input GET endpoints that have parameters and all other endpoints must be able to return a 400 response paths./organisaties/{id}/nested/_zoek.get.responses -✖ 1 problem (0 errors, 1 warning, 0 infos, 0 hints) +✖ 2 problems (1 error, 1 warning, 0 infos, 0 hints) diff --git a/linter/testcases/query-keys-camel-case/expected-output.txt b/linter/testcases/query-keys-camel-case/expected-output.txt index 3e99836..228c642 100644 --- a/linter/testcases/query-keys-camel-case/expected-output.txt +++ b/linter/testcases/query-keys-camel-case/expected-output.txt @@ -1,9 +1,10 @@ /testcases/query-keys-camel-case/openapi.json - 84:33 error nlgov:query-keys-camel-case kebab-case is not lower camelCase. paths./resource.get.parameters[1].name - 91:33 error nlgov:query-keys-camel-case _startMetUnderscore is not lower camelCase. paths./resource.get.parameters[2].name - 98:33 error nlgov:query-keys-camel-case 9startMetGetal is not lower camelCase. paths./resource.get.parameters[3].name - 105:33 error nlgov:query-keys-camel-case snake_case is not lower camelCase. paths./resource.get.parameters[4].name - 112:33 error nlgov:query-keys-camel-case UpperCamelCase is not lower camelCase. paths./resource.get.parameters[5].name + 84:33 error nlgov:query-keys-camel-case kebab-case is not lower camelCase. paths./resource.get.parameters[1].name + 91:33 error nlgov:query-keys-camel-case _startMetUnderscore is not lower camelCase. paths./resource.get.parameters[2].name + 98:33 error nlgov:query-keys-camel-case 9startMetGetal is not lower camelCase. paths./resource.get.parameters[3].name + 105:33 error nlgov:query-keys-camel-case snake_case is not lower camelCase. paths./resource.get.parameters[4].name + 112:33 error nlgov:query-keys-camel-case UpperCamelCase is not lower camelCase. paths./resource.get.parameters[5].name + 118:29 error nlgov:problem-invalid-input GET endpoints that have parameters and all other endpoints must be able to return a 400 response paths./resource.get.responses -✖ 5 problems (5 errors, 0 warnings, 0 infos, 0 hints) +✖ 6 problems (6 errors, 0 warnings, 0 infos, 0 hints) diff --git a/sections/designRules.md b/sections/designRules.md index 60c76cb..6397442 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -484,6 +484,164 @@ https://api.example.org/v1/comments/456 +## Error handling + +
+

Use problem details for error responses

+
+
Statement
+
+

Error responses with HTTP status codes 4xx or 5xx MUST use either application/problem+json or application/problem+xml as the Content-Type header, and the response body MUST conform to the structure defined in [[rfc9457]]. +

The following fields MUST be present: status, title, and detail. +

+
Rationale
+
+

Providing problem details in a machine-readable format aids automation and debugging. By using a common error format, APIs do not need to define their own or misuse existing HTTP status codes.

+ +
+
How to test
+
+ Verify all responses with status code 4xx or 5xx have Content-Type set to application/problem+json or application/problem+xml and the body contains fields status, title, and detail. +
+
+
+ +
+

Use status code 400 for invalid input

+
+
Statement
+
+

API requests containing invalid input MUST result in HTTP status code 400 (Bad Request). + Invalid input includes syntax errors, missing or invalid query parameters. +

The request payload SHOULD be validated with a schema. A request payload with schema validation errors MUST be treated as invalid input. +

+
Rationale
+
+

The semantics of status code 400 ("the server cannot or will not process the request due to something that is perceived to be a client error") match validation failures more closely than status code 422, which historically originates from WebDAV and introduces no added interoperability benefit.

+
+
How to test
+
+ Verify that operations accepting query parameters and/or a request body contain a response with status code 400. +
+
+
+ +
+

Add specific errors for Bad Request responses

+
+
Statement
+
+

Problem details with status code 400 (Bad Request) MUST include an additional member errors containing an ordered list of validation error objects, as specified below. +

Each error object MUST contain in and detail members, and MAY optionally contain location, index and code members. +

    +
  • in - where the error occurs: body or query.
  • +
  • detail - a human-readable message describing the violation.
  • +
  • location (optional) - a locator for the offending value: +
      +
    • For JSON request bodies: a JSON Pointer [[rfc6901]] expression pointing to the value.
    • +
    • For XML request bodies: an absolute XPath v3.1 [[xpath-31]] expression pointing to the value.
    • +
    • For query parameters: the parameter name.
    • +
    + For body errors, the location member may be omitted, in case the error refers to the body as a whole (e.g. syntax errors). +
  • +
  • index (optional) - a zero-based index position when multiple query parameters have the same name.
  • +
  • code (optional) - a short, stable machine-readable code as a rule identifier (e.g. date.format). If a type URI is provided on the message-level, dereferencing this URI SHOULD result in a page describing all possible code values including a description for each value.
  • +
+ + +
+
Rationale
+
+

Having a single, consistent errors structure makes validation issues predictable for clients, while relying on established locators using universal standards (JSON Pointer, XPath).

+
+
How to test
+
+ Verify all responses with status code 400 contain a required errors member conforming to the requirements above. +
+
+
+ +
+

Return all errors together for bad requests

+
+
Statement
+
+

API requests with HTTP status code 400 (Bad Request) SHOULD include all applicable schema validation errors and MAY include additional errors. +

+
Rationale
+
+

To reduce the amount of roundtrips between client and server, all applicable schema validation errors SHOULD be returned together. + This allows a client to present validation errors to a user in one go, reducing user friction with multiple retries. +

It depends on a validation technique whether this is possible or not. + For example, when a client provides a date in the weekend, where only dates on weekdays are allowed, it depends on which service performs these validation checks. + In these cases, include the additional validation errors together with other errors whenever feasible. +

+
+
+ ## Documentation An API is as good as the accompanying documentation. The documentation has to be easily findable, searchable and publicly accessible. Most developers will first read the documentation before they start implementing. Hiding the technical documentation in PDF documents and/or behind a login creates a barrier for both developers and search engines.