diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/serializer/ByteArraySerializer.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/serializer/ByteArraySerializer.java new file mode 100644 index 000000000000..d6714798f369 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/serializer/ByteArraySerializer.java @@ -0,0 +1,31 @@ +package org.openapitools.codegen.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * Serializes {@code byte[]} as a UTF-8 string, preserving the original base64 representation + * of an OpenAPI {@code format: byte} example. swagger-parser stores {@code format: byte} + * examples as the raw bytes of the input base64 string (not the decoded content), so writing + * those bytes back as a string round-trips to the user's original value. This prevents + * Jackson's default YAML behavior of emitting a {@code !!binary} block with a re-encoded payload. + * + *

Load-bearing assumption: this only round-trips correctly because swagger-parser stores the + * raw input bytes. If swagger-parser ever decodes {@code format: byte} examples internally, this + * serializer must switch to {@code Base64.getEncoder().encodeToString(value)}. + */ +public class ByteArraySerializer extends StdSerializer { + + public ByteArraySerializer() { + super(byte[].class); + } + + @Override + public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeString(new String(value, StandardCharsets.UTF_8)); + } +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/serializer/SerializerUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/serializer/SerializerUtils.java index 833ad2e24180..2799f9828d61 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/serializer/SerializerUtils.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/serializer/SerializerUtils.java @@ -66,6 +66,7 @@ public static String toJsonString(OpenAPI openAPI) { private static SimpleModule createModule() { SimpleModule module = new SimpleModule("OpenAPIModule"); module.addSerializer(OpenAPI.class, new OpenAPISerializer()); + module.addSerializer(byte[].class, new ByteArraySerializer()); return module; } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/yaml/YamlGeneratorTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/yaml/YamlGeneratorTest.java index 6c26f20f50f3..1bbc8dc39fce 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/yaml/YamlGeneratorTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/yaml/YamlGeneratorTest.java @@ -18,6 +18,8 @@ package org.openapitools.codegen.yaml; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.parameters.Parameter; import org.openapitools.codegen.ClientOptInput; import org.openapitools.codegen.DefaultGenerator; import org.openapitools.codegen.TestUtils; @@ -27,6 +29,7 @@ import org.testng.annotations.Test; import java.io.File; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; @@ -149,4 +152,37 @@ public void testIssue18622() throws Exception { Assert.assertEquals(actual.getComponents().getSchemas().get("myresponse").getExample(), expected.getComponents().getSchemas().get("myresponse").getExample()); } + + @Test + public void testIssue19929() throws Exception { + Map properties = new HashMap<>(); + properties.put(OpenAPIYamlGenerator.OUTPUT_NAME, "issue_19929.yaml"); + + File output = Files.createTempDirectory("issue_19929").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("openapi-yaml") + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/issue_19929.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + generator.opts(clientOptInput).generate(); + + Path generated = Path.of(output.getAbsolutePath(), "issue_19929.yaml"); + String generatedYaml = new String(Files.readAllBytes(generated), StandardCharsets.UTF_8); + + Assert.assertFalse(generatedYaml.contains("!!binary"), + "openapi-yaml output must not emit !!binary for format: byte examples. Output was:\n" + generatedYaml); + Assert.assertTrue(generatedYaml.contains("aGVsbG8K"), + "openapi-yaml output should preserve the original base64 example. Output was:\n" + generatedYaml); + + OpenAPI roundTripped = TestUtils.parseSpec(generated.toString()); + Parameter coordinates = roundTripped.getPaths().get("/operationsTest/").getGet().getParameters().get(0); + Schema dataSchema = (Schema) coordinates.getContent().get("application/json").getSchema().getProperties().get("data"); + byte[] exampleBytes = (byte[]) dataSchema.getExample(); + Assert.assertEquals(new String(exampleBytes, StandardCharsets.UTF_8), "aGVsbG8K"); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/issue_19929.yaml b/modules/openapi-generator/src/test/resources/3_0/issue_19929.yaml new file mode 100644 index 000000000000..0aa33bcb0c95 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/issue_19929.yaml @@ -0,0 +1,24 @@ +openapi: 3.0.3 +info: + title: TestRestService2.v2 + description: Hello + version: 2.2.2 +paths: + /operationsTest/: + get: + responses: + '200': + description: Empty response. + parameters: + - in: query + name: coordinates + content: + application/json: + schema: + type: object + properties: + data: + type: string + format: byte + description: 'Base64 encoded JSON with description of audited operation.' + example: 'aGVsbG8K'