mirror of
https://github.com/valitydev/openapi-generator.git
synced 2024-11-06 10:35:25 +00:00
🐛 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:
parent
ad4a9df328
commit
c9ec084418
2
.github/workflows/sonar.yml
vendored
2
.github/workflows/sonar.yml
vendored
@ -21,4 +21,4 @@ jobs:
|
||||
- name: Jacoco Aggregate
|
||||
run: mvn jacoco:report-aggregate
|
||||
- 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##*/}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"!include": "./bin/jaxrs-datelib-j8.json",
|
||||
"!include": "bin/jaxrs-datelib-j8.json",
|
||||
"generatorName": "jaxrs-jersey",
|
||||
"inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml",
|
||||
"outputDir": "samples/server/petstore/jaxrs-datelib-j8/",
|
||||
|
@ -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",
|
||||
"generatorName": "jaxrs-resteasy-eap",
|
||||
"inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore.yaml",
|
||||
|
@ -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",
|
||||
"generatorName": "jaxrs-resteasy-eap",
|
||||
"inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore.yaml",
|
||||
|
@ -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",
|
||||
"generatorName": "jaxrs-resteasy",
|
||||
"inputSpec": "modules/openapi-generator/src/test/resources/2_0/petstore.yaml",
|
||||
|
@ -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()));
|
||||
|
||||
// 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;
|
||||
}
|
||||
});
|
||||
|
||||
SimpleModule module = getCustomDeserializationModel(includesDir);
|
||||
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.
|
||||
@ -169,6 +154,8 @@ public class GenerateBatch implements Runnable {
|
||||
System.out.println("COMPLETE.");
|
||||
} catch (InterruptedException e) {
|
||||
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 {
|
||||
private static final String INCLUDE = "!include";
|
||||
private File scanDir;
|
||||
@ -255,11 +264,13 @@ public class GenerateBatch implements Runnable {
|
||||
// load the file into the tree node and continue parsing as normal
|
||||
((ObjectNode) node).remove(INCLUDE);
|
||||
|
||||
JsonParser includeParser = codec.getFactory().createParser(includeFile);
|
||||
TreeNode includeNode = includeParser.readValueAsTree();
|
||||
TreeNode includeNode;
|
||||
try (JsonParser includeParser = codec.getFactory().createParser(includeFile)) {
|
||||
includeNode = includeParser.readValueAsTree();
|
||||
}
|
||||
|
||||
ObjectReader reader = codec.readerForUpdating(node);
|
||||
TreeNode updated = reader.readValue(includeFile);
|
||||
TreeNode updated = reader.readValue(includeNode.traverse());
|
||||
JsonParser updatedParser = updated.traverse();
|
||||
updatedParser.nextToken();
|
||||
return super.deserialize(updatedParser, ctx);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"serializableModel": true,
|
||||
"withXml": true,
|
||||
"dateLibrary": "java8",
|
||||
"java8": true,
|
||||
"useBeanValidation": true
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
serializableModel: true
|
||||
withXml: true
|
||||
dateLibrary: java8
|
||||
java8: true
|
||||
useBeanValidation: true
|
@ -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'
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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'
|
@ -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
|
@ -777,12 +777,13 @@ public class CodeGenMojo extends AbstractMojo {
|
||||
conn.setRequestProperty(auth.getKeyName(), auth.getValue());
|
||||
}
|
||||
}
|
||||
ReadableByteChannel readableByteChannel = Channels.newChannel(conn.getInputStream());
|
||||
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(inputSpecTempFile);
|
||||
FileChannel fileChannel = fileOutputStream.getChannel();
|
||||
|
||||
fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
|
||||
try (ReadableByteChannel readableByteChannel = Channels.newChannel(conn.getInputStream())) {
|
||||
FileChannel fileChannel;
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(inputSpecTempFile)) {
|
||||
fileChannel = fileOutputStream.getChannel();
|
||||
fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ByteSource inputSpecByteSource =
|
||||
|
@ -49,7 +49,9 @@ public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen imp
|
||||
public static final String NPM_VERSION = "npmVersion";
|
||||
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 Boolean supportsES6 = false;
|
||||
@ -158,7 +160,7 @@ public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen imp
|
||||
" 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(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));
|
||||
|
||||
}
|
||||
@ -204,9 +206,9 @@ public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen imp
|
||||
|
||||
if (additionalProperties.containsKey(SNAPSHOT) && Boolean.parseBoolean(additionalProperties.get(SNAPSHOT).toString())) {
|
||||
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 {
|
||||
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);
|
||||
|
@ -25,24 +25,11 @@ import java.math.BigDecimal;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
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 java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openapitools.codegen.CliOption;
|
||||
import org.openapitools.codegen.CodegenConstants;
|
||||
import org.openapitools.codegen.CodegenModel;
|
||||
import org.openapitools.codegen.CodegenOperation;
|
||||
import org.openapitools.codegen.CodegenParameter;
|
||||
@ -275,10 +262,25 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF
|
||||
|
||||
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",
|
||||
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_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;
|
||||
|
||||
@ -292,13 +294,11 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF
|
||||
"LocalDateTime", "LocalDate");
|
||||
|
||||
static {
|
||||
ISO8601_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
ISO8601_DATETIME_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
long minDate = 0;
|
||||
long maxDate = 0;
|
||||
try {
|
||||
minDate = ISO8601_DATETIME_FORMAT.parse("1970-01-01T00:00:00Z").getTime();
|
||||
maxDate = ISO8601_DATETIME_FORMAT.parse("2099-12-31T23:59:59Z").getTime();
|
||||
minDate = ISO8601_DATETIME_FORMAT.get().parse("1970-01-01T00:00:00Z").getTime();
|
||||
maxDate = ISO8601_DATETIME_FORMAT.get().parse("2099-12-31T23:59:59Z").getTime();
|
||||
} catch (ParseException e) {
|
||||
// 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 exclusiveMin = (short) (var != null && var.exclusiveMinimum ? 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];
|
||||
for (int i = 0; i < itemCount; i++)
|
||||
randomBytes[i] = (byte) (min + exclusiveMin + ((max + inclusiveMax - min - exclusiveMin) * Math.random()));
|
||||
String randomBytesBase64 = Base64.getEncoder().encodeToString(randomBytes);
|
||||
if (loadTestDataFromFile)
|
||||
if (loadTestDataFromFile && var != null)
|
||||
var.addTestData(randomBytesBase64);
|
||||
else
|
||||
buffer.append('"');
|
||||
@ -431,13 +434,10 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF
|
||||
* @param buffer
|
||||
* @param indent
|
||||
* @param op
|
||||
* @param var
|
||||
* @param localVars
|
||||
* @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.
|
||||
*/
|
||||
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 maxDate = MAX_DATE;
|
||||
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";
|
||||
if (var.minimum != null) {
|
||||
try {
|
||||
@ -622,10 +622,10 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF
|
||||
Date randomDate = new Date(randomDateLong);
|
||||
switch (var.dataFormat) {
|
||||
case "date":
|
||||
var.addTestData(ISO8601_DATE_FORMAT.format(randomDate));
|
||||
var.addTestData(ISO8601_DATE_FORMAT.get().format(randomDate));
|
||||
break;
|
||||
case "date-time":
|
||||
var.addTestData(ISO8601_DATETIME_FORMAT.format(randomDate));
|
||||
var.addTestData(ISO8601_DATETIME_FORMAT.get().format(randomDate));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@ -787,7 +787,6 @@ public class JavaCXFExtServerCodegen extends JavaCXFServerCodegen implements CXF
|
||||
* @param localVar The variable whose value is to be set.
|
||||
* @param localVars Tracks local variables which have been allocated.
|
||||
* @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,
|
||||
String localVar, Collection<String> localVars, Map<String, CodegenModel> models) {
|
||||
|
@ -516,7 +516,7 @@ public class PythonAbstractConnexionServerCodegen extends DefaultCodegen impleme
|
||||
if (pathExtensions != null) {
|
||||
// Get and remove the (temporary) vendor extension
|
||||
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 + "'");
|
||||
paths.remove(pythonPathname);
|
||||
paths.put(openapiPathname, path);
|
||||
@ -535,7 +535,7 @@ public class PythonAbstractConnexionServerCodegen extends DefaultCodegen impleme
|
||||
String swaggerParameterName = (String) parameterExtensions.remove("x-python-connexion-openapi-name");
|
||||
if (swaggerParameterName != null) {
|
||||
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 + "'");
|
||||
parameter.setName(swaggerParameterName);
|
||||
} else {
|
||||
|
@ -818,7 +818,7 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
|
||||
if (ModelUtils.isFreeFormObject(p) && ModelUtils.getAdditionalProperties(p) == null) {
|
||||
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);
|
||||
return prefix + "{str: " + getTypeString(inner, "(", ")") + "}" + fullSuffix;
|
||||
} else if (ModelUtils.isArraySchema(p)) {
|
||||
|
@ -710,11 +710,7 @@ public class RClientCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
} else if (codegenParameter.isMapContainer) { // TODO: map
|
||||
return "TODO";
|
||||
} else if (languageSpecificPrimitives.contains(codegenParameter.dataType)) { // primitive type
|
||||
if ("character".equals(codegenParameter.dataType)) {
|
||||
return codegenParameter.example;
|
||||
} else {
|
||||
return codegenParameter.example;
|
||||
}
|
||||
return codegenParameter.example;
|
||||
} else { // model
|
||||
// look up the model
|
||||
if (modelMaps.containsKey(codegenParameter.dataType)) {
|
||||
|
@ -664,7 +664,7 @@ public class RustServerCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
// Get the original API response so we get process the schema
|
||||
// directly.
|
||||
ApiResponse original;
|
||||
if (rsp.code == "0") {
|
||||
if ("0".equals(rsp.code)) {
|
||||
original = operation.getResponses().get("default");
|
||||
} else {
|
||||
original = operation.getResponses().get(rsp.code);
|
||||
@ -714,10 +714,7 @@ public class RustServerCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
String firstProduces = null;
|
||||
|
||||
if (original.getContent() != null) {
|
||||
for (String mimetype : original.getContent().keySet()) {
|
||||
firstProduces = mimetype;
|
||||
break;
|
||||
}
|
||||
firstProduces = original.getContent().keySet().stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
// The output mime type. This allows us to do sensible fallback
|
||||
|
@ -1011,7 +1011,7 @@ public class Swift4Codegen extends DefaultCodegen implements CodegenConfig {
|
||||
return "\"" + codegenParameter.paramName + "_example\"";
|
||||
}
|
||||
} else if ("Bool".equals(codegenParameter.dataType)) { // boolean
|
||||
if (Boolean.TRUE.equals(codegenParameter.example)) {
|
||||
if (Boolean.parseBoolean(codegenParameter.example)) {
|
||||
return "true";
|
||||
} else {
|
||||
return "false";
|
||||
@ -1051,7 +1051,7 @@ public class Swift4Codegen extends DefaultCodegen implements CodegenConfig {
|
||||
return "\"" + codegenProperty.name + "_example\"";
|
||||
}
|
||||
} else if ("Bool".equals(codegenProperty.dataType)) { // boolean
|
||||
if (Boolean.TRUE.equals(codegenProperty.example)) {
|
||||
if (Boolean.parseBoolean(codegenProperty.example)) {
|
||||
return "true";
|
||||
} else {
|
||||
return "false";
|
||||
|
@ -993,7 +993,7 @@ public class Swift5ClientCodegen extends DefaultCodegen implements CodegenConfig
|
||||
return "\"" + codegenParameter.paramName + "_example\"";
|
||||
}
|
||||
} else if ("Bool".equals(codegenParameter.dataType)) { // boolean
|
||||
if (Boolean.TRUE.equals(codegenParameter.example)) {
|
||||
if (Boolean.parseBoolean(codegenParameter.example)) {
|
||||
return "true";
|
||||
} else {
|
||||
return "false";
|
||||
@ -1033,7 +1033,7 @@ public class Swift5ClientCodegen extends DefaultCodegen implements CodegenConfig
|
||||
return "\"" + codegenProperty.name + "_example\"";
|
||||
}
|
||||
} else if ("Bool".equals(codegenProperty.dataType)) { // boolean
|
||||
if (Boolean.TRUE.equals(codegenProperty.example)) {
|
||||
if (Boolean.parseBoolean(codegenProperty.example)) {
|
||||
return "true";
|
||||
} else {
|
||||
return "false";
|
||||
|
@ -283,7 +283,7 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
|
||||
Map<String, Object> _operations = (Map<String, Object>) operations.get("operations");
|
||||
List<CodegenOperation> operationList = (List<CodegenOperation>) _operations.get("operation");
|
||||
for (CodegenOperation op : operationList) {
|
||||
if(op.returnType == "object") {
|
||||
if("object".equals(op.returnType)) {
|
||||
op.isMapContainer = true;
|
||||
op.returnSimpleType = false;
|
||||
}
|
||||
|
@ -255,7 +255,7 @@ public class TypeScriptReduxQueryClientCodegen extends AbstractTypeScriptClientC
|
||||
Map<String, Object> _operations = (Map<String, Object>) operations.get("operations");
|
||||
List<CodegenOperation> operationList = (List<CodegenOperation>) _operations.get("operation");
|
||||
for (CodegenOperation op : operationList) {
|
||||
if(op.returnType == "object") {
|
||||
if("object".equals(op.returnType)) {
|
||||
op.isMapContainer = true;
|
||||
op.returnSimpleType = false;
|
||||
}
|
||||
|
@ -35,11 +35,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -246,10 +242,14 @@ public class ModelUtils {
|
||||
if (parameters != null) {
|
||||
for (Parameter p : parameters) {
|
||||
Parameter parameter = getReferencedParameter(openAPI, p);
|
||||
if (parameter.getSchema() != null) {
|
||||
visitSchema(openAPI, parameter.getSchema(), null, visitedSchemas, visitor);
|
||||
if (parameter != null) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,29 +195,31 @@ public class URLPathUtils {
|
||||
*/
|
||||
public static String getHost(OpenAPI openAPI, final Map<String, String> userDefinedVariables) {
|
||||
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;
|
||||
}
|
||||
|
||||
private static String sanitizeUrl(String url) {
|
||||
if (url.startsWith("//")) {
|
||||
url = "http:" + url;
|
||||
LOGGER.warn("'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]", url);
|
||||
} else if (url.startsWith("/")) {
|
||||
url = LOCAL_HOST + url;
|
||||
LOGGER.warn("'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [{}] for server URL [{}]", LOCAL_HOST, url);
|
||||
} else if (!url.matches("[a-zA-Z][0-9a-zA-Z.+\\-]+://.+")) {
|
||||
// Add http scheme for urls without a scheme.
|
||||
// 2.0 spec is restricted to the following schemes: "http", "https", "ws", "wss"
|
||||
// 3.0 spec does not have an enumerated list of schemes
|
||||
// This regex attempts to capture all schemes in IANA example schemes which
|
||||
// can have alpha-numeric characters and [.+-]. Examples are here:
|
||||
// https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
|
||||
url = "http://" + url;
|
||||
LOGGER.warn("'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]", url);
|
||||
if (url != null) {
|
||||
if (url.startsWith("//")) {
|
||||
url = "http:" + url;
|
||||
LOGGER.warn("'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]", url);
|
||||
} else if (url.startsWith("/")) {
|
||||
url = LOCAL_HOST + url;
|
||||
LOGGER.warn("'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [{}] for server URL [{}]", LOCAL_HOST, url);
|
||||
} else if (!url.matches("[a-zA-Z][0-9a-zA-Z.+\\-]+://.+")) {
|
||||
// Add http scheme for urls without a scheme.
|
||||
// 2.0 spec is restricted to the following schemes: "http", "https", "ws", "wss"
|
||||
// 3.0 spec does not have an enumerated list of schemes
|
||||
// This regex attempts to capture all schemes in IANA example schemes which
|
||||
// can have alpha-numeric characters and [.+-]. Examples are here:
|
||||
// https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
|
||||
url = "http://" + url;
|
||||
LOGGER.warn("'scheme' not defined in the spec (2.0). Default to [http] for server URL [{}]", url);
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user