Skip to content

Commit ff40810

Browse files
authored
feat: skip validation in case of binary content (#87)
Signed-off-by: Pascal Krause <[email protected]>
1 parent 6f8df32 commit ff40810

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+768
-443
lines changed

src/main/java/io/vertx/openapi/contract/MediaType.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
package io.vertx.openapi.contract;
1414

15-
import io.netty.handler.codec.http.HttpHeaderValues;
1615
import io.vertx.codegen.annotations.VertxGen;
1716
import io.vertx.json.schema.JsonSchema;
1817

@@ -29,18 +28,24 @@
2928
@VertxGen
3029
public interface MediaType extends OpenAPIObject {
3130

32-
String APPLICATION_HAL_JSON = "application/hal+json";
33-
String APPLICATION_JSON = HttpHeaderValues.APPLICATION_JSON.toString();
31+
String APPLICATION_JSON = "application/json";
3432
String APPLICATION_JSON_UTF8 = APPLICATION_JSON + "; charset=utf-8";
35-
String MULTIPART_FORM_DATA = HttpHeaderValues.MULTIPART_FORM_DATA.toString();
36-
List<String> SUPPORTED_MEDIA_TYPES = Arrays.asList(APPLICATION_JSON, APPLICATION_JSON_UTF8, MULTIPART_FORM_DATA, APPLICATION_HAL_JSON);
33+
String MULTIPART_FORM_DATA = "multipart/form-data";
34+
String APPLICATION_HAL_JSON = "application/hal+json";
35+
String APPLICATION_OCTET_STREAM = "application/octet-stream";
36+
List<String> SUPPORTED_MEDIA_TYPES = Arrays.asList(APPLICATION_JSON, APPLICATION_JSON_UTF8, MULTIPART_FORM_DATA,
37+
APPLICATION_HAL_JSON, APPLICATION_OCTET_STREAM);
3738

3839
static boolean isMediaTypeSupported(String type) {
3940
return SUPPORTED_MEDIA_TYPES.contains(type.toLowerCase());
4041
}
4142

4243
/**
43-
* @return the schema defining the content of the request.
44+
* This method returns the schema defined in the media type.
45+
* <p></p>
46+
* In OpenAPI 3.1 it is allowed to define an empty media type model. In this case the method returns null.
47+
*
48+
* @return the schema defined in the media type model, or null in case no media type model was defined.
4449
*/
4550
JsonSchema getSchema();
4651

src/main/java/io/vertx/openapi/contract/impl/MediaTypeImpl.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,22 @@ public class MediaTypeImpl implements MediaType {
2828
public MediaTypeImpl(String identifier, JsonObject mediaTypeModel) {
2929
this.identifier = identifier;
3030
this.mediaTypeModel = mediaTypeModel;
31-
JsonObject schemaJson = mediaTypeModel.getJsonObject(KEY_SCHEMA);
32-
if (schemaJson == null || schemaJson.isEmpty()) {
31+
32+
if (mediaTypeModel == null) {
3333
throw createUnsupportedFeature("Media Type without a schema");
3434
}
35-
schema = JsonSchema.of(schemaJson);
35+
36+
if (mediaTypeModel.isEmpty()) {
37+
// OpenAPI 3.1 allows defining MediaTypes without a schema.
38+
schema = null;
39+
} else {
40+
JsonObject schemaJson = mediaTypeModel.getJsonObject(KEY_SCHEMA);
41+
if (schemaJson == null || schemaJson.isEmpty()) {
42+
throw createUnsupportedFeature("Media Type without a schema");
43+
}
44+
schema = JsonSchema.of(schemaJson);
45+
46+
}
3647
}
3748

3849
@Override

src/main/java/io/vertx/openapi/contract/impl/RequestBodyImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public MediaType determineContentType(String contentType) {
8484
if (contentType == null) {
8585
return null;
8686
}
87-
87+
8888
String condensedIdentifier = removeWhiteSpaces(contentType);
8989
if (content.containsKey(condensedIdentifier)) {
9090
return content.get(condensedIdentifier);

src/main/java/io/vertx/openapi/impl/Utils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ public static Future<JsonObject> readYamlOrJson(Vertx vertx, String path) {
5555
});
5656
}
5757

58-
/**
58+
/**
5959
* Reads YAML string and transforms it into a JsonObject.
6060
*
61-
* @param path The yamlString proper YAML formatted STring
61+
* @param yamlString The yamlString proper YAML formatted STring
6262
* @return A succeeded Future holding the JsonObject, or a failed Future if the file could not be parsed.
6363
*/
6464
public static Future<JsonObject> yamlStringToJson(String yamlString) {

src/main/java/io/vertx/openapi/validation/SchemaValidationException.java

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,27 +40,23 @@ public static SchemaValidationException createInvalidValueParameter(Parameter pa
4040
return new SchemaValidationException(msg, INVALID_VALUE, outputUnit, cause);
4141
}
4242

43-
public static SchemaValidationException createInvalidValueRequestBody(OutputUnit outputUnit,
44-
JsonSchemaValidationException cause) {
45-
String msg = String.format("The value of the request body is invalid. Reason: %s", extractReason(outputUnit));
46-
return new SchemaValidationException(msg, INVALID_VALUE, outputUnit, cause);
47-
}
48-
49-
public static SchemaValidationException createInvalidValueResponseBody(OutputUnit outputUnit,
50-
JsonSchemaValidationException cause) {
51-
String msg = String.format("The value of the response body is invalid. Reason: %s", extractReason(outputUnit));
43+
public static SchemaValidationException createInvalidValueBody(OutputUnit outputUnit,
44+
ValidationContext requestOrResponse,
45+
JsonSchemaValidationException cause) {
46+
String msg = String.format("The value of the " + requestOrResponse + " body is invalid. Reason: %s",
47+
extractReason(outputUnit));
5248
return new SchemaValidationException(msg, INVALID_VALUE, outputUnit, cause);
5349
}
5450

5551
public static SchemaValidationException createMissingValueRequestBody(OutputUnit outputUnit,
56-
JsonSchemaValidationException cause) {
52+
JsonSchemaValidationException cause) {
5753
String msg = String.format("The value of the request body is missing. Reason: %s", extractReason(outputUnit));
5854
return new SchemaValidationException(msg, MISSING_REQUIRED_PARAMETER, outputUnit, cause);
5955
}
6056

6157
public static SchemaValidationException createErrorFromOutputUnitType(Parameter parameter, OutputUnit outputUnit,
6258
JsonSchemaValidationException cause) {
63-
switch(outputUnit.getErrorType()) {
59+
switch (outputUnit.getErrorType()) {
6460
case MISSING_VALUE:
6561
return createMissingValueRequestBody(outputUnit, cause);
6662
case INVALID_VALUE:
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (c) 2024, SAP SE
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7+
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10+
*
11+
*/
12+
13+
package io.vertx.openapi.validation;
14+
15+
public enum ValidationContext {
16+
REQUEST, RESPONSE;
17+
18+
@Override
19+
public String toString() {
20+
return name().toLowerCase();
21+
}
22+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (c) 2024, SAP SE
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7+
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10+
*
11+
*/
12+
13+
package io.vertx.openapi.validation.analyser;
14+
15+
import io.vertx.core.buffer.Buffer;
16+
import io.vertx.openapi.validation.ValidationContext;
17+
18+
public class ApplicationJsonAnalyser extends ContentAnalyser {
19+
private Object decodedValue;
20+
21+
public ApplicationJsonAnalyser(String contentType, Buffer content, ValidationContext context) {
22+
super(contentType, content, context);
23+
}
24+
25+
@Override
26+
public void checkSyntacticalCorrectness() {
27+
decodedValue = decodeJsonContent(content, requestOrResponse);
28+
}
29+
30+
@Override
31+
public Object transform() {
32+
return decodedValue;
33+
}
34+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* Copyright (c) 2024, SAP SE
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7+
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10+
*
11+
*/
12+
13+
package io.vertx.openapi.validation.analyser;
14+
15+
import io.vertx.core.buffer.Buffer;
16+
import io.vertx.core.json.DecodeException;
17+
import io.vertx.core.json.Json;
18+
import io.vertx.openapi.contract.MediaType;
19+
import io.vertx.openapi.validation.ValidationContext;
20+
import io.vertx.openapi.validation.ValidatorException;
21+
22+
import static io.vertx.openapi.validation.ValidatorErrorType.ILLEGAL_VALUE;
23+
24+
/**
25+
* The content analyser is responsible for checking if the content is syntactically correct, and transforming the
26+
* content.
27+
* <p>
28+
* These two methods are intentionally bundled in {@link ContentAnalyser} to prevent some operations from having to
29+
* be performed twice. This is particularly helpful if a library is used that cannot distinguish between these steps.
30+
* In this case, an intermediate result that was generated in {@link #checkSyntacticalCorrectness()}, for example,
31+
* can be reused.
32+
* <p>
33+
* Therefore, it is very important to ensure that the {@link #checkSyntacticalCorrectness()} method is always called
34+
* before.
35+
*/
36+
public abstract class ContentAnalyser {
37+
private static class OctetStreamAnalyser extends ContentAnalyser {
38+
public OctetStreamAnalyser(String contentType, Buffer content, ValidationContext context) {
39+
super(contentType, content, context);
40+
}
41+
42+
@Override
43+
public void checkSyntacticalCorrectness() {
44+
// no syntax check for octet-stream
45+
}
46+
47+
@Override
48+
public Object transform() {
49+
return content;
50+
}
51+
}
52+
53+
/**
54+
* Returns the content analyser for the given content type.
55+
*
56+
* @param mediaType the media type to determine the content analyser.
57+
* @param contentType the raw content type value from the HTTP header field.
58+
* @param content the content to be analysed.
59+
* @return the content analyser for the given content type.
60+
*/
61+
public static ContentAnalyser getContentAnalyser(MediaType mediaType, String contentType, Buffer content,
62+
ValidationContext context) {
63+
switch (mediaType.getIdentifier()) {
64+
case MediaType.APPLICATION_JSON:
65+
case MediaType.APPLICATION_JSON_UTF8:
66+
case MediaType.APPLICATION_HAL_JSON:
67+
return new ApplicationJsonAnalyser(contentType, content, context);
68+
case MediaType.MULTIPART_FORM_DATA:
69+
return new MultipartFormAnalyser(contentType, content, context);
70+
case MediaType.APPLICATION_OCTET_STREAM:
71+
return new OctetStreamAnalyser(contentType, content, context);
72+
default:
73+
return null;
74+
}
75+
}
76+
77+
protected String contentType;
78+
protected Buffer content;
79+
protected ValidationContext requestOrResponse;
80+
81+
/**
82+
* Creates a new content analyser.
83+
*
84+
* @param contentType the content type.
85+
* @param content the content to be analysed.
86+
* @param context the context in which the content is used.
87+
*/
88+
public ContentAnalyser(String contentType, Buffer content, ValidationContext context) {
89+
this.contentType = contentType;
90+
this.content = content;
91+
this.requestOrResponse = context;
92+
}
93+
94+
/**
95+
* Checks if the content is syntactically correct.
96+
* <p>
97+
* Throws a {@link ValidatorException} if the content is syntactically incorrect.
98+
*/
99+
public abstract void checkSyntacticalCorrectness();
100+
101+
/**
102+
* Transforms the content into a format that can be validated by the
103+
* {@link io.vertx.openapi.validation.RequestValidator}, or {@link io.vertx.openapi.validation.ResponseValidator}.
104+
* <p>
105+
* Throws a {@link ValidatorException} if the content can't be transformed.
106+
*
107+
* @return the transformed content.
108+
*/
109+
public abstract Object transform();
110+
111+
/**
112+
* Builds a {@link ValidatorException} for the case that the content is syntactically incorrect.
113+
*
114+
* @param message the error message.
115+
* @return the {@link ValidatorException}.
116+
*/
117+
protected static ValidatorException buildSyntaxException(String message) {
118+
return new ValidatorException(message, ILLEGAL_VALUE);
119+
}
120+
121+
/**
122+
* Decodes the passed content as JSON.
123+
*
124+
* @return an object representing the passed JSON content.
125+
* @throws ValidatorException if the content can't be decoded.
126+
*/
127+
protected static Object decodeJsonContent(Buffer content, ValidationContext requestOrResponse) {
128+
try {
129+
return Json.decodeValue(content);
130+
} catch (DecodeException e) {
131+
throw buildSyntaxException("The " + requestOrResponse + " body can't be decoded");
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)