mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2025-07-04 22:50:53 +00:00
[Crystal] Allow double colons in model name (#13736)
This commit is contained in:
parent
ad2169ea33
commit
b2e8a15d9f
@ -84,27 +84,21 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
SecurityFeature.BasicAuth,
|
||||
SecurityFeature.BearerToken,
|
||||
SecurityFeature.ApiKey,
|
||||
SecurityFeature.OAuth2_Implicit
|
||||
))
|
||||
SecurityFeature.OAuth2_Implicit))
|
||||
.excludeGlobalFeatures(
|
||||
GlobalFeature.XMLStructureDefinitions,
|
||||
GlobalFeature.Callbacks,
|
||||
GlobalFeature.LinkObjects,
|
||||
GlobalFeature.ParameterStyling,
|
||||
GlobalFeature.ParameterizedServer,
|
||||
GlobalFeature.MultiServer
|
||||
)
|
||||
GlobalFeature.MultiServer)
|
||||
.includeSchemaSupportFeatures(
|
||||
SchemaSupportFeature.Polymorphism
|
||||
)
|
||||
SchemaSupportFeature.Polymorphism)
|
||||
.excludeParameterFeatures(
|
||||
ParameterFeature.Cookie
|
||||
)
|
||||
ParameterFeature.Cookie)
|
||||
.includeClientModificationFeatures(
|
||||
ClientModificationFeature.BasePath,
|
||||
ClientModificationFeature.UserAgent
|
||||
)
|
||||
);
|
||||
ClientModificationFeature.UserAgent));
|
||||
|
||||
generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
|
||||
.stability(Stability.BETA)
|
||||
@ -134,7 +128,8 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
// default HIDE_GENERATION_TIMESTAMP to true
|
||||
hideGenerationTimestamp = Boolean.TRUE;
|
||||
|
||||
// reserved word. Ref: https://github.com/crystal-lang/crystal/wiki/Crystal-for-Rubyists#available-keywords
|
||||
// reserved word. Ref:
|
||||
// https://github.com/crystal-lang/crystal/wiki/Crystal-for-Rubyists#available-keywords
|
||||
reservedWords = new HashSet<>(
|
||||
Arrays.asList(
|
||||
"abstract", "annotation", "do", "if", "nil?", "select", "union",
|
||||
@ -146,8 +141,7 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
"break", "extend", "macro", "require", "true", "with",
|
||||
"case", "false", "module", "rescue", "type", "yield",
|
||||
"class", "for", "next", "responds_to?", "typeof",
|
||||
"def", "fun", "nil", "return", "uninitialized")
|
||||
);
|
||||
"def", "fun", "nil", "return", "uninitialized"));
|
||||
|
||||
languageSpecificPrimitives.clear();
|
||||
languageSpecificPrimitives.add("String");
|
||||
@ -195,29 +189,25 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
cliOptions.removeIf(opt -> CodegenConstants.MODEL_PACKAGE.equals(opt.getOpt()) ||
|
||||
CodegenConstants.API_PACKAGE.equals(opt.getOpt()));
|
||||
|
||||
cliOptions.add(new CliOption(SHARD_NAME, "shard name (e.g. twitter_client").
|
||||
defaultValue("openapi_client"));
|
||||
cliOptions.add(new CliOption(SHARD_NAME, "shard name (e.g. twitter_client").defaultValue("openapi_client"));
|
||||
|
||||
cliOptions.add(new CliOption(MODULE_NAME, "module name (e.g. TwitterClient").
|
||||
defaultValue("OpenAPIClient"));
|
||||
cliOptions.add(new CliOption(MODULE_NAME, "module name (e.g. TwitterClient").defaultValue("OpenAPIClient"));
|
||||
|
||||
cliOptions.add(new CliOption(SHARD_VERSION, "shard version.").defaultValue("1.0.0"));
|
||||
|
||||
cliOptions.add(new CliOption(SHARD_LICENSE, "shard license.").
|
||||
defaultValue("unlicense"));
|
||||
cliOptions.add(new CliOption(SHARD_LICENSE, "shard license.").defaultValue("unlicense"));
|
||||
|
||||
cliOptions.add(new CliOption(SHARD_HOMEPAGE, "shard homepage.").
|
||||
defaultValue("http://org.openapitools"));
|
||||
cliOptions.add(new CliOption(SHARD_HOMEPAGE, "shard homepage.").defaultValue("http://org.openapitools"));
|
||||
|
||||
cliOptions.add(new CliOption(SHARD_DESCRIPTION, "shard description.").
|
||||
defaultValue("This shard maps to a REST API"));
|
||||
cliOptions.add(
|
||||
new CliOption(SHARD_DESCRIPTION, "shard description.").defaultValue("This shard maps to a REST API"));
|
||||
|
||||
cliOptions.add(new CliOption(SHARD_AUTHOR, "shard author (only one is supported)."));
|
||||
|
||||
cliOptions.add(new CliOption(SHARD_AUTHOR_EMAIL, "shard author email (only one is supported)."));
|
||||
|
||||
cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC).
|
||||
defaultValue(Boolean.TRUE.toString()));
|
||||
cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP,
|
||||
CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC).defaultValue(Boolean.TRUE.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -225,7 +215,8 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
super.processOpts();
|
||||
|
||||
if (StringUtils.isEmpty(System.getenv("CRYSTAL_POST_PROCESS_FILE"))) {
|
||||
LOGGER.info("Hint: Environment variable 'CRYSTAL_POST_PROCESS_FILE' (optional) not defined. E.g. to format the source code, please try 'export CRYSTAL_POST_PROCESS_FILE=\"/usr/local/bin/crystal tool format\"' (Linux/Mac)");
|
||||
LOGGER.info(
|
||||
"Hint: Environment variable 'CRYSTAL_POST_PROCESS_FILE' (optional) not defined. E.g. to format the source code, please try 'export CRYSTAL_POST_PROCESS_FILE=\"/usr/local/bin/crystal tool format\"' (Linux/Mac)");
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(SHARD_NAME)) {
|
||||
@ -314,12 +305,14 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
|
||||
@Override
|
||||
public String apiFileFolder() {
|
||||
return outputFolder + File.separator + srcFolder + File.separator + shardName + File.separator + apiPackage.replace("/", File.separator);
|
||||
return outputFolder + File.separator + srcFolder + File.separator + shardName + File.separator
|
||||
+ apiPackage.replace("/", File.separator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String modelFileFolder() {
|
||||
return outputFolder + File.separator + srcFolder + File.separator + shardName + File.separator + modelPackage.replace("/", File.separator);
|
||||
return outputFolder + File.separator + srcFolder + File.separator + shardName + File.separator
|
||||
+ modelPackage.replace("/", File.separator);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -365,7 +358,7 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
@Override
|
||||
public String toModelName(final String name) {
|
||||
String modelName;
|
||||
modelName = sanitizeName(name);
|
||||
modelName = sanitizeModelName(name);
|
||||
|
||||
if (!StringUtils.isEmpty(modelNamePrefix)) {
|
||||
modelName = modelNamePrefix + "_" + modelName;
|
||||
@ -394,6 +387,15 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
return camelize(modelName);
|
||||
}
|
||||
|
||||
public String sanitizeModelName(String modelName) {
|
||||
String[] parts = modelName.split("::");
|
||||
ArrayList<String> new_parts = new ArrayList<String>();
|
||||
for (String part : parts) {
|
||||
new_parts.add(sanitizeName(part));
|
||||
}
|
||||
return String.join("::", new_parts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toModelFilename(String name) {
|
||||
return underscore(toModelName(name));
|
||||
@ -511,7 +513,8 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
|
||||
// operationId starts with a number
|
||||
if (operationId.matches("^\\d.*")) {
|
||||
LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId)));
|
||||
LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId,
|
||||
underscore(sanitizeName("call_" + operationId)));
|
||||
operationId = "call_" + operationId;
|
||||
}
|
||||
|
||||
@ -610,7 +613,8 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
return objs;
|
||||
}
|
||||
|
||||
private String constructExampleCode(CodegenParameter codegenParameter, HashMap<String, CodegenModel> modelMaps, HashMap<String, Integer> processedModelMap) {
|
||||
private String constructExampleCode(CodegenParameter codegenParameter, HashMap<String, CodegenModel> modelMaps,
|
||||
HashMap<String, Integer> processedModelMap) {
|
||||
if (codegenParameter.isArray) { // array
|
||||
if (codegenParameter.items == null) {
|
||||
return "[]";
|
||||
@ -670,13 +674,15 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
if (modelMaps.containsKey(codegenParameter.dataType)) {
|
||||
return constructExampleCode(modelMaps.get(codegenParameter.dataType), modelMaps, processedModelMap);
|
||||
} else {
|
||||
//LOGGER.error("Error in constructing examples. Failed to look up the model " + codegenParameter.dataType);
|
||||
// LOGGER.error("Error in constructing examples. Failed to look up the model " +
|
||||
// codegenParameter.dataType);
|
||||
return "TODO";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String constructExampleCode(CodegenProperty codegenProperty, HashMap<String, CodegenModel> modelMaps, HashMap<String, Integer> processedModelMap) {
|
||||
private String constructExampleCode(CodegenProperty codegenProperty, HashMap<String, CodegenModel> modelMaps,
|
||||
HashMap<String, Integer> processedModelMap) {
|
||||
if (codegenProperty.isArray) { // array
|
||||
return "[" + constructExampleCode(codegenProperty.items, modelMaps, processedModelMap) + "]";
|
||||
} else if (codegenProperty.isMap) {
|
||||
@ -736,14 +742,17 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
if (modelMaps.containsKey(codegenProperty.dataType)) {
|
||||
return constructExampleCode(modelMaps.get(codegenProperty.dataType), modelMaps, processedModelMap);
|
||||
} else {
|
||||
//LOGGER.error("Error in constructing examples. Failed to look up the model " + codegenParameter.dataType);
|
||||
// LOGGER.error("Error in constructing examples. Failed to look up the model " +
|
||||
// codegenParameter.dataType);
|
||||
return "TODO";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String constructExampleCode(CodegenModel codegenModel, HashMap<String, CodegenModel> modelMaps, HashMap<String, Integer> processedModelMap) {
|
||||
// break infinite recursion. Return, in case a model is already processed in the current context.
|
||||
private String constructExampleCode(CodegenModel codegenModel, HashMap<String, CodegenModel> modelMaps,
|
||||
HashMap<String, Integer> processedModelMap) {
|
||||
// break infinite recursion. Return, in case a model is already processed in the
|
||||
// current context.
|
||||
String model = codegenModel.name;
|
||||
if (processedModelMap.containsKey(model)) {
|
||||
int count = processedModelMap.get(model);
|
||||
@ -755,7 +764,8 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
throw new RuntimeException("Invalid count when constructing example: " + count);
|
||||
}
|
||||
} else if (codegenModel.isEnum) {
|
||||
List<Map<String, String>> enumVars = (List<Map<String, String>>) codegenModel.allowableValues.get("enumVars");
|
||||
List<Map<String, String>> enumVars = (List<Map<String, String>>) codegenModel.allowableValues
|
||||
.get("enumVars");
|
||||
return moduleName + "::" + codegenModel.classname + "::" + enumVars.get(0).get("name");
|
||||
} else if (codegenModel.oneOf != null && !codegenModel.oneOf.isEmpty()) {
|
||||
String subModel = (String) codegenModel.oneOf.toArray()[0];
|
||||
@ -767,7 +777,8 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
|
||||
List<String> propertyExamples = new ArrayList<>();
|
||||
for (CodegenProperty codegenProperty : codegenModel.requiredVars) {
|
||||
propertyExamples.add(codegenProperty.name + ": " + constructExampleCode(codegenProperty, modelMaps, processedModelMap));
|
||||
propertyExamples.add(
|
||||
codegenProperty.name + ": " + constructExampleCode(codegenProperty, modelMaps, processedModelMap));
|
||||
}
|
||||
String example = moduleName + "::" + toModelName(model) + ".new";
|
||||
if (!propertyExamples.isEmpty()) {
|
||||
@ -827,7 +838,8 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||
return "Date.parse(\"" + String.format(Locale.ROOT, localDate.toString(), "") + "\")";
|
||||
} else if (p.getDefault() instanceof java.time.OffsetDateTime) {
|
||||
return "Time.parse(\"" + String.format(Locale.ROOT, ((java.time.OffsetDateTime) p.getDefault()).atZoneSameInstant(ZoneId.systemDefault()).toString(), "") + "\")";
|
||||
return "Time.parse(\"" + String.format(Locale.ROOT, ((java.time.OffsetDateTime) p.getDefault())
|
||||
.atZoneSameInstant(ZoneId.systemDefault()).toString(), "") + "\")";
|
||||
} else {
|
||||
return "\"" + escapeText((String.valueOf(p.getDefault()))) + "\"";
|
||||
}
|
||||
@ -902,14 +914,16 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
Process p = Runtime.getRuntime().exec(command);
|
||||
int exitValue = p.waitFor();
|
||||
if (exitValue != 0) {
|
||||
try (InputStreamReader inputStreamReader = new InputStreamReader(p.getErrorStream(), StandardCharsets.UTF_8);
|
||||
try (InputStreamReader inputStreamReader = new InputStreamReader(p.getErrorStream(),
|
||||
StandardCharsets.UTF_8);
|
||||
BufferedReader br = new BufferedReader(inputStreamReader)) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
LOGGER.error("Error running the command ({}). Exit value: {}, Error output: {}", command, exitValue, sb);
|
||||
LOGGER.error("Error running the command ({}). Exit value: {}, Error output: {}", command,
|
||||
exitValue, sb);
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("Successfully executed: {}", command);
|
||||
@ -923,5 +937,7 @@ public class CrystalClientCodegen extends DefaultCodegen {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeneratorLanguage generatorLanguage() { return GeneratorLanguage.CRYSTAL; }
|
||||
public GeneratorLanguage generatorLanguage() {
|
||||
return GeneratorLanguage.CRYSTAL;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
|
||||
* Copyright 2018 SmartBear Software
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openapitools.codegen.crystal;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.openapitools.codegen.*;
|
||||
import org.openapitools.codegen.languages.CrystalClientCodegen;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
|
||||
import static org.testng.Assert.assertTrue;
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
/**
|
||||
* Tests for CrystalClientCodegen-generated templates
|
||||
*/
|
||||
public class CrystalClientCodegenTest {
|
||||
|
||||
@Test
|
||||
public void testGenerateCrystalClientWithHtmlEntity() throws Exception {
|
||||
final File output = Files.createTempDirectory("test").toFile();
|
||||
output.mkdirs();
|
||||
output.deleteOnExit();
|
||||
|
||||
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/pathWithHtmlEntity.yaml");
|
||||
CodegenConfig codegenConfig = new CrystalClientCodegen();
|
||||
codegenConfig.setOutputDir(output.getAbsolutePath());
|
||||
|
||||
ClientOptInput clientOptInput = new ClientOptInput().openAPI(openAPI).config(codegenConfig);
|
||||
|
||||
DefaultGenerator generator = new DefaultGenerator();
|
||||
List<File> files = generator.opts(clientOptInput).generate();
|
||||
boolean apiFileGenerated = false;
|
||||
for (File file : files) {
|
||||
if (file.getName().equals("default_api.cr")) {
|
||||
apiFileGenerated = true;
|
||||
// Crystal client should set the path unescaped in the api file
|
||||
assertTrue(FileUtils.readFileToString(file, StandardCharsets.UTF_8)
|
||||
.contains("local_var_path = \"/foo=bar\""));
|
||||
}
|
||||
}
|
||||
if (!apiFileGenerated) {
|
||||
fail("Default api file is not generated!");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitialConfigValues() throws Exception {
|
||||
final CrystalClientCodegen codegen = new CrystalClientCodegen();
|
||||
codegen.processOpts();
|
||||
|
||||
Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP),
|
||||
Boolean.TRUE);
|
||||
Assert.assertEquals(codegen.isHideGenerationTimestamp(), true);
|
||||
Assert.assertEquals(codegen.modelPackage(), "models");
|
||||
Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.MODEL_PACKAGE), null);
|
||||
Assert.assertEquals(codegen.apiPackage(), "api");
|
||||
Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.API_PACKAGE), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSettersForConfigValues() throws Exception {
|
||||
final CrystalClientCodegen codegen = new CrystalClientCodegen();
|
||||
codegen.setHideGenerationTimestamp(false);
|
||||
codegen.processOpts();
|
||||
|
||||
Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP),
|
||||
Boolean.FALSE);
|
||||
Assert.assertEquals(codegen.isHideGenerationTimestamp(), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdditionalPropertiesPutForConfigValues() throws Exception {
|
||||
final CrystalClientCodegen codegen = new CrystalClientCodegen();
|
||||
codegen.additionalProperties().put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, false);
|
||||
codegen.additionalProperties().put(CodegenConstants.MODEL_PACKAGE, "crystal-models");
|
||||
codegen.additionalProperties().put(CodegenConstants.API_PACKAGE, "crystal-api");
|
||||
codegen.processOpts();
|
||||
|
||||
Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP),
|
||||
Boolean.FALSE);
|
||||
Assert.assertEquals(codegen.isHideGenerationTimestamp(), false);
|
||||
Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.MODEL_PACKAGE), "crystal-models");
|
||||
Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.API_PACKAGE), "crystal-api");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBooleanDefaultValue() throws Exception {
|
||||
final File output = Files.createTempDirectory("test").toFile();
|
||||
output.mkdirs();
|
||||
output.deleteOnExit();
|
||||
|
||||
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/npe1.yaml");
|
||||
CodegenConfig codegenConfig = new CrystalClientCodegen();
|
||||
codegenConfig.setOutputDir(output.getAbsolutePath());
|
||||
|
||||
ClientOptInput clientOptInput = new ClientOptInput().openAPI(openAPI).config(codegenConfig);
|
||||
|
||||
DefaultGenerator generator = new DefaultGenerator();
|
||||
List<File> files = generator.opts(clientOptInput).generate();
|
||||
boolean apiFileGenerated = false;
|
||||
for (File file : files) {
|
||||
if (file.getName().equals("default_api.cr")) {
|
||||
apiFileGenerated = true;
|
||||
// Crystal client should set the path unescaped in the api file
|
||||
assertTrue(FileUtils.readFileToString(file, StandardCharsets.UTF_8)
|
||||
.contains("local_var_path = \"/default/Resources/{id}\""));
|
||||
}
|
||||
}
|
||||
if (!apiFileGenerated) {
|
||||
fail("Default api file is not generated!");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSanitizeModelName() throws Exception {
|
||||
final CrystalClientCodegen codegen = new CrystalClientCodegen();
|
||||
codegen.setHideGenerationTimestamp(false);
|
||||
codegen.processOpts();
|
||||
|
||||
Assert.assertEquals(codegen.sanitizeModelName("JSON::Any"), "JSON::Any");
|
||||
// Disallows single colons
|
||||
Assert.assertEquals(codegen.sanitizeModelName("JSON:Any"), "JSONAny");
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user