🐛 Fixing some issues with threading and NPE (#5107)

* 🐛 Fixing some issues with threading and NPE

After running Sonar on the master branch, some major analysis
opportunities were displayed.

This fixes the use of SimpleDateFormat stored as static fields.
SimpleDateFormat is not thread-safe, and may retain data across threads.
While there's no indicator that this has caused any issues (these are
mostly used for example code), we should follow these best practices.

This also fixes a handful of NPE and other minor issues such as
comparing Boolean.TRUE to strings and no wrapping some closeables in
try-with-resources.

* [cli] Unit test GenerateBatch custom deserialization helper

* Quiet batch mode in sonar.yml

* Suppress unnecessary warnings (ThreadLocals in static fields)
This commit is contained in:
Jim Schubert 2020-01-25 18:28:16 -05:00 committed by GitHub
parent ad4a9df328
commit c9ec084418
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 380 additions and 110 deletions

View File

@ -21,4 +21,4 @@ jobs:
- name: Jacoco Aggregate - name: Jacoco Aggregate
run: mvn jacoco:report-aggregate run: mvn jacoco:report-aggregate
- name: Publish to Sonar - name: Publish to Sonar
run: mvn sonar:sonar -Dsonar.projectKey=OpenAPITools_openapi-generator -Dsonar.organization=openapitools -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=${{ secrets.SONAR_LOGIN }} -Dsonar.branch.name=${GITHUB_REF##*/} run: mvn -B -q sonar:sonar -Dsonar.projectKey=OpenAPITools_openapi-generator -Dsonar.organization=openapitools -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=${{ secrets.SONAR_LOGIN }} -Dsonar.branch.name=${GITHUB_REF##*/}

View File

@ -1,5 +1,5 @@
{ {
"!include": "./bin/jaxrs-datelib-j8.json", "!include": "bin/jaxrs-datelib-j8.json",
"generatorName": "jaxrs-jersey", "generatorName": "jaxrs-jersey",
"inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml", "inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml",
"outputDir": "samples/server/petstore/jaxrs-datelib-j8/", "outputDir": "samples/server/petstore/jaxrs-datelib-j8/",

View File

@ -1,5 +1,5 @@
{ {
"!include": "./bin/jaxrs-resteasy-eap-java8-petstore-server.json", "!include": "bin/jaxrs-resteasy-eap-java8-petstore-server.json",
"artifactId": "jaxrs-resteasy-eap-java8-server", "artifactId": "jaxrs-resteasy-eap-java8-server",
"generatorName": "jaxrs-resteasy-eap", "generatorName": "jaxrs-resteasy-eap",
"inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore.yaml", "inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore.yaml",

View File

@ -1,5 +1,5 @@
{ {
"!include": "./bin/jaxrs-resteasy-eap-joda-petstore-server.json", "!include": "bin/jaxrs-resteasy-eap-joda-petstore-server.json",
"artifactId": "jaxrs-resteasy-eap-joda-server", "artifactId": "jaxrs-resteasy-eap-joda-server",
"generatorName": "jaxrs-resteasy-eap", "generatorName": "jaxrs-resteasy-eap",
"inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore.yaml", "inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore.yaml",

View File

@ -1,5 +1,5 @@
{ {
"!include": "./bin/jaxrs-resteasy-joda-petstore-server.json", "!include": "bin/jaxrs-resteasy-joda-petstore-server.json",
"artifactId": "jaxrs-resteasy-joda-server", "artifactId": "jaxrs-resteasy-joda-server",
"generatorName": "jaxrs-resteasy", "generatorName": "jaxrs-resteasy",
"inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore.yaml", "inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore.yaml",

View File

@ -123,27 +123,12 @@ public class GenerateBatch implements Runnable {
} }
} }
LOGGER.info(String.format(Locale.ROOT, "Batch generation using up to %d threads.\nIncludes: %s\nRoot: %s", numThreads, includesDir.getAbsolutePath(), rootDir.toAbsolutePath().toString())); LOGGER.info(String.format(Locale.ROOT, "Batch generation using up to %d threads.\nIncludes: %s\nRoot: %s", numThreads, includesDir.getAbsolutePath(), rootDir.toAbsolutePath().toString()));
// Create a module which loads our config files, but supports a special "!include" key which can point to an existing config file. // Create a module which loads our config files, but supports a special "!include" key which can point to an existing config file.
// This allows us to create a sort of meta-config which holds configs which are otherwise required at CLI time (via generate task). // This allows us to create a sort of meta-config which holds configs which are otherwise required at CLI time (via generate task).
// That is, this allows us to create a wrapper config for generatorName, inputSpec, outputDir, etc. // That is, this allows us to create a wrapper config for generatorName, inputSpec, outputDir, etc.
SimpleModule module = new SimpleModule("GenerateBatch"); SimpleModule module = getCustomDeserializationModel(includesDir);
module.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription bd, JsonDeserializer<?> original) {
JsonDeserializer<?> result;
if (bd.getBeanClass() == DynamicSettings.class) {
result = new DynamicSettingsRefSupport(original, includesDir);
} else {
result = original;
}
return result;
}
});
List<CodegenConfigurator> configurators = configs.stream().map(config -> CodegenConfigurator.fromFile(config, module)).collect(Collectors.toList()); List<CodegenConfigurator> configurators = configs.stream().map(config -> CodegenConfigurator.fromFile(config, module)).collect(Collectors.toList());
// it doesn't make sense to interleave INFO level logs, so limit these to only ERROR. // it doesn't make sense to interleave INFO level logs, so limit these to only ERROR.
@ -169,6 +154,8 @@ public class GenerateBatch implements Runnable {
System.out.println("COMPLETE."); System.out.println("COMPLETE.");
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
// re-interrupt
Thread.currentThread().interrupt();
} }
} }
@ -227,6 +214,28 @@ public class GenerateBatch implements Runnable {
} }
} }
static SimpleModule getCustomDeserializationModel(final File includesDir) {
// Create a module which loads our config files, but supports a special "!include" key which can point to an existing config file.
// This allows us to create a sort of meta-config which holds configs which are otherwise required at CLI time (via generate task).
// That is, this allows us to create a wrapper config for generatorName, inputSpec, outputDir, etc.
SimpleModule module = new SimpleModule("GenerateBatch");
module.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription bd, JsonDeserializer<?> original) {
JsonDeserializer<?> result;
if (bd.getBeanClass() == DynamicSettings.class) {
result = new DynamicSettingsRefSupport(original, includesDir);
} else {
result = original;
}
return result;
}
});
return module;
}
static class DynamicSettingsRefSupport extends DelegatingDeserializer { static class DynamicSettingsRefSupport extends DelegatingDeserializer {
private static final String INCLUDE = "!include"; private static final String INCLUDE = "!include";
private File scanDir; private File scanDir;
@ -255,11 +264,13 @@ public class GenerateBatch implements Runnable {
// load the file into the tree node and continue parsing as normal // load the file into the tree node and continue parsing as normal
((ObjectNode) node).remove(INCLUDE); ((ObjectNode) node).remove(INCLUDE);
JsonParser includeParser = codec.getFactory().createParser(includeFile); TreeNode includeNode;
TreeNode includeNode = includeParser.readValueAsTree(); try (JsonParser includeParser = codec.getFactory().createParser(includeFile)) {
includeNode = includeParser.readValueAsTree();
}
ObjectReader reader = codec.readerForUpdating(node); ObjectReader reader = codec.readerForUpdating(node);
TreeNode updated = reader.readValue(includeFile); TreeNode updated = reader.readValue(includeNode.traverse());
JsonParser updatedParser = updated.traverse(); JsonParser updatedParser = updated.traverse();
updatedParser.nextToken(); updatedParser.nextToken();
return super.deserialize(updatedParser, ctx); return super.deserialize(updatedParser, ctx);

View File

@ -0,0 +1,102 @@
package org.openapitools.codegen.cmd;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.openapitools.codegen.config.CodegenConfigurator;
import org.openapitools.codegen.config.Context;
import org.openapitools.codegen.config.GeneratorSettings;
import org.openapitools.codegen.config.WorkflowSettings;
import org.testng.ITestContext;
import org.testng.TestRunner;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static org.testng.Assert.*;
@SuppressWarnings("RedundantThrows")
public class GenerateBatchTest {
private static final String SPEC_FILE = "batch/specs/petstore.yaml";
private static final String JAXRS_DATELIB_J8_JSON = "jaxrs-datelib-j8.json";
private static final String JAXRS_DATELIB_J8_YAML = "jaxrs-datelib-j8.yaml";
private static final String JAXRS_DATELIB_J8_YAML_INCLUDE_JSON = "jaxrs-datelib-j8-yaml-include.json";
private static final String JAXRS_DATELIB_J8_JSON_INCLUDE_YAML = "jaxrs-datelib-j8-json-include.yaml";
Path workingDirectory;
@BeforeTest
public void setUp(ITestContext ctx) throws IOException {
workingDirectory = Paths.get("src", "test", "resources", "batch");
}
@DataProvider(name = "customIncludeDeserializerFiles")
public Object[][] customIncludeDeserializerFiles() {
return new Object[][] {
{JAXRS_DATELIB_J8_JSON},
{JAXRS_DATELIB_J8_YAML},
{JAXRS_DATELIB_J8_JSON_INCLUDE_YAML}
};
}
@Test(dataProvider = "customIncludeDeserializerFiles")
public void testDeserializerWithJsonInclude(String file) throws IOException {
String config = getTargetResourceAsFile(file).toString();
SimpleModule module = GenerateBatch.getCustomDeserializationModel(getIncludesDir());
CodegenConfigurator loaded = CodegenConfigurator.fromFile(config, module);
Map<String, Object> expectedAdditionalProperties = new HashMap<>();
expectedAdditionalProperties.put("serverPort", "8082");
expectedAdditionalProperties.put("dateLibrary", "java8");
expectedAdditionalProperties.put("hideGenerationTimestamp", true);
expectedAdditionalProperties.put("serializableModel", true);
expectedAdditionalProperties.put("withXml", true);
expectedAdditionalProperties.put("java8", true);
expectedAdditionalProperties.put("useBeanValidation", true);
assertNotNull(loaded);
Context<?> context = loaded.toContext();
WorkflowSettings workflowSettings = context.getWorkflowSettings();
GeneratorSettings generatorSettings = context.getGeneratorSettings();
assertNotNull(workflowSettings);
assertNotNull(generatorSettings);
assertEquals(generatorSettings.getGeneratorName(), "jaxrs-jersey");
assertEquals(workflowSettings.getOutputDir(), "outputDir");
assertEquals(workflowSettings.getInputSpec(), SPEC_FILE);
assertTrue(generatorSettings.getAdditionalProperties().size() >= 7);
Set<Map.Entry<String, Object>> actualSet = generatorSettings.getAdditionalProperties().entrySet();
assertTrue(actualSet.containsAll(expectedAdditionalProperties.entrySet()));
}
@SuppressWarnings("unused")
@Test(
expectedExceptions = { RuntimeException.class },
expectedExceptionsMessageRegExp = "Unable to deserialize config file: .*"
)
public void testInvalidDeserializerWithIncludeOption() {
// JSON is valid YAML, but not the other way around, so we can't load a YAML include from a JSON config
// to do so would require additional work.
String config = getTargetResourceAsFile(JAXRS_DATELIB_J8_YAML_INCLUDE_JSON).toString();
SimpleModule module = GenerateBatch.getCustomDeserializationModel(getIncludesDir());
CodegenConfigurator loaded = CodegenConfigurator.fromFile(config, module);
fail("Expected an exception when trying to load a YAML include from a JSON file");
}
private File getIncludesDir() {
// The includes directory would be "batch" under resources here, as everything is relative to this directory.
return workingDirectory.toFile();
}
private File getTargetResourceAsFile(String relative) {
return workingDirectory.resolve(relative).toAbsolutePath().toFile();
}
}

View File

@ -0,0 +1,7 @@
{
"serializableModel": true,
"withXml": true,
"dateLibrary": "java8",
"java8": true,
"useBeanValidation": true
}

View File

@ -0,0 +1,6 @@
---
serializableModel: true
withXml: true
dateLibrary: java8
java8: true
useBeanValidation: true

View File

@ -0,0 +1,8 @@
---
"!include": common/jaxrs-datelib-j8.json
generatorName: jaxrs-jersey
inputSpec: batch/specs/petstore.yaml
outputDir: outputDir
additionalProperties:
hideGenerationTimestamp: true
serverPort: '8082'

View File

@ -0,0 +1,10 @@
{
"!include": "common/jaxrs-datelib-j8.yaml",
"generatorName": "jaxrs-jersey",
"inputSpec": "batch/specs/petstore.yaml",
"outputDir": "outputDir",
"additionalProperties": {
"hideGenerationTimestamp": true,
"serverPort": "8082"
}
}

View File

@ -0,0 +1,10 @@
{
"!include": "common/jaxrs-datelib-j8.json",
"generatorName": "jaxrs-jersey",
"inputSpec": "batch/specs/petstore.yaml",
"outputDir": "outputDir",
"additionalProperties": {
"hideGenerationTimestamp": true,
"serverPort": "8082"
}
}

View File

@ -0,0 +1,8 @@
---
"!include": common/jaxrs-datelib-j8.yaml
generatorName: jaxrs-jersey
inputSpec: batch/specs/petstore.yaml
outputDir: outputDir
additionalProperties:
hideGenerationTimestamp: true
serverPort: '8082'

View File

@ -0,0 +1,111 @@
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
servers:
- url: http://petstore.swagger.io/v1
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: limit
in: query
description: How many items to return at one time (max 100)
required: false
schema:
type: integer
format: int32
responses:
'200':
description: A paged array of pets
headers:
x-next:
description: A link to the next page of responses
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/Pets"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
summary: Create a pet
operationId: createPets
tags:
- pets
responses:
'201':
description: Null response
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/pets/{petId}:
get:
summary: Info for a specific pet
operationId: showPetById
tags:
- pets
parameters:
- name: petId
in: path
required: true
description: The id of the pet to retrieve
schema:
type: string
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
Pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Pets:
type: array
items:
$ref: "#/components/schemas/Pet"
Error:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string

View File

@ -777,12 +777,13 @@ public class CodeGenMojo extends AbstractMojo {
conn.setRequestProperty(auth.getKeyName(), auth.getValue()); conn.setRequestProperty(auth.getKeyName(), auth.getValue());
} }
} }
ReadableByteChannel readableByteChannel = Channels.newChannel(conn.getInputStream()); try (ReadableByteChannel readableByteChannel = Channels.newChannel(conn.getInputStream())) {
FileChannel fileChannel;
FileOutputStream fileOutputStream = new FileOutputStream(inputSpecTempFile); try (FileOutputStream fileOutputStream = new FileOutputStream(inputSpecTempFile)) {
FileChannel fileChannel = fileOutputStream.getChannel(); fileChannel = fileOutputStream.getChannel();
fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE); }
}
} }
ByteSource inputSpecByteSource = ByteSource inputSpecByteSource =

View File

@ -49,7 +49,9 @@ public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen imp
public static final String NPM_VERSION = "npmVersion"; public static final String NPM_VERSION = "npmVersion";
public static final String SNAPSHOT = "snapshot"; public static final String SNAPSHOT = "snapshot";
protected static final SimpleDateFormat SNAPSHOT_SUFFIX_FORMAT = new SimpleDateFormat("yyyyMMddHHmm", Locale.ROOT); // NOTE: SimpleDateFormat is not thread-safe and may not be static unless it is thread-local
@SuppressWarnings("squid:S5164")
protected static final ThreadLocal<SimpleDateFormat> SNAPSHOT_SUFFIX_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmm", Locale.ROOT));
protected String modelPropertyNaming = "camelCase"; protected String modelPropertyNaming = "camelCase";
protected Boolean supportsES6 = false; protected Boolean supportsES6 = false;
@ -158,7 +160,7 @@ public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen imp
" Required to generate a full package")); " Required to generate a full package"));
this.cliOptions.add(new CliOption(NPM_VERSION, "The version of your npm package. If not provided, using the version from the OpenAPI specification file.").defaultValue(this.getNpmVersion())); this.cliOptions.add(new CliOption(NPM_VERSION, "The version of your npm package. If not provided, using the version from the OpenAPI specification file.").defaultValue(this.getNpmVersion()));
this.cliOptions.add(CliOption.newBoolean(SNAPSHOT, this.cliOptions.add(CliOption.newBoolean(SNAPSHOT,
"When setting this property to true, the version will be suffixed with -SNAPSHOT." + this.SNAPSHOT_SUFFIX_FORMAT.toPattern(), "When setting this property to true, the version will be suffixed with -SNAPSHOT." + this.SNAPSHOT_SUFFIX_FORMAT.get().toPattern(),
false)); false));
} }
@ -204,9 +206,9 @@ public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen imp
if (additionalProperties.containsKey(SNAPSHOT) && Boolean.parseBoolean(additionalProperties.get(SNAPSHOT).toString())) { if (additionalProperties.containsKey(SNAPSHOT) && Boolean.parseBoolean(additionalProperties.get(SNAPSHOT).toString())) {
if (npmVersion.toUpperCase(Locale.ROOT).matches("^.*-SNAPSHOT$")) { if (npmVersion.toUpperCase(Locale.ROOT).matches("^.*-SNAPSHOT$")) {
this.setNpmVersion(npmVersion + "." + SNAPSHOT_SUFFIX_FORMAT.format(new Date())); this.setNpmVersion(npmVersion + "." + SNAPSHOT_SUFFIX_FORMAT.get().format(new Date()));
} else { } else {
this.setNpmVersion(npmVersion + "-SNAPSHOT." + SNAPSHOT_SUFFIX_FORMAT.format(new Date())); this.setNpmVersion(npmVersion + "-SNAPSHOT." + SNAPSHOT_SUFFIX_FORMAT.get().format(new Date()));
} }
} }
additionalProperties.put(NPM_VERSION, npmVersion); additionalProperties.put(NPM_VERSION, npmVersion);

View File

@ -25,24 +25,11 @@ import java.math.BigDecimal;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;
import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.CliOption; import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenModel; import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.CodegenOperation; import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenParameter; import org.openapitools.codegen.CodegenParameter;
@ -275,10 +262,25 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF
private static final String INDENT = " "; private static final String INDENT = " ";
private static final SimpleDateFormat ISO8601_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); // SimpleDateFormat is not thread-safe, and may not be stored in a static field unless stored by ThreadLocal.
// It's not enough to add a ThreadLocal at the usage site.
@SuppressWarnings("squid:S5164")
private static final ThreadLocal<SimpleDateFormat> ISO8601_DATE_FORMAT = ThreadLocal.withInitial(() ->
{
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
f.setTimeZone(TimeZone.getTimeZone("UTC"));
return f;
});
private static final SimpleDateFormat ISO8601_DATETIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", // SimpleDateFormat is not thread-safe, and may not be stored in a static field unless stored by ThreadLocal.
Locale.getDefault()); // It's not enough to add a ThreadLocal at the usage site.
@SuppressWarnings("squid:S5164")
private static final ThreadLocal<SimpleDateFormat> ISO8601_DATETIME_FORMAT = ThreadLocal.withInitial(() ->
{
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.getDefault());
f.setTimeZone(TimeZone.getTimeZone("UTC"));
return f;
});
private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000; private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
@ -292,13 +294,11 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF
"LocalDateTime", "LocalDate"); "LocalDateTime", "LocalDate");
static { static {
ISO8601_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
ISO8601_DATETIME_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
long minDate = 0; long minDate = 0;
long maxDate = 0; long maxDate = 0;
try { try {
minDate = ISO8601_DATETIME_FORMAT.parse("1970-01-01T00:00:00Z").getTime(); minDate = ISO8601_DATETIME_FORMAT.get().parse("1970-01-01T00:00:00Z").getTime();
maxDate = ISO8601_DATETIME_FORMAT.parse("2099-12-31T23:59:59Z").getTime(); maxDate = ISO8601_DATETIME_FORMAT.get().parse("2099-12-31T23:59:59Z").getTime();
} catch (ParseException e) { } catch (ParseException e) {
// Won't happen with the values provided. // Won't happen with the values provided.
} }
@ -383,12 +383,15 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF
short max = var == null || var.maximum == null ? Byte.MAX_VALUE : Byte.parseByte(var.maximum); short max = var == null || var.maximum == null ? Byte.MAX_VALUE : Byte.parseByte(var.maximum);
short exclusiveMin = (short) (var != null && var.exclusiveMinimum ? 1 : 0); short exclusiveMin = (short) (var != null && var.exclusiveMinimum ? 1 : 0);
short inclusiveMax = (short) (var == null || !var.exclusiveMaximum ? 1 : 0); short inclusiveMax = (short) (var == null || !var.exclusiveMaximum ? 1 : 0);
int itemCount = Math.max(var.itemCount, var.minItems == null ? 1 : Math.max(1, var.minItems)); int itemCount = 0;
if (var != null) {
itemCount = Math.max(var.itemCount, var.minItems == null ? 1 : Math.max(1, var.minItems));
}
byte[] randomBytes = new byte[itemCount]; byte[] randomBytes = new byte[itemCount];
for (int i = 0; i < itemCount; i++) for (int i = 0; i < itemCount; i++)
randomBytes[i] = (byte) (min + exclusiveMin + ((max + inclusiveMax - min - exclusiveMin) * Math.random())); randomBytes[i] = (byte) (min + exclusiveMin + ((max + inclusiveMax - min - exclusiveMin) * Math.random()));
String randomBytesBase64 = Base64.getEncoder().encodeToString(randomBytes); String randomBytesBase64 = Base64.getEncoder().encodeToString(randomBytes);
if (loadTestDataFromFile) if (loadTestDataFromFile && var != null)
var.addTestData(randomBytesBase64); var.addTestData(randomBytesBase64);
else else
buffer.append('"'); buffer.append('"');
@ -431,13 +434,10 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF
* @param buffer * @param buffer
* @param indent * @param indent
* @param op * @param op
* @param var
* @param localVars * @param localVars
* @param models * @param models
* @param type *
* @param baseType
* @param isListContainer
* @param isMapContainer
* @param localVar
* @return <code>localVar</code> with a numeric suffix if necessary to ensure uniqueness. * @return <code>localVar</code> with a numeric suffix if necessary to ensure uniqueness.
*/ */
private String appendLocalVariable(StringBuilder buffer, String indent, CodegenOperation op, CodegenVariable var, private String appendLocalVariable(StringBuilder buffer, String indent, CodegenOperation op, CodegenVariable var,
@ -582,7 +582,7 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF
long minDate = MIN_DATE; long minDate = MIN_DATE;
long maxDate = MAX_DATE; long maxDate = MAX_DATE;
if (var != null) { if (var != null) {
DateFormat df = var.dataFormat.equals("date-time") ? ISO8601_DATETIME_FORMAT : ISO8601_DATE_FORMAT; DateFormat df = var.dataFormat.equals("date-time") ? ISO8601_DATETIME_FORMAT.get() : ISO8601_DATE_FORMAT.get();
String isoFormat = var.dataFormat.equals("date-time") ? "date-time" : "full-date"; String isoFormat = var.dataFormat.equals("date-time") ? "date-time" : "full-date";
if (var.minimum != null) { if (var.minimum != null) {
try { try {
@ -622,10 +622,10 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF
Date randomDate = new Date(randomDateLong); Date randomDate = new Date(randomDateLong);
switch (var.dataFormat) { switch (var.dataFormat) {
case "date": case "date":
var.addTestData(ISO8601_DATE_FORMAT.format(randomDate)); var.addTestData(ISO8601_DATE_FORMAT.get().format(randomDate));
break; break;
case "date-time": case "date-time":
var.addTestData(ISO8601_DATETIME_FORMAT.format(randomDate)); var.addTestData(ISO8601_DATETIME_FORMAT.get().format(randomDate));
break; break;
} }
} else { } else {
@ -787,7 +787,6 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF
* @param localVar The variable whose value is to be set. * @param localVar The variable whose value is to be set.
* @param localVars Tracks local variables which have been allocated. * @param localVars Tracks local variables which have been allocated.
* @param models A map of models, keyed on class name. * @param models A map of models, keyed on class name.
* @param type The value type.
*/ */
private void appendScalarValue(StringBuilder buffer, String indent, CodegenOperation op, CodegenVariable var, private void appendScalarValue(StringBuilder buffer, String indent, CodegenOperation op, CodegenVariable var,
String localVar, Collection<String> localVars, Map<String, CodegenModel> models) { String localVar, Collection<String> localVars, Map<String, CodegenModel> models) {

View File

@ -516,7 +516,7 @@ public class PythonAbstractConnexionServerCodegen extends DefaultCodegen impleme
if (pathExtensions != null) { if (pathExtensions != null) {
// Get and remove the (temporary) vendor extension // Get and remove the (temporary) vendor extension
String openapiPathname = (String) pathExtensions.remove("x-python-connexion-openapi-name"); String openapiPathname = (String) pathExtensions.remove("x-python-connexion-openapi-name");
if (openapiPathname != null && openapiPathname != pythonPathname) { if (openapiPathname != null && !openapiPathname.equals(pythonPathname)) {
LOGGER.info("Path '" + pythonPathname + "' is not consistant with the original OpenAPI definition. It will be replaced back by '" + openapiPathname + "'"); LOGGER.info("Path '" + pythonPathname + "' is not consistant with the original OpenAPI definition. It will be replaced back by '" + openapiPathname + "'");
paths.remove(pythonPathname); paths.remove(pythonPathname);
paths.put(openapiPathname, path); paths.put(openapiPathname, path);
@ -535,7 +535,7 @@ public class PythonAbstractConnexionServerCodegen extends DefaultCodegen impleme
String swaggerParameterName = (String) parameterExtensions.remove("x-python-connexion-openapi-name"); String swaggerParameterName = (String) parameterExtensions.remove("x-python-connexion-openapi-name");
if (swaggerParameterName != null) { if (swaggerParameterName != null) {
String pythonParameterName = parameter.getName(); String pythonParameterName = parameter.getName();
if (swaggerParameterName != pythonParameterName) { if (!swaggerParameterName.equals(pythonParameterName)) {
LOGGER.info("Reverting name of parameter '" + pythonParameterName + "' of operation '" + operation.getOperationId() + "' back to '" + swaggerParameterName + "'"); LOGGER.info("Reverting name of parameter '" + pythonParameterName + "' of operation '" + operation.getOperationId() + "' back to '" + swaggerParameterName + "'");
parameter.setName(swaggerParameterName); parameter.setName(swaggerParameterName);
} else { } else {

View File

@ -818,7 +818,7 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
if (ModelUtils.isFreeFormObject(p) && ModelUtils.getAdditionalProperties(p) == null) { if (ModelUtils.isFreeFormObject(p) && ModelUtils.getAdditionalProperties(p) == null) {
return prefix + "bool, date, datetime, dict, float, int, list, str" + fullSuffix; return prefix + "bool, date, datetime, dict, float, int, list, str" + fullSuffix;
} }
if ((ModelUtils.isMapSchema(p) || p.getType() == "object") && ModelUtils.getAdditionalProperties(p) != null) { if ((ModelUtils.isMapSchema(p) || "object".equals(p.getType())) && ModelUtils.getAdditionalProperties(p) != null) {
Schema inner = ModelUtils.getAdditionalProperties(p); Schema inner = ModelUtils.getAdditionalProperties(p);
return prefix + "{str: " + getTypeString(inner, "(", ")") + "}" + fullSuffix; return prefix + "{str: " + getTypeString(inner, "(", ")") + "}" + fullSuffix;
} else if (ModelUtils.isArraySchema(p)) { } else if (ModelUtils.isArraySchema(p)) {

View File

@ -710,11 +710,7 @@ public class RClientCodegen extends DefaultCodegen implements CodegenConfig {
} else if (codegenParameter.isMapContainer) { // TODO: map } else if (codegenParameter.isMapContainer) { // TODO: map
return "TODO"; return "TODO";
} else if (languageSpecificPrimitives.contains(codegenParameter.dataType)) { // primitive type } else if (languageSpecificPrimitives.contains(codegenParameter.dataType)) { // primitive type
if ("character".equals(codegenParameter.dataType)) { return codegenParameter.example;
return codegenParameter.example;
} else {
return codegenParameter.example;
}
} else { // model } else { // model
// look up the model // look up the model
if (modelMaps.containsKey(codegenParameter.dataType)) { if (modelMaps.containsKey(codegenParameter.dataType)) {

View File

@ -664,7 +664,7 @@ public class RustServerCodegen extends DefaultCodegen implements CodegenConfig {
// Get the original API response so we get process the schema // Get the original API response so we get process the schema
// directly. // directly.
ApiResponse original; ApiResponse original;
if (rsp.code == "0") { if ("0".equals(rsp.code)) {
original = operation.getResponses().get("default"); original = operation.getResponses().get("default");
} else { } else {
original = operation.getResponses().get(rsp.code); original = operation.getResponses().get(rsp.code);
@ -714,10 +714,7 @@ public class RustServerCodegen extends DefaultCodegen implements CodegenConfig {
String firstProduces = null; String firstProduces = null;
if (original.getContent() != null) { if (original.getContent() != null) {
for (String mimetype : original.getContent().keySet()) { firstProduces = original.getContent().keySet().stream().findFirst().orElse(null);
firstProduces = mimetype;
break;
}
} }
// The output mime type. This allows us to do sensible fallback // The output mime type. This allows us to do sensible fallback

View File

@ -1011,7 +1011,7 @@ public class Swift4Codegen extends DefaultCodegen implements CodegenConfig {
return "\"" + codegenParameter.paramName + "_example\""; return "\"" + codegenParameter.paramName + "_example\"";
} }
} else if ("Bool".equals(codegenParameter.dataType)) { // boolean } else if ("Bool".equals(codegenParameter.dataType)) { // boolean
if (Boolean.TRUE.equals(codegenParameter.example)) { if (Boolean.parseBoolean(codegenParameter.example)) {
return "true"; return "true";
} else { } else {
return "false"; return "false";
@ -1051,7 +1051,7 @@ public class Swift4Codegen extends DefaultCodegen implements CodegenConfig {
return "\"" + codegenProperty.name + "_example\""; return "\"" + codegenProperty.name + "_example\"";
} }
} else if ("Bool".equals(codegenProperty.dataType)) { // boolean } else if ("Bool".equals(codegenProperty.dataType)) { // boolean
if (Boolean.TRUE.equals(codegenProperty.example)) { if (Boolean.parseBoolean(codegenProperty.example)) {
return "true"; return "true";
} else { } else {
return "false"; return "false";

View File

@ -993,7 +993,7 @@ public class Swift5ClientCodegen extends DefaultCodegen implements CodegenConfig
return "\"" + codegenParameter.paramName + "_example\""; return "\"" + codegenParameter.paramName + "_example\"";
} }
} else if ("Bool".equals(codegenParameter.dataType)) { // boolean } else if ("Bool".equals(codegenParameter.dataType)) { // boolean
if (Boolean.TRUE.equals(codegenParameter.example)) { if (Boolean.parseBoolean(codegenParameter.example)) {
return "true"; return "true";
} else { } else {
return "false"; return "false";
@ -1033,7 +1033,7 @@ public class Swift5ClientCodegen extends DefaultCodegen implements CodegenConfig
return "\"" + codegenProperty.name + "_example\""; return "\"" + codegenProperty.name + "_example\"";
} }
} else if ("Bool".equals(codegenProperty.dataType)) { // boolean } else if ("Bool".equals(codegenProperty.dataType)) { // boolean
if (Boolean.TRUE.equals(codegenProperty.example)) { if (Boolean.parseBoolean(codegenProperty.example)) {
return "true"; return "true";
} else { } else {
return "false"; return "false";

View File

@ -283,7 +283,7 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
Map<String, Object> _operations = (Map<String, Object>) operations.get("operations"); Map<String, Object> _operations = (Map<String, Object>) operations.get("operations");
List<CodegenOperation> operationList = (List<CodegenOperation>) _operations.get("operation"); List<CodegenOperation> operationList = (List<CodegenOperation>) _operations.get("operation");
for (CodegenOperation op : operationList) { for (CodegenOperation op : operationList) {
if(op.returnType == "object") { if("object".equals(op.returnType)) {
op.isMapContainer = true; op.isMapContainer = true;
op.returnSimpleType = false; op.returnSimpleType = false;
} }

View File

@ -255,7 +255,7 @@ public class TypeScriptReduxQueryClientCodegen extends AbstractTypeScriptClientC
Map<String, Object> _operations = (Map<String, Object>) operations.get("operations"); Map<String, Object> _operations = (Map<String, Object>) operations.get("operations");
List<CodegenOperation> operationList = (List<CodegenOperation>) _operations.get("operation"); List<CodegenOperation> operationList = (List<CodegenOperation>) _operations.get("operation");
for (CodegenOperation op : operationList) { for (CodegenOperation op : operationList) {
if(op.returnType == "object") { if("object".equals(op.returnType)) {
op.isMapContainer = true; op.isMapContainer = true;
op.returnSimpleType = false; op.returnSimpleType = false;
} }

View File

@ -35,11 +35,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -246,10 +242,14 @@ public class ModelUtils {
if (parameters != null) { if (parameters != null) {
for (Parameter p : parameters) { for (Parameter p : parameters) {
Parameter parameter = getReferencedParameter(openAPI, p); Parameter parameter = getReferencedParameter(openAPI, p);
if (parameter.getSchema() != null) { if (parameter != null) {
visitSchema(openAPI, parameter.getSchema(), null, visitedSchemas, visitor); if (parameter.getSchema() != null) {
visitSchema(openAPI, parameter.getSchema(), null, visitedSchemas, visitor);
}
visitContent(openAPI, parameter.getContent(), visitor, visitedSchemas);
} else {
LOGGER.warn("Unreferenced parameter found.");
} }
visitContent(openAPI, parameter.getContent(), visitor, visitedSchemas);
} }
} }
} }

View File

@ -195,29 +195,31 @@ public class URLPathUtils {
*/ */
public static String getHost(OpenAPI openAPI, final Map<String, String> userDefinedVariables) { public static String getHost(OpenAPI openAPI, final Map<String, String> userDefinedVariables) {
if (openAPI.getServers() != null && openAPI.getServers().size() > 0) { if (openAPI.getServers() != null && openAPI.getServers().size() > 0) {
return sanitizeUrl(getServerURL(openAPI.getServers().get(0), userDefinedVariables).toString()); URL url = getServerURL(openAPI.getServers().get(0), userDefinedVariables);
return url != null ? sanitizeUrl(url.toString()) : "";
} }
return LOCAL_HOST; return LOCAL_HOST;
} }
private static String sanitizeUrl(String url) { private static String sanitizeUrl(String url) {
if (url.startsWith("//")) { if (url != null) {
url = "http:" + url; if (url.startsWith("//")) {
LOGGER.warn("'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]", url); url = "http:" + url;
} else if (url.startsWith("/")) { LOGGER.warn("'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]", url);
url = LOCAL_HOST + url; } else if (url.startsWith("/")) {
LOGGER.warn("'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [{}] for server URL [{}]", LOCAL_HOST, url); url = LOCAL_HOST + url;
} else if (!url.matches("[a-zA-Z][0-9a-zA-Z.+\\-]+://.+")) { LOGGER.warn("'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [{}] for server URL [{}]", LOCAL_HOST, url);
// Add http scheme for urls without a scheme. } else if (!url.matches("[a-zA-Z][0-9a-zA-Z.+\\-]+://.+")) {
// 2.0 spec is restricted to the following schemes: "http", "https", "ws", "wss" // Add http scheme for urls without a scheme.
// 3.0 spec does not have an enumerated list of schemes // 2.0 spec is restricted to the following schemes: "http", "https", "ws", "wss"
// This regex attempts to capture all schemes in IANA example schemes which // 3.0 spec does not have an enumerated list of schemes
// can have alpha-numeric characters and [.+-]. Examples are here: // This regex attempts to capture all schemes in IANA example schemes which
// https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml // can have alpha-numeric characters and [.+-]. Examples are here:
url = "http://" + url; // https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
LOGGER.warn("'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]", url); url = "http://" + url;
LOGGER.warn("'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]", url);
}
} }
return url; return url;
} }