From 968af5857fcaa220d2e421998c51b9a4bf522820 Mon Sep 17 00:00:00 2001 From: Michael Glanznig Date: Mon, 6 Nov 2023 13:04:50 +0100 Subject: [PATCH] add possibility generate several specification files at once --- .../openapitools/codegen/config/Context.java | 15 ++++++- .../codegen/config/WorkflowSettings.java | 28 +++++++++++++ .../plugin/tasks/CommonGenerateCheckTask.kt | 4 ++ .../openapitools/codegen/ClientOptInput.java | 21 ++++++++++ .../openapitools/codegen/CodegenConfig.java | 7 ++++ .../codegen/DefaultGenerator.java | 12 +++--- .../codegen/config/CodegenConfigurator.java | 39 +++++++++++++++++-- 7 files changed, 116 insertions(+), 10 deletions(-) diff --git a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/Context.java b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/Context.java index 4aceaacb2c4e6..d1631cc6158ae 100644 --- a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/Context.java +++ b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/Context.java @@ -16,6 +16,8 @@ package org.openapitools.codegen.config; +import java.util.List; + /** * The Context used for generation. * @@ -23,6 +25,7 @@ */ public class Context { private TSpecDocument specDocument; + private List additionalSpecDocuments; private GeneratorSettings generatorSettings; private WorkflowSettings workflowSettings; @@ -33,8 +36,9 @@ public class Context { * @param generatorSettings the generator settings * @param workflowSettings the workflow settings */ - public Context(TSpecDocument specDocument, GeneratorSettings generatorSettings, WorkflowSettings workflowSettings) { + public Context(TSpecDocument specDocument, List additionalSpecDocuments, GeneratorSettings generatorSettings, WorkflowSettings workflowSettings) { this.specDocument = specDocument; + this.additionalSpecDocuments = additionalSpecDocuments; this.generatorSettings = generatorSettings; this.workflowSettings = workflowSettings; } @@ -57,6 +61,15 @@ public TSpecDocument getSpecDocument() { return specDocument; } + /** + * Gets the additional spec documents. + * + * @return the spec document + */ + public List getAdditionalSpecDocuments() { + return additionalSpecDocuments; + } + /** * Gets the workflow settings. These options are specific to "how" code gets generated (input, output directory, ignore files, template engine, etc). * diff --git a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/WorkflowSettings.java b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/WorkflowSettings.java index e9d9824673c7c..65cfbff54142f 100644 --- a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/WorkflowSettings.java +++ b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/WorkflowSettings.java @@ -50,6 +50,7 @@ public class WorkflowSettings { public static final Map DEFAULT_GLOBAL_PROPERTIES = Collections.unmodifiableMap(new HashMap<>()); private String inputSpec; + private List additionalSpecFiles = new ArrayList<>(); private String outputDir = DEFAULT_OUTPUT_DIR; private boolean verbose = DEFAULT_VERBOSE; private boolean skipOverwrite = DEFAULT_SKIP_OVERWRITE; @@ -68,6 +69,7 @@ public class WorkflowSettings { private WorkflowSettings(Builder builder) { this.inputSpec = builder.inputSpec; + this.additionalSpecFiles = builder.additionalSpecFiles; this.outputDir = builder.outputDir; this.verbose = builder.verbose; this.skipOverwrite = builder.skipOverwrite; @@ -100,6 +102,7 @@ public static Builder newBuilder() { public static Builder newBuilder(WorkflowSettings copy) { Builder builder = newBuilder(); builder.inputSpec = copy.getInputSpec(); + builder.additionalSpecFiles = copy.getAdditionalSpecFiles(); builder.outputDir = copy.getOutputDir(); builder.verbose = copy.isVerbose(); builder.skipOverwrite = copy.isSkipOverwrite(); @@ -132,6 +135,15 @@ public String getInputSpec() { return inputSpec; } + /** + * Gets additional input spec locations, as URL or file + * + * @return the additional input spec files + */ + public List getAdditionalSpecFiles() { + return additionalSpecFiles; + } + /** * Gets the output dir (where we write generated files). Defaults to the current directory. * @@ -298,6 +310,7 @@ public Map getGlobalProperties() { @SuppressWarnings("unused") public static final class Builder { private String inputSpec; + private List additionalSpecFiles = new ArrayList<>(); private String outputDir = DEFAULT_OUTPUT_DIR; private Boolean verbose = DEFAULT_VERBOSE; private Boolean skipOverwrite = DEFAULT_SKIP_OVERWRITE; @@ -333,6 +346,19 @@ public Builder withInputSpec(String inputSpec) { return this; } + /** + * Sets the {@code additionalSpecFiles} and returns a reference to this Builder so that the methods can be chained together. + * + * @param additionalSpecFiles the {@code additionalSpecFiles} to set + * @return a reference to this Builder + */ + public Builder withAdditionalSpecFiles(List additionalSpecFiles) { + if (additionalSpecFiles != null) { + this.additionalSpecFiles = additionalSpecFiles; + } + return this; + } + /** * Sets the {@code outputDir} and returns a reference to this Builder so that the methods can be chained together. * @@ -572,6 +598,7 @@ public WorkflowSettings build() { public String toString() { return "WorkflowSettings{" + "inputSpec='" + inputSpec + '\'' + + ", additionalSpecFiles='" + additionalSpecFiles + '\'' + ", outputDir='" + outputDir + '\'' + ", verbose=" + verbose + ", skipOverwrite=" + skipOverwrite + @@ -616,6 +643,7 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( getInputSpec(), + getAdditionalSpecFiles(), getOutputDir(), isVerbose(), isSkipOverwrite(), diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/CommonGenerateCheckTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/CommonGenerateCheckTask.kt index f9c5f0577e88b..767acd65bd5e8 100644 --- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/CommonGenerateCheckTask.kt +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/CommonGenerateCheckTask.kt @@ -646,6 +646,10 @@ open class CommonGenerateCheckTask : DefaultTask() { resolvedInputSpec = value } + if (additionalSpecFiles.isPresent) { + configurator.setAdditionalSpecFiles(additionalSpecFiles.get()) + } + remoteInputSpec.ifNotEmpty { value -> resolvedInputSpec = value } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/ClientOptInput.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/ClientOptInput.java index 519a390a0d5fd..438719ce1998b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/ClientOptInput.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/ClientOptInput.java @@ -31,6 +31,7 @@ public class ClientOptInput { private CodegenConfig config; private GeneratorSettings generatorSettings; private OpenAPI openAPI; + private List additionalSpecs; private List auths; // not deprecated as this is added to match other functionality, we need to move to Context instead of ClientOptInput. @Getter private List userDefinedTemplates; @@ -40,6 +41,11 @@ public ClientOptInput openAPI(OpenAPI openAPI) { return this; } + public ClientOptInput additionalSpecs(List additionalSpecs) { + this.setAdditionalSpecs(additionalSpecs); + return this; + } + public ClientOptInput generatorSettings(GeneratorSettings generatorSettings) { this.setGeneratorSettings(generatorSettings); return this; @@ -129,4 +135,19 @@ public void setOpenAPI(OpenAPI openAPI) { this.config.setOpenAPI(this.openAPI); } } + + public List getAdditionalSpecs() { + return additionalSpecs; + } + + /** + * Sets additional OpenAPI documents + * + * @deprecated use {@link #additionalSpecs} instead + * @param additionalSpecs the specifications + */ + @Deprecated + public void setAdditionalSpecs(List additionalSpecs) { + this.additionalSpecs = additionalSpecs; + } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java index 1c25a69f5dd32..c14d83d31c93d 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java @@ -33,6 +33,7 @@ import org.openapitools.codegen.model.WebhooksMap; import java.io.File; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -122,6 +123,12 @@ public interface CodegenConfig { void setInputSpec(String inputSpec); + default List getAdditionalSpecFiles() { + return Collections.emptyList(); + } + + default void setAdditionalSpecFiles(List additionalSpecFiles) {} + String getOutputDir(); void setOutputDir(String dir); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java index 157e927b37d88..461a5cfaff3ac 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java @@ -76,7 +76,7 @@ public class DefaultGenerator implements Generator { private static final String METADATA_DIR = ".openapi-generator"; protected final Logger LOGGER = LoggerFactory.getLogger(DefaultGenerator.class); - private final boolean dryRun; + protected final boolean dryRun; protected CodegenConfig config; protected ClientOptInput opts; protected OpenAPI openAPI; @@ -84,7 +84,7 @@ public class DefaultGenerator implements Generator { private Boolean generateApis = null; private Boolean generateModels = null; private Boolean generateRecursiveDependentModels = null; - private Boolean generateSupportingFiles = null; + protected Boolean generateSupportingFiles = null; private Boolean generateWebhooks = null; private Boolean generateApiTests = null; private Boolean generateApiDocumentation = null; @@ -328,7 +328,7 @@ void configureGeneratorProperties() { } } - private void configureOpenAPIInfo() { + void configureOpenAPIInfo() { Info info = this.openAPI.getInfo(); if (info == null) { return; @@ -1075,7 +1075,7 @@ private void generateOpenapiGeneratorIgnoreFile() { } } - private void generateSupportingFiles(List files, Map bundle) { + void generateSupportingFiles(List files, Map bundle) { if (!generateSupportingFiles) { // TODO: process these anyway and report via dryRun? LOGGER.info("Skipping generation of supporting files."); @@ -1389,7 +1389,7 @@ public void displayDryRunResults(Map fileStatusMap) { LOGGER.error(sb.toString()); } - private void processUserDefinedTemplates() { + void processUserDefinedTemplates() { // TODO: initial behavior is "merge" user defined with built-in templates. consider offering user a "replace" option. if (userDefinedTemplates != null && !userDefinedTemplates.isEmpty()) { Map supportingFilesMap = config.supportingFiles().stream() @@ -1993,7 +1993,7 @@ private Path absPath(File input) { * * @param files The list tracking generated files */ - private void generateFilesMetadata(List files) { + void generateFilesMetadata(List files) { if (generateMetadata) { try { StringBuilder sb = new StringBuilder(); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java index 90a35cd5641cd..b36b83ddb3f74 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java @@ -62,6 +62,7 @@ public class CodegenConfigurator { private String generatorName; private String inputSpec; + private List additionalSpecFiles = new ArrayList<>(); private String templatingEngineName; private Map globalProperties = new HashMap<>(); private Map instantiationTypes = new HashMap<>(); @@ -494,6 +495,12 @@ public CodegenConfigurator setInputSpec(String inputSpec) { return this; } + public CodegenConfigurator setAdditionalSpecFiles(List additionalSpecFiles) { + this.additionalSpecFiles = additionalSpecFiles; + workflowSettingsBuilder.withAdditionalSpecFiles(additionalSpecFiles); + return this; + } + public CodegenConfigurator setInstantiationTypes(Map instantiationTypes) { this.instantiationTypes = instantiationTypes; generatorSettingsBuilder.withInstantiationTypes(instantiationTypes); @@ -639,7 +646,7 @@ public CodegenConfigurator setVerbose(boolean verbose) { } @SuppressWarnings("WeakerAccess") - public Context toContext() { + public Context toContext() { Validate.notEmpty(generatorName, "generator name must be specified"); Validate.notEmpty(inputSpec, "input spec must be specified"); @@ -693,6 +700,13 @@ public Context toContext() { // https://github.com/swagger-api/swagger-parser/pull/1374 //ModelUtils.getOpenApiVersion(specification, inputSpec, authorizationValues); + final List additionalSpecifications = new ArrayList<>(); + for (final String additionalSpecFile : additionalSpecFiles) { + final SwaggerParseResult additionalResult = new OpenAPIParser().readLocation(additionalSpecFile, authorizationValues, options); + validationMessages.addAll(additionalResult.getMessages()); + additionalSpecifications.add(additionalResult.getOpenAPI()); + } + // NOTE: We will only expose errors+warnings if there are already errors in the spec. if (validationMessages.size() > 0) { Set warnings = new HashSet<>(); @@ -711,6 +725,22 @@ public Context toContext() { } } + int i = 0; + for (final OpenAPI additionalSpecification : additionalSpecifications) { + // Wrap the getUnusedSchemas() in try catch block so it catches the NPE + // when the input spec file is not correct + try { + List unusedModels = ModelUtils.getUnusedSchemas(additionalSpecification); + if (unusedModels != null) { + unusedModels.forEach(name -> warnings.add("Unused model: " + name)); + } + } catch (Exception e){ + System.err.println("[error] There is an error with OpenAPI specification parsed from the input spec file: " + additionalSpecFiles.get(i)); + System.err.println("[error] Please make sure the spec file has correct format and all required fields are populated with valid value."); + } + i++; + } + if (workflowSettings.isValidateSpec()) { String sb = "There were issues with the specification. The option can be disabled via validateSpec (Maven/Gradle) or --skip-validate-spec (CLI)." + System.lineSeparator(); @@ -737,11 +767,11 @@ public Context toContext() { } } - return new Context<>(specification, generatorSettings, workflowSettings); + return new Context<>(specification, additionalSpecifications, generatorSettings, workflowSettings); } public ClientOptInput toClientOptInput() { - Context context = toContext(); + Context context = toContext(); WorkflowSettings workflowSettings = context.getWorkflowSettings(); GeneratorSettings generatorSettings = context.getGeneratorSettings(); @@ -755,6 +785,7 @@ public ClientOptInput toClientOptInput() { // TODO: Work toward CodegenConfig having a "WorkflowSettings" property, or better a "Workflow" object which itself has a "WorkflowSettings" property. config.setInputSpec(workflowSettings.getInputSpec()); + config.setAdditionalSpecFiles(workflowSettings.getAdditionalSpecFiles()); config.setOutputDir(workflowSettings.getOutputDir()); config.setSkipOverwrite(workflowSettings.isSkipOverwrite()); config.setIgnoreFilePathOverride(workflowSettings.getIgnoreFileOverride()); @@ -808,6 +839,8 @@ public ClientOptInput toClientOptInput() { .generatorSettings(generatorSettings) .userDefinedTemplates(userDefinedTemplates); + input.additionalSpecs(context.getAdditionalSpecDocuments()); + return input.openAPI((OpenAPI)context.getSpecDocument()); } }