From afc09aaac0e47ca4df87a2dd0f3d946758016d5d Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Thu, 5 Mar 2026 11:00:00 +0100 Subject: [PATCH 1/2] Voeg regel toe voor error structuur bij Bad Requests Hier was initieel geen consensus voor bij de behandeling van het hoofdstuk "Error Handling". --- .../expected-output.txt | 7 + .../error-type-bad-invalid-input/openapi.json | 232 ++++++++++++++ .../expected-output.txt | 6 + .../error-type-bad-request/openapi.json | 295 ++++++++++++++++++ .../paths-kebab-variables/expected-output.txt | 8 +- .../expected-output.txt | 5 +- .../query-keys-camel-case/expected-output.txt | 13 +- media/linter.yaml | 54 ++++ sections/designRules.md | 89 ++++++ 9 files changed, 700 insertions(+), 9 deletions(-) create mode 100644 linter/testcases/error-type-bad-invalid-input/expected-output.txt create mode 100644 linter/testcases/error-type-bad-invalid-input/openapi.json create mode 100644 linter/testcases/error-type-bad-request/expected-output.txt create mode 100644 linter/testcases/error-type-bad-request/openapi.json 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 00000000..69b4bb4e --- /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 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-bad-invalid-input/openapi.json b/linter/testcases/error-type-bad-invalid-input/openapi.json new file mode 100644 index 00000000..c8ab9261 --- /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 00000000..7dbc0bd2 --- /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 00000000..1ee4a307 --- /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/paths-kebab-variables/expected-output.txt b/linter/testcases/paths-kebab-variables/expected-output.txt index 95cc954a..97a1f2e8 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 and DELETE 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 and DELETE 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 and DELETE 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 d1c1822b..b4423b4b 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 and DELETE 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 3e99836d..2afb315a 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 and DELETE 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/media/linter.yaml b/media/linter.yaml index a64854fc..2d5a227a 100644 --- a/media/linter.yaml +++ b/media/linter.yaml @@ -213,6 +213,60 @@ 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" + + #/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/sections/designRules.md b/sections/designRules.md index 139554b9..09d73dca 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -637,6 +637,95 @@ Content-Type: application/problem+json{ +
+

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

From 56ee465b5027926f97263e756aba4ecc3c88ff1b Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Tue, 14 Apr 2026 17:01:01 +0200 Subject: [PATCH 2/2] Beheer: update Quarkus example met invalide query parameters --- examples/quarkus/pom.xml | 17 ++++- .../main/java/org/acme/BadRequestProblem.java | 65 +++++++++++++++++++ .../main/java/org/acme/GreetingResource.java | 64 +++++++++++++++--- media/linter.yaml | 33 ++++++---- 4 files changed, 154 insertions(+), 25 deletions(-) create mode 100644 examples/quarkus/src/main/java/org/acme/BadRequestProblem.java diff --git a/examples/quarkus/pom.xml b/examples/quarkus/pom.xml index a60366fd..1d9b178f 100644 --- a/examples/quarkus/pom.xml +++ b/examples/quarkus/pom.xml @@ -12,7 +12,7 @@ UTF-8 quarkus-bom io.quarkus.platform - 3.21.1 + 3.34.3 true 3.5.2 @@ -44,7 +44,20 @@ io.quarkus - quarkus-junit5 + quarkus-rest-jackson + + + io.quarkus + quarkus-hibernate-validator + + + io.quarkiverse.resteasy-problem + quarkus-resteasy-problem + 3.32.0 + + + io.quarkus + quarkus-junit test diff --git a/examples/quarkus/src/main/java/org/acme/BadRequestProblem.java b/examples/quarkus/src/main/java/org/acme/BadRequestProblem.java new file mode 100644 index 00000000..02983b07 --- /dev/null +++ b/examples/quarkus/src/main/java/org/acme/BadRequestProblem.java @@ -0,0 +1,65 @@ +package org.acme; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.quarkiverse.resteasy.problem.HttpProblem; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.util.List; + +public class BadRequestProblem extends HttpProblem { + protected BadRequestProblem(String message, List errors) { + super( + builder() + .withTitle("Bad hello request") + .withStatus(Response.Status.BAD_REQUEST) + .withDetail(message) + .with("errors", errors)); + } + + @Schema(name = "errors", description = "All bad request errors") + public List errors; + + @Schema( + name = "BadRequestDetails", + description = "Request details according to API Design Rules") + public static class BadRequestDetails { + @Schema(description = "Where the error occurs") + public BadRequestLocation in; + + @Schema(description = "Human-readable message describing the violation") + public String detail; + + @Schema(description = "A locator for the offending value") + public String location; + + @Schema( + nullable = true, + description = + "A zero-based index position when multiple query parameters have the same name") + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public Integer index; + + private BadRequestDetails( + BadRequestLocation in, String detail, String location, Integer index) { + this.in = in; + this.detail = detail; + this.location = location; + this.index = index; + } + + public static BadRequestDetails forQuery( + BadRequestLocation in, String detail, Integer index) { + return new BadRequestDetails(in, detail, "query", index); + } + } + + public enum BadRequestLocation { + @JsonProperty("body") + Body, + @JsonProperty("query") + Query, + ; + } +} diff --git a/examples/quarkus/src/main/java/org/acme/GreetingResource.java b/examples/quarkus/src/main/java/org/acme/GreetingResource.java index ad0db735..cd5a5208 100644 --- a/examples/quarkus/src/main/java/org/acme/GreetingResource.java +++ b/examples/quarkus/src/main/java/org/acme/GreetingResource.java @@ -1,8 +1,8 @@ package org.acme; import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.headers.Header; @@ -10,26 +10,70 @@ import org.eclipse.microprofile.openapi.annotations.media.Schema; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.eclipse.microprofile.openapi.annotations.tags.Tag; +import org.jboss.resteasy.reactive.RestQuery; +import org.jboss.resteasy.reactive.Separator; + +import java.util.List; +import java.util.Map; @Path("/hello") @Tag(name = "greeting") public class GreetingResource { @GET - @Operation( - description = "Hello world endpoint" - ) + @Operation(description = "Hello world endpoint") @APIResponse( responseCode = "200", content = @Content(mediaType = MediaType.TEXT_PLAIN), headers = { - @Header( - name = OpenApiConstants.API_VERSION_HEADER_NAME, - schema = @Schema(implementation = String.class) - ) - } - ) + @Header( + name = OpenApiConstants.API_VERSION_HEADER_NAME, + schema = @Schema(implementation = String.class)) + }) public String hello() { return "Hello from Quarkus REST"; } + + @POST + @Operation(description = "Test bad request with query parameter") + @APIResponse( + responseCode = "200", + content = @Content(mediaType = MediaType.TEXT_PLAIN), + headers = { + @Header( + name = OpenApiConstants.API_VERSION_HEADER_NAME, + schema = @Schema(implementation = String.class)) + }) + @APIResponse( + responseCode = "400", + description = "Bad request response", + content = + @Content( + mediaType = "application/problem+json", + schema = @Schema(implementation = BadRequestProblem.class))) + public String withInput(@RestQuery("query") @Separator(",") List queryParam) { + if (queryParam.isEmpty()) { + throw new BadRequestProblem( + "Missing required query parameter", + List.of( + BadRequestProblem.BadRequestDetails.forQuery( + BadRequestProblem.BadRequestLocation.Query, + "Query parameter is invalid", + null))); + } + var queryParams = queryParam.iterator(); + for (var index = 0; queryParams.hasNext(); index++) { + var param = queryParams.next(); + if (!param.equals("42")) { + throw new BadRequestProblem( + "Missing required query parameter", + List.of( + BadRequestProblem.BadRequestDetails.forQuery( + BadRequestProblem.BadRequestLocation.Query, + "Query parameter is invalid", + index))); + } + } + return "Successful response"; + } } diff --git a/media/linter.yaml b/media/linter.yaml index 2d5a227a..78a67ba8 100644 --- a/media/linter.yaml +++ b/media/linter.yaml @@ -244,26 +244,33 @@ rules: errors: type: object properties: - properties: + items: type: object properties: - in: {} - detail: {} - location: + properties: type: object properties: - properties: + in: {} + detail: {} + location: type: object properties: - pointer: {} - name: {} - index: {} - additionalProperties: false - code: {} + properties: + type: object + properties: + pointer: {} + name: {} + index: {} + additionalProperties: false + code: {} + required: + - in + - detail + - location required: - - in - - detail - additionalProperties: false + - properties + required: + - items required: - errors