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 @@ -22,6 +22,7 @@
import org.openapitools.codegen.*;
import org.openapitools.codegen.meta.features.*;
import org.openapitools.codegen.serializer.SerializerUtils;
import org.openapitools.codegen.utils.OpenAPISorter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -31,10 +32,12 @@

public class OpenAPIGenerator extends DefaultCodegen implements CodegenConfig {
public static final String OUTPUT_NAME = "outputFileName";
public static final String SORT_OUTPUT = "sortOutput";

private final Logger LOGGER = LoggerFactory.getLogger(OpenAPIGenerator.class);

protected String outputFileName = "openapi.json";
protected boolean sortOutput = false;

public OpenAPIGenerator() {
super();
Expand All @@ -55,6 +58,10 @@ public OpenAPIGenerator() {
supportingFiles.add(new SupportingFile("README.md", "", "README.md"));

cliOptions.add(CliOption.newString(OUTPUT_NAME, "Output file name").defaultValue(outputFileName));
cliOptions.add(CliOption.newBoolean(SORT_OUTPUT,
"Sort paths alphabetically, schemas/parameters by name, and HTTP methods in classical order "
+ "(GET, PUT, POST, DELETE, OPTIONS, HEAD, PATCH, TRACE).")
.defaultValue(Boolean.FALSE.toString()));
}

@Override
Expand All @@ -80,11 +87,18 @@ public void processOpts() {
outputFileName = additionalProperties.get(OUTPUT_NAME).toString();
}
LOGGER.info("Output file name [outputFileName={}]", outputFileName);

if (additionalProperties.containsKey(SORT_OUTPUT)) {
sortOutput = Boolean.parseBoolean(additionalProperties.get(SORT_OUTPUT).toString());
}
}

@Override
public void processOpenAPI(OpenAPI openAPI) {
String jsonOpenAPI = SerializerUtils.toJsonString(openAPI);
if (sortOutput) {
OpenAPISorter.sort(openAPI);
}
String jsonOpenAPI = SerializerUtils.toJsonString(openAPI, sortOutput);

try {
String outputFile = outputFolder + File.separator + outputFileName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@

import com.google.common.collect.ImmutableMap;
import com.samskivert.mustache.Mustache.Lambda;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import org.openapitools.codegen.*;
import org.openapitools.codegen.meta.features.*;
import org.openapitools.codegen.serializer.SerializerUtils;
import org.openapitools.codegen.templating.mustache.OnChangeLambda;
import org.openapitools.codegen.utils.OpenAPISorter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -33,10 +36,12 @@

public class OpenAPIYamlGenerator extends DefaultCodegen implements CodegenConfig {
public static final String OUTPUT_NAME = "outputFile";
public static final String SORT_OUTPUT = "sortOutput";

private final Logger LOGGER = LoggerFactory.getLogger(OpenAPIYamlGenerator.class);

protected String outputFile = "openapi/openapi.yaml";
protected boolean sortOutput = false;

public OpenAPIYamlGenerator() {
super();
Expand All @@ -54,6 +59,10 @@ public OpenAPIYamlGenerator() {
embeddedTemplateDir = templateDir = "openapi-yaml";
outputFolder = "generated-code/openapi-yaml";
cliOptions.add(CliOption.newString(OUTPUT_NAME, "Output filename").defaultValue(outputFile));
cliOptions.add(CliOption.newBoolean(SORT_OUTPUT,
"Sort paths alphabetically, schemas/parameters by name, and HTTP methods in classical order "
+ "(GET, PUT, POST, DELETE, OPTIONS, HEAD, PATCH, TRACE).")
.defaultValue(Boolean.FALSE.toString()));
supportingFiles.add(new SupportingFile("README.md", "", "README.md"));
}

Expand All @@ -80,6 +89,17 @@ public void processOpts() {
}
LOGGER.info("Output file [outputFile={}]", outputFile);
supportingFiles.add(new SupportingFile("openapi.mustache", outputFile));

if (additionalProperties.containsKey(SORT_OUTPUT)) {
sortOutput = Boolean.parseBoolean(additionalProperties.get(SORT_OUTPUT).toString());
}
}

@Override
public void processOpenAPI(OpenAPI openAPI) {
if (sortOutput) {
OpenAPISorter.sort(openAPI);
}
}

@Override
Expand All @@ -100,6 +120,15 @@ public void addOperationToGroup(String tag, String resourcePath, Operation opera
opList.add(co);
}

@Override
public void generateYAMLSpecFile(Map<String, Object> objs) {
OpenAPI openAPI = (OpenAPI) objs.get("openAPI");
String yaml = SerializerUtils.toYamlString(openAPI, sortOutput);
if (yaml != null) {
objs.put("openapi-yaml", yaml);
}
}

@Override
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
generateYAMLSpecFile(objs);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.openapitools.codegen.serializer;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import io.swagger.v3.oas.models.PathItem;

import java.io.IOException;
import java.util.Map;

/**
* Serializes a {@link PathItem} with HTTP methods written in classical OpenAPI spec order:
* GET, PUT, POST, DELETE, OPTIONS, HEAD, PATCH, TRACE — instead of Jackson's default
* alphabetical order (which would put DELETE before GET).
*/
public class PathItemSerializer extends JsonSerializer<PathItem> {

@Override
public void serialize(PathItem value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
if (value.getSummary() != null) {
gen.writeStringField("summary", value.getSummary());
}
if (value.getDescription() != null) {
gen.writeStringField("description", value.getDescription());
}
// HTTP methods in classical OpenAPI spec order
if (value.getGet() != null) {
gen.writeObjectField("get", value.getGet());
}
if (value.getPut() != null) {
gen.writeObjectField("put", value.getPut());
}
if (value.getPost() != null) {
gen.writeObjectField("post", value.getPost());
}
if (value.getDelete() != null) {
gen.writeObjectField("delete", value.getDelete());
}
if (value.getOptions() != null) {
gen.writeObjectField("options", value.getOptions());
}
if (value.getHead() != null) {
gen.writeObjectField("head", value.getHead());
}
if (value.getPatch() != null) {
gen.writeObjectField("patch", value.getPatch());
}
if (value.getTrace() != null) {
gen.writeObjectField("trace", value.getTrace());
}
if (value.getServers() != null) {
gen.writeObjectField("servers", value.getServers());
}
if (value.getParameters() != null) {
gen.writeObjectField("parameters", value.getParameters());
}
if (value.getExtensions() != null) {
for (Map.Entry<String, Object> e : value.getExtensions().entrySet()) {
gen.writeObjectField(e.getKey(), e.getValue());
}
}
gen.writeEndObject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.swagger.v3.core.util.Json;
import io.swagger.v3.core.util.Yaml;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.PathItem;
import org.openapitools.codegen.config.GlobalSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -19,10 +20,14 @@ public class SerializerUtils {
private static final boolean minimizeYamlQuotes = Boolean.parseBoolean(GlobalSettings.getProperty(YAML_MINIMIZE_QUOTES_PROPERTY, "true"));

public static String toYamlString(OpenAPI openAPI) {
return toYamlString(openAPI, false);
}

public static String toYamlString(OpenAPI openAPI, boolean sortOutput) {
if (openAPI == null) {
return null;
}
SimpleModule module = createModule();
SimpleModule module = createModule(sortOutput);
try {
ObjectMapper yamlMapper = Yaml.mapper().copy();
// there is an unfortunate YAML condition where user inputs should be treated as strings (e.g. "1234_1234"), but in yaml this is a valid number and
Expand All @@ -44,11 +49,15 @@ public static String toYamlString(OpenAPI openAPI) {
}

public static String toJsonString(OpenAPI openAPI) {
return toJsonString(openAPI, false);
}

public static String toJsonString(OpenAPI openAPI, boolean sortOutput) {
if (openAPI == null) {
return null;
}

SimpleModule module = createModule();
SimpleModule module = createModule(sortOutput);
try {
return Json.mapper()
.copy()
Expand All @@ -63,10 +72,13 @@ public static String toJsonString(OpenAPI openAPI) {
return null;
}

private static SimpleModule createModule() {
private static SimpleModule createModule(boolean sortOutput) {
SimpleModule module = new SimpleModule("OpenAPIModule");
module.addSerializer(OpenAPI.class, new OpenAPISerializer());
module.addSerializer(byte[].class, new ByteArraySerializer());
if (sortOutput) {
module.addSerializer(PathItem.class, new PathItemSerializer());
}
return module;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package org.openapitools.codegen.utils;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Paths;

import java.util.TreeMap;

/**
* Utility for sorting an {@link OpenAPI} model in-place before serialization.
*
* <ul>
* <li>Paths are sorted alphabetically by path string.</li>
* <li>All component maps (schemas, parameters, requestBodies, responses, headers,
* securitySchemes, examples, links, callbacks) are sorted alphabetically by name.</li>
* <li>HTTP method ordering within a path is handled by {@link org.openapitools.codegen.serializer.PathItemSerializer},
* which writes operations in classical spec order: GET, PUT, POST, DELETE, OPTIONS, HEAD, PATCH, TRACE.</li>
* </ul>
*/
public class OpenAPISorter {

private OpenAPISorter() {
}

public static void sort(OpenAPI openAPI) {
if (openAPI == null) {
return;
}
sortPaths(openAPI);
sortComponents(openAPI);
}

private static void sortPaths(OpenAPI openAPI) {
if (openAPI.getPaths() == null || openAPI.getPaths().isEmpty()) {
return;
}
Paths sorted = new Paths();
openAPI.getPaths().entrySet().stream()
.sorted(java.util.Map.Entry.comparingByKey())
.forEach(e -> sorted.addPathItem(e.getKey(), e.getValue()));
if (openAPI.getPaths().getExtensions() != null) {
openAPI.getPaths().getExtensions().forEach(sorted::addExtension);
}
openAPI.setPaths(sorted);
}

private static void sortComponents(OpenAPI openAPI) {
Components c = openAPI.getComponents();
if (c == null) {
return;
}
if (c.getSchemas() != null) {
c.setSchemas(new TreeMap<>(c.getSchemas()));
}
if (c.getParameters() != null) {
c.setParameters(new TreeMap<>(c.getParameters()));
}
if (c.getRequestBodies() != null) {
c.setRequestBodies(new TreeMap<>(c.getRequestBodies()));
}
if (c.getResponses() != null) {
c.setResponses(new TreeMap<>(c.getResponses()));
}
if (c.getHeaders() != null) {
c.setHeaders(new TreeMap<>(c.getHeaders()));
}
if (c.getSecuritySchemes() != null) {
c.setSecuritySchemes(new TreeMap<>(c.getSecuritySchemes()));
}
if (c.getExamples() != null) {
c.setExamples(new TreeMap<>(c.getExamples()));
}
if (c.getLinks() != null) {
c.setLinks(new TreeMap<>(c.getLinks()));
}
if (c.getCallbacks() != null) {
c.setCallbacks(new TreeMap<>(c.getCallbacks()));
}
}
}
Loading
Loading