From c7d24d6ec87c32da0a53cc78a0b3c9efbdaf5a73 Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Wed, 28 Jan 2026 14:56:46 +0100 Subject: [PATCH 1/3] Voeg regel toe over het documenteren van server URLs Hiermee maken we een eerste stap in het enforceren van informatie over servers. Tevens maken we hiermee duidelijk dat een relatieve URL is toegestaan, echter moet er altijd minstens 1 absolute URL zijn. --- sections/designRules.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/sections/designRules.md b/sections/designRules.md index 60c76cb..df269cc 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -534,6 +534,43 @@ An API is as good as the accompanying documentation. The documentation has to be +
+

Document server information

+
+
Statement
+
+ OpenAPI definition document MUST include at least one Server object in servers. + One or more Server objects MUST have a url which is an absolute URL. + At most one Server object MAY have a url which is a relative URL. +
+
Rationale
+
+ The OpenAPI Specification (OAS) [[OPENAPIS]] can include information about servers which serve this API. It can list different environments, such as production and pre-production. If desired, a relative URL can be used to denote where an API resides, regardless of origin. However, only one relative URL may be present to avoid ambiguation for consumers which relative URL is applicable for the current origin. + +
+
How to test
+
+
    +
  • Step 1: Parse the OpenAPI Description to confirm the servers list is present.
  • +
  • Step 2: The list contains at least one object with an absolute URL
  • +
  • Step 3: The list contains at most one object with a relative URL
  • +
+
+
+

Publish documentation in Dutch unless there is existing documentation in English

From a0bcd3b6a7a95ac171ca11aeb487db844cc7c9b2 Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Thu, 29 Jan 2026 14:45:18 +0100 Subject: [PATCH 2/3] Fix servers example --- sections/designRules.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/sections/designRules.md b/sections/designRules.md index df269cc..f1b81f5 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -547,18 +547,22 @@ An API is as good as the accompanying documentation. The documentation has to be
The OpenAPI Specification (OAS) [[OPENAPIS]] can include information about servers which serve this API. It can list different environments, such as production and pre-production. If desired, a relative URL can be used to denote where an API resides, regardless of origin. However, only one relative URL may be present to avoid ambiguation for consumers which relative URL is applicable for the current origin.
How to test
From aa531be291fcd4ce027d5f1af314baa9fcb8b8e3 Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Tue, 10 Feb 2026 15:37:24 +0100 Subject: [PATCH 3/3] Voeg spectral linter regels toe --- linter/spectral.yml | 45 ++++++++- .../servers-empty/expected-output.txt | 5 +- .../servers-no-absolute/expected-output.txt | 5 + .../servers-no-absolute/openapi.json | 81 ++++++++++++++++ .../servers-no-https/expected-output.txt | 5 + .../testcases/servers-no-https/openapi.json | 85 +++++++++++++++++ .../servers-relative-one/expected-output.txt | 1 + .../servers-relative-one/openapi.json | 89 ++++++++++++++++++ .../servers-relative-two/expected-output.txt | 5 + .../servers-relative-two/openapi.json | 93 +++++++++++++++++++ 10 files changed, 409 insertions(+), 5 deletions(-) create mode 100644 linter/testcases/servers-no-absolute/expected-output.txt create mode 100644 linter/testcases/servers-no-absolute/openapi.json create mode 100644 linter/testcases/servers-no-https/expected-output.txt create mode 100644 linter/testcases/servers-no-https/openapi.json create mode 100644 linter/testcases/servers-relative-one/expected-output.txt create mode 100644 linter/testcases/servers-relative-one/openapi.json create mode 100644 linter/testcases/servers-relative-two/expected-output.txt create mode 100644 linter/testcases/servers-relative-two/openapi.json diff --git a/linter/spectral.yml b/linter/spectral.yml index da7089a..407143c 100644 --- a/linter/spectral.yml +++ b/linter/spectral.yml @@ -172,8 +172,8 @@ rules: char: "" nlgov:servers-use-https: - severity: warn - message: "Server URL {{value}} {{error}}." + severity: error + message: "Server URL {{value}} must start with https:// instead of http://." given: - $.servers[*] - $.paths..servers[*] @@ -181,7 +181,46 @@ rules: field: url function: pattern functionOptions: - match: ^https://.* + notMatch: ^http://.* + + nlgov:servers-at-most-one-relative: + severity: error + message: "At most one relative URL may be specified as server." + given: + - $.servers + - $.paths..servers + then: + function: schema + functionOptions: + dialect: draft2020-12 + schema: + type: array + contains: + type: object + properties: + url: + pattern: ^(?!https:).+ + minContains: 0 + maxContains: 1 + + nlgov:servers-at-least-one-absolute: + severity: error + message: "At least one absolute URL must be specified as server." + given: + - $.servers + - $.paths..servers + then: + function: schema + functionOptions: + dialect: draft2020-12 + schema: + type: array + contains: + type: object + properties: + url: + pattern: ^https://.* + minContains: 1 nlgov:use-problem-schema: severity: warn diff --git a/linter/testcases/servers-empty/expected-output.txt b/linter/testcases/servers-empty/expected-output.txt index 866834c..d50a6d3 100644 --- a/linter/testcases/servers-empty/expected-output.txt +++ b/linter/testcases/servers-empty/expected-output.txt @@ -1,5 +1,6 @@ /testcases/servers-empty/openapi.json - 13:15 error oas3-api-servers OpenAPI "servers" must be present and non-empty array. servers + 13:15 error nlgov:servers-at-least-one-absolute At least one absolute URL must be specified as server. servers + 13:15 error oas3-api-servers OpenAPI "servers" must be present and non-empty array. servers -✖ 1 problem (1 error, 0 warnings, 0 infos, 0 hints) +✖ 2 problems (2 errors, 0 warnings, 0 infos, 0 hints) diff --git a/linter/testcases/servers-no-absolute/expected-output.txt b/linter/testcases/servers-no-absolute/expected-output.txt new file mode 100644 index 0000000..6e2a080 --- /dev/null +++ b/linter/testcases/servers-no-absolute/expected-output.txt @@ -0,0 +1,5 @@ + +/testcases/servers-no-absolute/openapi.json + 13:15 error nlgov:servers-at-least-one-absolute At least one absolute URL must be specified as server. servers + +✖ 1 problem (1 error, 0 warnings, 0 infos, 0 hints) diff --git a/linter/testcases/servers-no-absolute/openapi.json b/linter/testcases/servers-no-absolute/openapi.json new file mode 100644 index 0000000..11938a9 --- /dev/null +++ b/linter/testcases/servers-no-absolute/openapi.json @@ -0,0 +1,81 @@ +{ + "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": "/api/v1", + "description": "API location on the origin that the openapi.json is served" + } + ], + "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" + } + } + } + } + }, + "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/servers-no-https/expected-output.txt b/linter/testcases/servers-no-https/expected-output.txt new file mode 100644 index 0000000..6070a9a --- /dev/null +++ b/linter/testcases/servers-no-https/expected-output.txt @@ -0,0 +1,5 @@ + +/testcases/servers-no-https/openapi.json + 15:20 error nlgov:servers-use-https Server URL http://production.example.com/api/v1 must start with https:// instead of http://. servers[0].url + +✖ 1 problem (1 error, 0 warnings, 0 infos, 0 hints) diff --git a/linter/testcases/servers-no-https/openapi.json b/linter/testcases/servers-no-https/openapi.json new file mode 100644 index 0000000..f9ea2e5 --- /dev/null +++ b/linter/testcases/servers-no-https/openapi.json @@ -0,0 +1,85 @@ +{ + "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": "http://production.example.com/api/v1", + "description": "Production server" + }, + { + "url": "https://staging.example.com/api/v1", + "description": "Pre-production server" + } + ], + "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" + } + } + } + } + }, + "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/servers-relative-one/expected-output.txt b/linter/testcases/servers-relative-one/expected-output.txt new file mode 100644 index 0000000..95cc954 --- /dev/null +++ b/linter/testcases/servers-relative-one/expected-output.txt @@ -0,0 +1 @@ +No results with a severity of 'error' found! diff --git a/linter/testcases/servers-relative-one/openapi.json b/linter/testcases/servers-relative-one/openapi.json new file mode 100644 index 0000000..4a42d58 --- /dev/null +++ b/linter/testcases/servers-relative-one/openapi.json @@ -0,0 +1,89 @@ +{ + "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://production.example.com/api/v1", + "description": "Production server" + }, + { + "url": "https://staging.example.com/api/v1", + "description": "Pre-production server" + }, + { + "url": "/api/v1", + "description": "API location on the origin that the openapi.json is served" + } + ], + "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" + } + } + } + } + }, + "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/servers-relative-two/expected-output.txt b/linter/testcases/servers-relative-two/expected-output.txt new file mode 100644 index 0000000..3e5525f --- /dev/null +++ b/linter/testcases/servers-relative-two/expected-output.txt @@ -0,0 +1,5 @@ + +/testcases/servers-relative-two/openapi.json + 13:15 error nlgov:servers-at-most-one-relative At most one relative URL may be specified as server. servers + +✖ 1 problem (1 error, 0 warnings, 0 infos, 0 hints) diff --git a/linter/testcases/servers-relative-two/openapi.json b/linter/testcases/servers-relative-two/openapi.json new file mode 100644 index 0000000..f6fe366 --- /dev/null +++ b/linter/testcases/servers-relative-two/openapi.json @@ -0,0 +1,93 @@ +{ + "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://production.example.com/api/v1", + "description": "Production server" + }, + { + "url": "https://staging.example.com/api/v1", + "description": "Pre-production server" + }, + { + "url": "/api/v1", + "description": "API location on the origin that the openapi.json is served" + }, + { + "url": "/api/v2", + "description": "API location on the origin that the openapi.json is served" + } + ], + "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" + } + } + } + } + }, + "security": [ + { + "default": [] + } + ] + } + } + }, + "components": { + "schemas": { + }, + "securitySchemes": { + "default": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://test.com", + "scopes": {} + } + } + } + } + } +} \ No newline at end of file