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 diff --git a/sections/designRules.md b/sections/designRules.md index 60c76cb..f1b81f5 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -534,6 +534,47 @@ 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