From b66e8342535e33bb19cf137f320820f5e549fa9c Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Wed, 6 May 2026 14:24:21 +0200 Subject: [PATCH 1/5] Publiceer 2.2 versie ter vaststelling --- js/config.mjs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/config.mjs b/js/config.mjs index a749a8aa..47245f13 100644 --- a/js/config.mjs +++ b/js/config.mjs @@ -99,12 +99,12 @@ loadRespecWithConfiguration({ ], github: "https://github.com/Logius-standaarden/API-Design-Rules", pubDomain: "api", - publishDate: "2025-08-27", - publishVersion: "2.1.0", - previousPublishDate: "2025-02-17", - previousPublishVersion: "2.0.2", + publishDate: "2026-06-02", + publishVersion: "2.2.0", + previousPublishDate: "2025-08-27", + previousPublishVersion: "2.1.0", shortName: "adr", - specStatus: "WV", + specStatus: "VV", specType: "ST", pluralize: true, From 19eada95867236694a1216fff0fc68b492a08a2c Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Thu, 7 May 2026 08:29:52 +0200 Subject: [PATCH 2/5] Beheer: fix RFC capitalization Co-authored-by: Alexander Green --- sections/designRules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/designRules.md b/sections/designRules.md index 5d8b3e4d..139554b9 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -593,7 +593,7 @@ https://api.example.org/v1/comments/456
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]]. +

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
From c2639e474f9d2b5bffec5bc9b09bbde33d948414 Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Wed, 6 May 2026 16:07:35 +0200 Subject: [PATCH 3/5] Beheer: gebruik enkel specifieke Spectral OAS rules (#325) Hiermee zorgen we ervoor dat we enkel regels aanzetten die ook relevant zijn voor de API Design Rules. Tevens zetten we die regels op error in plaats van warning, omdat ze ook verplicht zijn. Als laatste zorgt dit ervoor dat we ook netjes de publish OpenAPI design rule kunnen linken aan regels. --- .../paths-kebab-slashes/expected-output.txt | 6 +++--- .../expected-output.txt | 4 ++-- media/linter.yaml | 14 +++++++++++++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/linter/testcases/paths-kebab-slashes/expected-output.txt b/linter/testcases/paths-kebab-slashes/expected-output.txt index dcd00d6e..1681916a 100644 --- a/linter/testcases/paths-kebab-slashes/expected-output.txt +++ b/linter/testcases/paths-kebab-slashes/expected-output.txt @@ -1,6 +1,6 @@ /testcases/paths-kebab-slashes/openapi.json - 96:26 warning path-keys-no-trailing-slash Path must not end with slash. paths./suffix-slash/ - 154:37 warning path-keys-no-trailing-slash Path must not end with slash. paths./nested-slash/met-suffix/ + 96:26 error path-keys-no-trailing-slash Path must not end with slash. paths./suffix-slash/ + 154:37 error path-keys-no-trailing-slash Path must not end with slash. paths./nested-slash/met-suffix/ -✖ 2 problems (0 errors, 2 warnings, 0 infos, 0 hints) +✖ 2 problems (2 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 d1c1822b..688946e9 100644 --- a/linter/testcases/paths-kebab-zoek-uitzondering/expected-output.txt +++ b/linter/testcases/paths-kebab-zoek-uitzondering/expected-output.txt @@ -1,5 +1,5 @@ /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 error path-keys-no-trailing-slash Path must not end with slash. paths./_zoek/ -✖ 1 problem (0 errors, 1 warning, 0 infos, 0 hints) +✖ 1 problem (1 error, 0 warnings, 0 infos, 0 hints) diff --git a/media/linter.yaml b/media/linter.yaml index a64854fc..a9fa2349 100644 --- a/media/linter.yaml +++ b/media/linter.yaml @@ -17,11 +17,23 @@ # spectral lint -r .linter.yaml $OAS_URL_OR_FILE # ``` -extends: spectral:oas +extends: + - ["spectral:oas", "off"] # extend spectral:oas maar zet standaard alle regels uit rules: + #/core/publish-openapi (deze error-rules valideren of OAS valid is) + oas3-schema: error + operation-operationId-unique: error + path-params: error + openapi-tags-uniqueness: error + oas3-valid-media-example: error + oas3-valid-schema-example: error + oas3-server-variables: error oas3-api-servers: error + #/core/no-trailing-slash + path-keys-no-trailing-slash: error + #/core/doc-openapi nlgov:openapi3: severity: error From c540a74fc5ca89a315bf0174bd46cee4b68ce0f9 Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Thu, 7 May 2026 08:29:07 +0200 Subject: [PATCH 4/5] Beheer: voeg linter regel toe voor invalid-input (#328) Die is per abuis verwijderd in 616fe47f16bf4536bf54b398c2a3d96f9d407b3b terwijl die er wel in had gemoeten. Tevens fixt het een klein issue waar `openapi.yaml` niet goed werden genegeerd, zoals we al deden voor `openapi.json`. --- .../expected-output.txt | 7 + .../error-type-invalid-input/openapi.json | 232 ++++++++++++++++++ .../paths-kebab-variables/openapi.json | 120 +++++++++ .../openapi.json | 30 +++ .../query-keys-camel-case/openapi.json | 30 +++ media/linter.yaml | 20 +- 6 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 linter/testcases/error-type-invalid-input/expected-output.txt create mode 100644 linter/testcases/error-type-invalid-input/openapi.json diff --git a/linter/testcases/error-type-invalid-input/expected-output.txt b/linter/testcases/error-type-invalid-input/expected-output.txt new file mode 100644 index 00000000..40c7eccb --- /dev/null +++ b/linter/testcases/error-type-invalid-input/expected-output.txt @@ -0,0 +1,7 @@ + +/testcases/error-type-invalid-input/openapi.json + 119:29 error nlgov:problem-invalid-input GET and DELETE 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 and DELETE 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 and DELETE 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-invalid-input/openapi.json b/linter/testcases/error-type-invalid-input/openapi.json new file mode 100644 index 00000000..06e03e47 --- /dev/null +++ b/linter/testcases/error-type-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": {} + } + } + } + } + } +} diff --git a/linter/testcases/paths-kebab-variables/openapi.json b/linter/testcases/paths-kebab-variables/openapi.json index ed570c9a..a99c0863 100644 --- a/linter/testcases/paths-kebab-variables/openapi.json +++ b/linter/testcases/paths-kebab-variables/openapi.json @@ -55,6 +55,36 @@ } } } + }, + "400": { + "description": "OK", + "content": { + "application/problem+json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "integer" }, + "title": { "type": "string" }, + "detail": { "type": "string" }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "in": { "type": "string" }, + "location": { "type": "string" }, + "code": { "type": "string" }, + "detail": { "type": "string" }, + "index": { "type": "number" } + } + } + } + }, + "required": ["status", "title", "detail", "errors"], + "additionalProperties": false + } + } + } } }, "security": [ @@ -96,6 +126,36 @@ } } } + }, + "400": { + "description": "OK", + "content": { + "application/problem+json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "integer" }, + "title": { "type": "string" }, + "detail": { "type": "string" }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "in": { "type": "string" }, + "location": { "type": "string" }, + "code": { "type": "string" }, + "detail": { "type": "string" }, + "index": { "type": "number" } + } + } + } + }, + "required": ["status", "title", "detail", "errors"], + "additionalProperties": false + } + } + } } }, "security": [ @@ -137,6 +197,36 @@ } } } + }, + "400": { + "description": "OK", + "content": { + "application/problem+json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "integer" }, + "title": { "type": "string" }, + "detail": { "type": "string" }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "in": { "type": "string" }, + "location": { "type": "string" }, + "code": { "type": "string" }, + "detail": { "type": "string" }, + "index": { "type": "number" } + } + } + } + }, + "required": ["status", "title", "detail", "errors"], + "additionalProperties": false + } + } + } } }, "security": [ @@ -178,6 +268,36 @@ } } } + }, + "400": { + "description": "OK", + "content": { + "application/problem+json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "integer" }, + "title": { "type": "string" }, + "detail": { "type": "string" }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "in": { "type": "string" }, + "location": { "type": "string" }, + "code": { "type": "string" }, + "detail": { "type": "string" }, + "index": { "type": "number" } + } + } + } + }, + "required": ["status", "title", "detail", "errors"], + "additionalProperties": false + } + } + } } }, "security": [ diff --git a/linter/testcases/paths-kebab-zoek-uitzondering/openapi.json b/linter/testcases/paths-kebab-zoek-uitzondering/openapi.json index 4584bfd2..4d695733 100644 --- a/linter/testcases/paths-kebab-zoek-uitzondering/openapi.json +++ b/linter/testcases/paths-kebab-zoek-uitzondering/openapi.json @@ -183,6 +183,36 @@ } } } + }, + "400": { + "description": "OK", + "content": { + "application/problem+json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "integer" }, + "title": { "type": "string" }, + "detail": { "type": "string" }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "in": { "type": "string" }, + "location": { "type": "string" }, + "code": { "type": "string" }, + "detail": { "type": "string" }, + "index": { "type": "number" } + } + } + } + }, + "required": ["status", "title", "detail", "errors"], + "additionalProperties": false + } + } + } } }, "security": [ diff --git a/linter/testcases/query-keys-camel-case/openapi.json b/linter/testcases/query-keys-camel-case/openapi.json index 5c9ccbe2..a2a12c5d 100644 --- a/linter/testcases/query-keys-camel-case/openapi.json +++ b/linter/testcases/query-keys-camel-case/openapi.json @@ -127,6 +127,36 @@ } } } + }, + "400": { + "description": "OK", + "content": { + "application/problem+json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "integer" }, + "title": { "type": "string" }, + "detail": { "type": "string" }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "in": { "type": "string" }, + "location": { "type": "string" }, + "code": { "type": "string" }, + "detail": { "type": "string" }, + "index": { "type": "number" } + } + } + } + }, + "required": ["status", "title", "detail", "errors"], + "additionalProperties": false + } + } + } } }, "security": [ diff --git a/media/linter.yaml b/media/linter.yaml index a9fa2349..c096d7b8 100644 --- a/media/linter.yaml +++ b/media/linter.yaml @@ -146,7 +146,7 @@ rules: nlgov:paths-kebab-case: severity: error message: "{{property}} is not kebab-case." - given: $.paths[?(@property && !@property.match(/\/openapi\.json/))]~ + given: $.paths[?(@property && !@property.match(/\/openapi\.(json)|(yaml)/))]~ then: function: pattern functionOptions: @@ -225,6 +225,24 @@ rules: - title - detail + #/core/error-handling/invalid-input + nlgov:problem-invalid-input: + severity: error + message: "GET and DELETE endpoints that have parameters, and all other endpoints must be able to return a 400 response" + given: + - $.paths..[?( @property.match(/(get)|(delete)/) && @.parameters && @.parameters.length > 0 )] + - $.paths..[?( @property.match(/(put)|(post)|(patch)/))] + then: + function: schema + functionOptions: + schema: + type: object + properties: + responses: + type: object + required: + - "400" + nlgov:property-casing: severity: warn given: From c4b556f8707d26234b0dbc26849a27775c3d1bda Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Tue, 12 May 2026 11:36:19 +0200 Subject: [PATCH 5/5] Beheer: zet status op werkversie --- js/config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/config.mjs b/js/config.mjs index c9cfd3e8..d890a7d8 100644 --- a/js/config.mjs +++ b/js/config.mjs @@ -104,7 +104,7 @@ loadRespecWithConfiguration({ previousPublishDate: "2025-08-27", previousPublishVersion: "2.1.0", shortName: "adr", - specStatus: "VV", + specStatus: "WV", specType: "ST", pluralize: true,