Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class OpenAPINormalizer {
private TreeSet<String> anyTypeTreeSet = new TreeSet<>();

protected static final Logger LOGGER = LoggerFactory.getLogger(OpenAPINormalizer.class);
private static final String APPLICATION_OCTET_STREAM = "application/octet-stream";

Set<String> ruleNames = new TreeSet<>();
Set<String> rulesDefaultToTrue = new TreeSet<>();
Expand Down Expand Up @@ -919,6 +920,8 @@ public Schema normalizeSchema(Schema schema, Set<Schema> visitedSchemas) {
return schema;
}

normalizeBinaryContentSchema31(schema);

if (ModelUtils.isNullTypeSchema(openAPI, schema)) {
return schema;
}
Expand Down Expand Up @@ -2310,6 +2313,50 @@ protected Schema processNormalize31Spec(Schema schema, Set<Schema> visitedSchema
return schema;
}

private void normalizeBinaryContentSchema31(Schema<?> schema) {
if (!getRule(NORMALIZE_31SPEC)) {
return;
}
if (schema == null || schema.get$ref() != null) {
return;
}
if (StringUtils.isNotBlank(schema.getFormat()) || StringUtils.isNotBlank(schema.getContentEncoding())) {
return;
}
if (!isContentMediaType(schema.getContentMediaType(), APPLICATION_OCTET_STREAM)) {
return;
}
if (!isStringTypeOrTypeAbsent(schema)) {
return;
}

if (schema.getTypes() != null && !schema.getTypes().isEmpty()) {
schema.setType("string");
} else {
ModelUtils.setType(schema, "string");
}
schema.setFormat("binary");
}

private boolean isStringTypeOrTypeAbsent(Schema<?> schema) {
boolean hasType = StringUtils.isNotBlank(schema.getType());
boolean hasTypes = schema.getTypes() != null && !schema.getTypes().isEmpty();
if (!hasType && !hasTypes) {
return true;
}
if (hasType) {
return "string".equals(schema.getType());
}
return schema.getTypes().stream()
.map(String::valueOf)
.allMatch(type -> "string".equals(type) || "null".equals(type));
}

private boolean isContentMediaType(String actualContentMediaType, String expectedContentMediaType) {
String normalizedContentMediaType = StringUtils.substringBefore(actualContentMediaType, ";");
return StringUtils.equalsIgnoreCase(StringUtils.trim(normalizedContentMediaType), expectedContentMediaType);
}

private void normalizeExclusiveMinMax31(Schema<?> schema) {
if (schema == null || schema.get$ref() != null) return;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,46 @@ public void testDateTimeFormParameterHasDefaultValue() {
Assertions.assertNull(codegenParameter.getSchema());
}

@Test
public void testOAS31ContentMediaTypeBinaryFormParameter() {
final OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/binary-schema.yaml");
new OpenAPINormalizer(openAPI, Map.of("NORMALIZE_31SPEC", "true")).normalize();
new InlineModelResolver().flatten(openAPI);

final DefaultCodegen codegen = new DefaultCodegen();
codegen.setOpenAPI(openAPI);

RequestBody requestBody = openAPI.getPaths().get("/upload").getPost().getRequestBody();
List<CodegenParameter> formParams = codegen.fromRequestBodyToFormParameters(requestBody, new HashSet<>());
Map<String, CodegenParameter> paramsByBaseName = formParams.stream()
.collect(Collectors.toMap(param -> param.baseName, param -> param));

CodegenParameter file = paramsByBaseName.get("file");
assertTrue(file.isFormParam);
assertTrue(file.isBinary);
assertTrue(file.isFile);

CodegenParameter nullableFile = paramsByBaseName.get("nullableFile");
assertTrue(nullableFile.isFormParam);
assertTrue(nullableFile.isBinary);
assertTrue(nullableFile.isFile);

CodegenParameter encodedFile = paramsByBaseName.get("encodedFile");
assertTrue(encodedFile.isFormParam);
assertFalse(encodedFile.isBinary);
assertFalse(encodedFile.isFile);

CodegenParameter inferredFile = paramsByBaseName.get("inferredFile");
assertTrue(inferredFile.isFormParam);
assertTrue(inferredFile.isBinary);
assertTrue(inferredFile.isFile);

CodegenParameter image = paramsByBaseName.get("image");
assertTrue(image.isFormParam);
assertFalse(image.isBinary);
assertFalse(image.isFile);
}

@Test
public void testOriginalOpenApiDocumentVersion() {
// Test with OAS 2.0 document.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,38 @@ public void testNormalize31Schema() {
assertNotNull(petSchema.getTypes());
}

@Test
public void testNormalize31BinaryContentMediaType() {
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/binary-schema.yaml");

OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, Map.of("NORMALIZE_31SPEC", "true"));
openAPINormalizer.normalize();

Map<String, Schema> properties = ModelUtils.getSchema(openAPI, "UploadBody").getProperties();

Schema file = properties.get("file");
assertEquals(file.getType(), "string");
assertEquals(file.getFormat(), "binary");

Schema nullableFile = properties.get("nullableFile");
assertEquals(nullableFile.getType(), "string");
assertEquals(nullableFile.getFormat(), "binary");
assertTrue(nullableFile.getNullable());

Schema inferredFile = properties.get("inferredFile");
assertEquals(ModelUtils.getType(inferredFile), "string");
assertEquals(inferredFile.getType(), "string");
assertEquals(inferredFile.getFormat(), "binary");

Schema encodedFile = properties.get("encodedFile");
assertEquals(encodedFile.getType(), "string");
assertNull(encodedFile.getFormat());

Schema image = properties.get("image");
assertEquals(image.getType(), "string");
assertNull(image.getFormat());
}

@Test
public void testNormalize31Parameters() {
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/common-parameters.yaml");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
openapi: 3.1.0
info:
title: t
version: 1.0.0
paths:
/upload:
post:
operationId: upload
requestBody:
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/UploadBody'
responses:
'200':
description: ok
components:
schemas:
UploadBody:
type: object
properties:
file:
type: string
contentMediaType: application/octet-stream
nullableFile:
type:
- string
- 'null'
contentMediaType: application/octet-stream
encodedFile:
type: string
contentEncoding: base64
contentMediaType: application/octet-stream
inferredFile:
contentMediaType: application/octet-stream
image:
type: string
contentMediaType: image/png
required:
- file