diff --git a/bin/configs/java-helidon-client-mp.yaml b/bin/configs/java-helidon-client-mp.yaml new file mode 100644 index 00000000000..083e776f16b --- /dev/null +++ b/bin/configs/java-helidon-client-mp.yaml @@ -0,0 +1,12 @@ +generatorName: java-helidon-client +library: mp +outputDir: samples/client/petstore/java-helidon-client/mp +inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml +additionalProperties: + artifactId: petstore-helidon-client-mp + hideGenerationTimestamp: "true" + configureAuth: "false" + build: "all" + test: "spock" + requiredPropertiesInConstructor: "false" + visitable: "true" diff --git a/bin/configs/java-helidon-client-se.yaml b/bin/configs/java-helidon-client-se.yaml new file mode 100644 index 00000000000..fd7b9916356 --- /dev/null +++ b/bin/configs/java-helidon-client-se.yaml @@ -0,0 +1,12 @@ +generatorName: java-helidon-client +library: se +outputDir: samples/client/petstore/java-helidon-client/se +inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml +additionalProperties: + artifactId: petstore-helidon-client-se + hideGenerationTimestamp: "true" + configureAuth: "false" + build: "all" + test: "spock" + requiredPropertiesInConstructor: "false" + visitable: "true" diff --git a/bin/configs/java-helidon-server-mp.yaml b/bin/configs/java-helidon-server-mp.yaml new file mode 100644 index 00000000000..86bf0c9bdd9 --- /dev/null +++ b/bin/configs/java-helidon-server-mp.yaml @@ -0,0 +1,11 @@ +generatorName: java-helidon-server +library: mp +outputDir: samples/server/petstore/java-helidon-server/mp +inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml +templateDir: modules/openapi-generator/src/main/resources/java-helidon/server +additionalProperties: + artifactId: petstore-helidon-server-mp + hideGenerationTimestamp: "true" + build: "all" + test: "spock" + useAuth: "false" \ No newline at end of file diff --git a/bin/configs/java-helidon-server-se.yaml b/bin/configs/java-helidon-server-se.yaml new file mode 100644 index 00000000000..add3f7ea764 --- /dev/null +++ b/bin/configs/java-helidon-server-se.yaml @@ -0,0 +1,8 @@ +generatorName: java-helidon-server +library: se +outputDir: samples/server/petstore/java-helidon-server/se +inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml +templateDir: modules/openapi-generator/src/main/resources/java-helidon/server +additionalProperties: + artifactId: petstore-helidon-server-se + hideGenerationTimestamp: "true" \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig index 11a9572215d..92dcb24bbcd 100644 --- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -51,6 +51,8 @@ org.openapitools.codegen.languages.HaskellServantCodegen org.openapitools.codegen.languages.HaskellYesodServerCodegen org.openapitools.codegen.languages.JavaClientCodegen org.openapitools.codegen.languages.JavaCXFClientCodegen +org.openapitools.codegen.languages.JavaHelidonClientCodegen +org.openapitools.codegen.languages.JavaHelidonServerCodegen org.openapitools.codegen.languages.JavaInflectorServerCodegen org.openapitools.codegen.languages.JavaMicronautClientCodegen org.openapitools.codegen.languages.JavaMicronautServerCodegen diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonCommonCodegenPackagePrefixTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonCommonCodegenPackagePrefixTest.java new file mode 100644 index 00000000000..70f0e76eca9 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonCommonCodegenPackagePrefixTest.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * 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 + * + * http://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.java.helidon; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Assert; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.DefaultGenerator; +import org.openapitools.codegen.TestUtils; +import org.openapitools.codegen.config.CodegenConfigurator; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class JavaHelidonCommonCodegenPackagePrefixTest { + + private static final String INPUT_FILE = "src/test/resources/3_0/helidon/petstore-for-testing.yaml"; + private static final String PACKAGE_PREFIX_KEY = "rootJavaEEPackage"; + private static final String HELIDON_VERSION_KEY = "helidonVersion"; + + private static final String EXCEPTION_MESSAGE_FRAGMENT = "namespace but options specified"; + + // The generated SE client does not depend on the jakarta/javax imports, so no need to test it. + private static final List> GENERATOR_LIBRARY_PAIRS = new ArrayList>() { + { + add(listOf("java-helidon-client", "mp")); + add(listOf("java-helidon-server", "se")); + add(listOf("java-helidon-server", "mp")); + } + }; + + private String outputDir; + + @BeforeMethod + public void setup() throws IOException { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + outputDir = output.getAbsolutePath().replace('\\', '/'); + } + + @Test(dataProvider = "valid") + public void checkValidCombinations(String explicitHelidonVersion, + String explicitPrefix, + String expectedPrefix, + String generatorName, + String libraryName) { + List files = runTest(explicitHelidonVersion, explicitPrefix, generatorName, libraryName); + checkFileForPackagePrefix(files, generatorName, libraryName, expectedPrefix); + } + + @Test(dataProvider = "invalid") + public void checkInvalidCombinations(String explicitHelidonVersion, + String explicitPrefix, + String generatorName, + String libraryName) { + IllegalArgumentException e = Assert.assertThrows(IllegalArgumentException.class, + () -> runTest(explicitHelidonVersion, explicitPrefix, generatorName, libraryName)); + Assert.assertTrue("Exception message '" + e.getMessage() + "' contains '" + EXCEPTION_MESSAGE_FRAGMENT + "'", + e.getMessage().contains(EXCEPTION_MESSAGE_FRAGMENT)); + } + + @DataProvider(name = "valid") + public Object [][] createValidData() { + Object [][] settingsForEachRun = new Object[][] { + {null, null, "jakarta"}, + {"3.0.1", null, "jakarta"}, + {"2.5.3", null, "javax"}, + {null, "jakarta", "jakarta"}, + {"3.0.1", "jakarta", "jakarta"}, + {"2.5.3", "javax", "javax"} + }; + + return prepareTestData(settingsForEachRun); + } + + @DataProvider(name = "invalid") + public Object [][] createInvalidData() { + Object [][] settingsForEachRun = new Object[][] { + {"2.5.3", "jakarta"}, + {null, "javax"}, + {"3.0.1", "javax"} + }; + + return prepareTestData(settingsForEachRun); + } + + /** + * Creates test data for each tested generator/library pair for all the version/prefix settings. + * + * @param settingsForEachRun version/prefix settings to test + * @return test data for driving a test method + */ + private Object[][] prepareTestData(Object[][] settingsForEachRun) { + Object [][] result = new Object[GENERATOR_LIBRARY_PAIRS.size() * settingsForEachRun.length][]; + int resultSlot = 0; + + int settingsLength = settingsForEachRun[0].length; + for (List generatorLibraryPair : GENERATOR_LIBRARY_PAIRS) { + for (Object[] settings : settingsForEachRun) { + result[resultSlot] = Arrays.copyOf(settings, settingsLength + 2); + result[resultSlot][settingsLength] = generatorLibraryPair.get(0); // generator + result[resultSlot][settingsLength + 1] = generatorLibraryPair.get(1); // library + resultSlot++; + } + } + return result; + } + + private static List listOf(String... values) { + return new ArrayList<>(Arrays.asList(values)); + } + + private List runTest(String explicitHelidonVersion, + String explicitPackagePrefix, + String generatorName, + String libraryName) { + Map additionalProperties = new HashMap<>(); + CodegenConfigurator clientConfigurator = new CodegenConfigurator() + .setGeneratorName(generatorName) + .setLibrary(libraryName) + .setInputSpec(INPUT_FILE) + .setOutputDir(outputDir); + + if (explicitHelidonVersion != null) { + additionalProperties.put(HELIDON_VERSION_KEY, explicitHelidonVersion); + } + if (explicitPackagePrefix != null) { + additionalProperties.put(PACKAGE_PREFIX_KEY, explicitPackagePrefix); + } + + // Use JSON-B for serialization to force jakarta or json imports into the generated POJOs. + additionalProperties.put(CodegenConstants.SERIALIZATION_LIBRARY, "jsonb"); + + clientConfigurator.setAdditionalProperties(additionalProperties); + + DefaultGenerator generator = new DefaultGenerator(); + generator.opts(clientConfigurator.toClientOptInput()); + + return generator.generate(); + } + + private void checkFileForPackagePrefix(List files, + String generatorName, + String libraryName, + String expectedPrefix) { + // The SE client does not use the rootJavaEEPackage so we don't check any file in that case. + if (generatorName.equals("java-helidon-client") && libraryName.equals("se")) { + return; + } + + // The MP client and server generator create PetAPI containing a wildcard include. + if (libraryName.equals("mp")) { + TestUtils.ensureContainsFile(files, Paths.get(outputDir).toFile(), generatedFilePath(generatorName, libraryName)); + TestUtils.assertFileContains(Paths.get(outputDir + "/" + generatedFilePath(generatorName, libraryName)), + "import " + expectedPrefix + ".ws.rs.*;"); + return; + } + + // The SE server generates 'import {{rootJavaEEPackage}}.json.stream.JsonParser;' in POJOs for JSON-B seriolization. + TestUtils.ensureContainsFile(files, Paths.get(outputDir).toFile(), generatedFilePath(generatorName, libraryName)); + TestUtils.assertFileContains(Paths.get(outputDir + "/" + generatedFilePath(generatorName, libraryName)), + "import " + expectedPrefix + ".json.stream.JsonParser;"); + } + + private String generatedFilePath(String generatorName, String libraryName) { + // The path to the file depends on client or server. + String serverOrClient = (generatorName.contains("server") ? "server" : "client"); + + // The file to check depends on the generator: e.g., PetApi for client, PetService for server. + String apiFileNameSuffix = (generatorName.contains("server") ? "Service" : "Api"); + + // For MP, check api/PetApi or api/PetService; for SE check model/Pet.java. + String filePath = (libraryName.equals("mp") + ? "api/Pet" + apiFileNameSuffix + : "model/Pet") + + ".java"; + + return "src/main/java/org/openapitools/" + + serverOrClient + + "/" + + filePath; + } + +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonCommonCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonCommonCodegenTest.java new file mode 100644 index 00000000000..d399c97de46 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonCommonCodegenTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates + * + * 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.java.helidon; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.junit.Assert; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.DefaultGenerator; +import org.openapitools.codegen.TestUtils; +import org.openapitools.codegen.config.CodegenConfigurator; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class JavaHelidonCommonCodegenTest { + + private DefaultGenerator generator; + private CodegenConfigurator configurator; + private String outputDir; + + @BeforeMethod + public void setup() throws IOException { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + outputDir = output.getAbsolutePath().replace('\\', '/'); + + configurator = new CodegenConfigurator() + .setGeneratorName("java-helidon-server") + .setLibrary("mp") + .setInputSpec("src/test/resources/3_0/helidon/petstore-for-testing.yaml") + .setOutputDir(outputDir); + + generator = new DefaultGenerator(); + } + + @Test + public void defaultVersionTest() { + runVersionTest(null, null); + } + + @Test + public void customHelidonVersionOnlyTest() { + runVersionTest("3.0.0", null); + } + + @Test + public void customParentVersionOnlyTest() { + runVersionTest(null, "3.0.0"); + } + + @Test + public void bothEqualsVersionTest() { + runVersionTest("3.0.0", "3.0.0"); + } + + @Test + public void bothNotEqualsVersionTest() { + IllegalArgumentException e = Assert.assertThrows(IllegalArgumentException.class,() -> runVersionTest("1.0.0", "2.0.0")); + Assert.assertEquals( + "Both parentVersion and helidonVersion properties were set with different value.", + e.getMessage()); + } + + private void runVersionTest(String helidonVersion, String parentVersion) { + Map additionalProperties = new HashMap<>(); + String expected = "3.0.1"; + if (parentVersion != null) { + additionalProperties.put(CodegenConstants.PARENT_VERSION, parentVersion); + expected = parentVersion; + } + if (helidonVersion != null) { + additionalProperties.put("helidonVersion", helidonVersion); + expected = helidonVersion; + } + generator.opts(configurator.setAdditionalProperties(additionalProperties) + .toClientOptInput()); + List files = generator.generate(); + + TestUtils.ensureContainsFile(files, Paths.get(outputDir).toFile(), "pom.xml"); + TestUtils.assertFileContains(Paths.get(outputDir + "/pom.xml"), + String.format(Locale.ROOT, "%s", expected)); + } + +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonMpClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonMpClientCodegenTest.java new file mode 100644 index 00000000000..28a03cd6944 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonMpClientCodegenTest.java @@ -0,0 +1,97 @@ +package org.openapitools.codegen.java.helidon; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +import org.openapitools.codegen.ClientOptInput; +import org.openapitools.codegen.DefaultGenerator; +import org.openapitools.codegen.TestUtils; +import org.openapitools.codegen.config.CodegenConfigurator; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.openapitools.codegen.java.assertions.JavaFileAssert.assertThat; + +public class JavaHelidonMpClientCodegenTest { + + private String outputPath; + private List generatedFiles; + + @BeforeClass + public void setup() throws IOException { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + outputPath = output.getAbsolutePath().replace('\\', '/'); + + System.out.println("Generating java-helidon-client MP project in " + outputPath); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java-helidon-client") + .setLibrary("mp") + .setInputSpec("src/test/resources/3_0/helidon/petstore-no-multipart-for-testing.yaml") + .setOutputDir(outputPath); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + generator.opts(clientOptInput); + generatedFiles = generator.generate(); + } + + @Test + public void testPom() { + TestUtils.ensureContainsFile(generatedFiles, new File(outputPath), "pom.xml"); + } + + @Test + public void testPetApi() { + assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/client/api/PetApi.java")) + .assertMethod("addPet", "Pet") + .toFileAssert() + .assertMethod("deletePet", "Long", "String", "Long", "String", "Integer", + "List", "List") + .toFileAssert() + .assertMethod("findPetsByStatus", "List") + .toFileAssert() + .assertMethod("findPetsByTags", "List") + .toFileAssert() + .assertMethod("getPetById", "Long") + .toFileAssert() + .assertMethod("updatePet", "Pet") + .toFileAssert() + .assertMethod("updatePetWithForm", "Long", "String", "String") + .toFileAssert(); + } + + @Test + public void testStoreApi() { + assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/client/api/StoreApi.java")) + .assertMethod("deleteOrder", "String") + .toFileAssert() + .assertMethod("getInventory") + .toFileAssert() + .assertMethod("getOrderById", "BigDecimal") + .toFileAssert() + .assertMethod("placeOrder", "Order") + .toFileAssert(); + } + + @Test + public void testUserApi() { + assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/client/api/UserApi.java")) + .assertMethod("createUser", "User") + .toFileAssert() + .assertMethod("createUsersWithArrayInput", "List") + .toFileAssert() + .assertMethod("createUsersWithListInput", "List") + .toFileAssert() + .assertMethod("getUserByName", "String") + .toFileAssert() + .assertMethod("loginUser", "String", "String", "String", "Long", "BigDecimal") + .toFileAssert() + .assertMethod("updateUser", "String", "User") + .toFileAssert(); + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonMpServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonMpServerCodegenTest.java new file mode 100644 index 00000000000..599ef9f43d6 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonMpServerCodegenTest.java @@ -0,0 +1,192 @@ +package org.openapitools.codegen.java.helidon; + +import org.openapitools.codegen.DefaultGenerator; +import org.openapitools.codegen.TestUtils; +import org.openapitools.codegen.config.CodegenConfigurator; +import org.openapitools.codegen.java.assertions.JavaFileAssert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Objects; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import static org.openapitools.codegen.CodegenConstants.SERIALIZATION_LIBRARY; + +public class JavaHelidonMpServerCodegenTest { + + private DefaultGenerator generator; + private String outputPath; + private String apiPackage; + private String modelPackage; + + @BeforeMethod + public void setup() throws IOException { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + outputPath = output.getAbsolutePath().replace('\\', '/'); + apiPackage = outputPath + "/src/main/java/org/openapitools/server/api"; + modelPackage = outputPath + "/src/main/java/org/openapitools/server/model"; + generator = new DefaultGenerator(); + } + + private CodegenConfigurator createConfigurator() { + return new CodegenConfigurator() + .setGeneratorName("java-helidon-server") + .setLibrary("mp") + .setInputSpec("src/test/resources/3_0/helidon/petstore-for-testing.yaml") + .setOutputDir(outputPath); + } + + private void generate(CodegenConfigurator config) { + generator.opts(config.toClientOptInput()); + generator.setGenerateMetadata(false); + generator.generate(); + } + + private void generate() { + generate(createConfigurator()); + } + + @Test + public void testRestApiFilesOnly() { + generate(createConfigurator().addAdditionalProperty("fullProject", "false")); + + JavaFileAssert.assertThat(Paths.get(apiPackage + "/PetService.java")) + .fileContains("public interface PetService"); + + File outputFile = Paths.get(outputPath).toFile(); + assertThat(Objects.requireNonNull(outputFile.listFiles()).length, is(1)); + } + + @Test + public void testJackson() { + generate(createConfigurator().addAdditionalProperty(SERIALIZATION_LIBRARY, "jackson")); + + JavaFileAssert.assertThat(Paths.get(modelPackage + "/Color.java")) + .fileContains("com.fasterxml.jackson.annotation.JsonCreator") + .fileContains("com.fasterxml.jackson.annotation.JsonValue"); + } + + @Test + public void testJsonb() { + generate(createConfigurator().addAdditionalProperty(SERIALIZATION_LIBRARY, "jsonb")); + + JavaFileAssert.assertThat(Paths.get(modelPackage + "/Color.java")) + .fileContains(".json.bind.annotation.JsonbCreator"); + } + + @Test + public void testAbstractClass() { + generate(createConfigurator().addAdditionalProperty("useAbstractClass", "true")); + + JavaFileAssert.assertThat(Paths.get(apiPackage + "/PetService.java")) + .fileContains("public abstract class PetService") + .assertMethod("addPet", "Pet") + .doesNotHaveImplementation(); + + JavaFileAssert.assertThat(Paths.get(apiPackage + "/StoreService.java")) + .fileContains("public abstract class StoreService") + .assertMethod("placeOrder", "Order") + .doesNotHaveImplementation() + .hasReturnType("Response"); + + JavaFileAssert.assertThat(Paths.get(apiPackage + "/StoreServiceImpl.java")) + .fileContains("public class StoreServiceImpl extends StoreService") + .assertMethod("placeOrder", "Order") + .hasReturnType("Response") + .bodyContainsLines("return Response.ok().entity(\"magic!\").build();"); + } + + @Test + public void testFullProject() { + generate(createConfigurator().addAdditionalProperty("fullProject", "true")); + + JavaFileAssert.assertThat(Paths.get(apiPackage + "/PetService.java")) + .fileContains("public interface PetService") + .assertMethod("addPet", "Pet"); + + JavaFileAssert.assertThat(Paths.get(apiPackage + "/StoreService.java")) + .fileContains("public interface StoreService") + .assertMethod("placeOrder", "Order") + .hasReturnType("Response"); + } + + @Test + public void validatePetApi() { + generate(); + + JavaFileAssert.assertThat(Paths.get(apiPackage + "/PetService.java")) + .fileContains("org.openapitools.server.model.Pet") + .assertMethod("addPet", "Pet") + .toFileAssert() + .assertMethod("addPets", "String", "InputStream", "InputStream", "List", "List", "Integer") + .toFileAssert() + .assertMethod("deletePet", "Long", "String", "Long", "String", "Integer", "List", "List") + .toFileAssert() + .assertMethod("findPetsByStatus", "List") + .toFileAssert() + .assertMethod("findPetsByTags", "List") + .toFileAssert() + .assertMethod("getPetById", "Long") + .toFileAssert() + .assertMethod("updatePet", "Pet") + .toFileAssert() + .assertMethod("updatePetWithForm", "Long", "String", "String") + .toFileAssert() + .assertMethod("uploadFile", "Long", "Long", "String", "InputStream"); + } + + @Test + public void validateStoreApi() { + generate(); + + JavaFileAssert.assertThat(Paths.get(apiPackage + "/StoreService.java")) + .fileContains("org.openapitools.server.model.Order") + .assertMethod("deleteOrder", "String") + .toFileAssert() + .assertMethod("getInventory") + .toFileAssert() + .assertMethod("getOrderById", "BigDecimal") + .toFileAssert() + .assertMethod("placeOrder", "Order"); + } + + @Test + public void validateUserApi() { + generate(); + + JavaFileAssert.assertThat(Paths.get(apiPackage + "/UserService.java")) + .fileContains("org.openapitools.server.model.User") + .assertMethod("createUser", "User") + .toFileAssert() + .assertMethod("createUsersWithArrayInput", "List") + .toFileAssert() + .assertMethod("createUsersWithListInput", "List") + .toFileAssert() + .assertMethod("deleteUser", "String") + .toFileAssert() + .assertMethod("getUserByName", "String") + .toFileAssert() + .assertMethod("loginUser", "String", "String", "String", "Long", "BigDecimal") + .toFileAssert() + .assertMethod("logoutUser") + .toFileAssert() + .assertMethod("updateUser", "String", "User"); + } + + @Test + public void testGenerateGradleProject() { + generate(createConfigurator().addAdditionalProperty("gradleProject", "true")); + + assertThat(Paths.get(outputPath + "/build.gradle").toFile().exists(), is(true)); + assertThat(Paths.get(outputPath + "/settings.gradle").toFile().exists(), is(true)); + TestUtils.assertFileNotExists(Paths.get(outputPath + "/pom.xml")); + } + +} \ No newline at end of file diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonSeClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonSeClientCodegenTest.java new file mode 100644 index 00000000000..8ce80c3aca6 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonSeClientCodegenTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * 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 + * + * http://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.java.helidon; + +import org.openapitools.codegen.ClientOptInput; +import org.openapitools.codegen.DefaultGenerator; +import org.openapitools.codegen.TestUtils; +import org.openapitools.codegen.config.CodegenConfigurator; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +import static org.openapitools.codegen.java.assertions.JavaFileAssert.assertThat; + +public class JavaHelidonSeClientCodegenTest { + + private String outputPath; + private List generatedFiles; + + @BeforeClass + public void setup() throws IOException { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + outputPath = output.getAbsolutePath().replace('\\', '/'); + + System.out.println("Generating java-helidon-client SE project in " + outputPath); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java-helidon-client") + .setLibrary("se") + .setInputSpec("src/test/resources/3_0/helidon/petstore-no-multipart-for-testing.yaml") + .setOutputDir(outputPath); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + generator.opts(clientOptInput); + generatedFiles = generator.generate(); + } + + @DataProvider(name = "fileSuffix") + public Object[][] fileSuffixes() { + return new Object[][] { + {""}, + {"Impl"} + }; + } + + @Test + public void testPom() { + TestUtils.ensureContainsFile(generatedFiles, new File(outputPath), "pom.xml"); + } + + @Test(dataProvider = "fileSuffix") + public void testPetApi(String fileSuffix) { + assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/client/api/PetApi" + fileSuffix + ".java")) + .assertMethod("addPet", "Pet") + .toFileAssert() + .assertMethod("deletePet", "Long", "String", "Long", "String", "Integer", + "List", "List") + .toFileAssert() + .assertMethod("findPetsByStatus", "List") + .toFileAssert() + .assertMethod("findPetsByTags", "List") + .toFileAssert() + .assertMethod("getPetById", "Long") + .toFileAssert() + .assertMethod("updatePet", "Pet") + .toFileAssert() + .assertMethod("updatePetWithForm", "Long", "String", "String") + .toFileAssert(); + } + + @Test(dataProvider = "fileSuffix") + public void testStoreApi(String fileSuffix) { + assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/client/api/StoreApi" + fileSuffix + ".java")) + .assertMethod("deleteOrder", "String") + .toFileAssert() + .assertMethod("getInventory") + .toFileAssert() + .assertMethod("getOrderById", "BigDecimal") + .toFileAssert() + .assertMethod("placeOrder", "Order") + .toFileAssert(); + } + + @Test(dataProvider = "fileSuffix") + public void testUserApi(String fileSuffix) { + assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/client/api/UserApi" + fileSuffix + ".java")) + .assertMethod("createUser", "User") + .toFileAssert() + .assertMethod("createUsersWithArrayInput", "List") + .toFileAssert() + .assertMethod("createUsersWithListInput", "List") + .toFileAssert() + .assertMethod("getUserByName", "String") + .toFileAssert() + .assertMethod("loginUser", "String", "String", "String", "Long", "BigDecimal") + .toFileAssert() + .assertMethod("updateUser", "String", "User") + .toFileAssert(); + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonSeServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonSeServerCodegenTest.java new file mode 100644 index 00000000000..fb629a114ed --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/JavaHelidonSeServerCodegenTest.java @@ -0,0 +1,289 @@ +package org.openapitools.codegen.java.helidon; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import org.openapitools.codegen.ClientOptInput; +import org.openapitools.codegen.DefaultGenerator; +import org.openapitools.codegen.Generator; +import org.openapitools.codegen.TestUtils; +import org.openapitools.codegen.config.CodegenConfigurator; +import org.openapitools.codegen.java.assertions.JavaFileAssert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertTrue; + +public class JavaHelidonSeServerCodegenTest { + + private DefaultGenerator generator; + private String outputPath; + + @BeforeMethod + public void setup() throws IOException { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + outputPath = output.getAbsolutePath().replace('\\', '/'); + + final CodegenConfigurator configurator = codegenConfigurator(new HashMap<>()); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + generator = new DefaultGenerator(); + generator.opts(clientOptInput); + } + + private CodegenConfigurator codegenConfigurator(Map additionalProperties) { + return new CodegenConfigurator() + .setGeneratorName("java-helidon-server") + .setLibrary("se") + .setAdditionalProperties(additionalProperties) + .setInputSpec("src/test/resources/3_0/helidon/petstore-for-testing.yaml") + .setOutputDir(outputPath); + } + + + @Test + public void testGenerateFullProject() { + generator.generate(); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/server/api/PetServiceImpl.java")) + .fileContains( + "public class PetServiceImpl", + "response.status(HTTP_CODE_NOT_IMPLEMENTED).send();" + ); + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/server/Main.java")) + .fileContains( + "import org.openapitools.server.api.PetServiceImpl;", + ".register(\"/\", new PetServiceImpl())" + ); + } + + @Test + public void testGenerateProjectByDefault() { + generator.generate(); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/server/api/PetService.java")) + .fileContains( + "public interface PetService extends Service {", + "default void update(Routing.Rules rules) {", + "void addPet(ServerRequest request, ServerResponse response, Pet pet);", + "void deletePet(ServerRequest request, ServerResponse response);" + ); + TestUtils.assertFileNotExists(Paths.get(outputPath + "/build.gradle")); + TestUtils.assertFileNotExists(Paths.get(outputPath + "/settings.gradle")); + } + + @Test + public void testGenerateGradleProject() { + Map additionalProperties = new HashMap<>(); + additionalProperties.put("gradleProject", true); + final CodegenConfigurator configurator = codegenConfigurator(additionalProperties); + generator.opts(configurator.toClientOptInput()).generate(); + + assertTrue(Paths.get(outputPath + "/build.gradle").toFile().exists()); + assertTrue(Paths.get(outputPath + "/settings.gradle").toFile().exists()); + TestUtils.assertFileNotExists(Paths.get(outputPath + "/pom.xml")); + } + + @Test + public void testGeneratePathParams() throws IOException { + Map additionalProperties = new HashMap<>(); + additionalProperties.put("useAbstractClass", true); + final CodegenConfigurator configurator = codegenConfigurator(additionalProperties); + generator.opts(configurator.toClientOptInput()).generate(); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/server/api/PetService.java")) + .assertMethod("deletePet", "ServerRequest", "ServerResponse") + .bodyContainsLines( + "Long petId = Optional.ofNullable(request.path().param(\"petId\")).map(Long::valueOf).orElse" + + "(null);", + "ValidatorUtils.checkNonNull(petId);" + ) + .toFileAssert() + .assertMethod("getPetById") + .bodyContainsLines( + "Long petId = Optional.ofNullable(request.path().param(\"petId\")).map(Long::valueOf).orElse" + + "(null);", + "ValidatorUtils.checkNonNull(petId);" + ); + } + + @Test + public void testGenerateQueryParams() throws IOException { + Map additionalProperties = new HashMap<>(); + additionalProperties.put("useAbstractClass", true); + final CodegenConfigurator configurator = codegenConfigurator(additionalProperties); + generator.opts(configurator.toClientOptInput()).generate(); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/server/api/PetService.java")) + .fileContains("import java.util.List;") + .assertMethod("findPetsByTags") + .bodyContainsLines( + "List tags = Optional.ofNullable(request.queryParams().toMap().get(\"tags\"))" + + ".orElse(null);", + "ValidatorUtils.checkNonNull(tags);" + ) + .toFileAssert() + .assertMethod("findPetsByStatus") + .bodyContainsLines( + "List status = Optional.ofNullable(request.queryParams().toMap().get(\"status\")).orElse" + + "(null);", + "ValidatorUtils.checkNonNull(status);" + ); + } + + @Test + public void testGenerateBodyParams() throws IOException { + Map additionalProperties = new HashMap<>(); + additionalProperties.put("useAbstractClass", true); + final CodegenConfigurator configurator = codegenConfigurator(additionalProperties); + generator.opts(configurator.toClientOptInput()).generate(); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/server/api/PetService.java")) + .assertMethod("update") + .bodyContainsLines( + "rules.post(\"/pet\", Handler.create(Pet.class, this::addPet));", + "rules.put(\"/pet\", Handler.create(Pet.class, this::updatePet));" + ) + .toFileAssert() + .assertMethod("addPet", "ServerRequest", "ServerResponse", "Pet") + .bodyContainsLines( + "ValidatorUtils.checkNonNull(pet);", + "handleAddPet(request, response, pet);" + ) + .toFileAssert() + .assertMethod("updatePet", "ServerRequest", "ServerResponse", "Pet") + .bodyContainsLines( + "ValidatorUtils.checkNonNull(pet);", + "handleUpdatePet(request, response, pet);" + ); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/server/api/UserService.java")) + .assertMethod("update") + .bodyContainsLines( + "rules.post(\"/user\", Handler.create(User.class, this::createUser));", + "rules.post(\"/user/createWithArray\", this::createUsersWithArrayInput);", + "rules.post(\"/user/createWithList\", this::createUsersWithListInput);", + "rules.put(\"/user/{username}\", Handler.create(User.class, this::updateUser));" + ) + .toFileAssert() + .assertMethod("createUser", "ServerRequest", "ServerResponse", "User") + .bodyContainsLines( + "ValidatorUtils.checkNonNull(user);", + "handleCreateUser(request, response, user);" + ) + .toFileAssert() + .assertMethod("createUsersWithArrayInput", "ServerRequest", "ServerResponse") + .bodyContainsLines( + "Single.create(request.content().as(new GenericType>() { }))", + ".thenAccept(user -> {", + "ValidatorUtils.checkNonNull(user);", + "handleCreateUsersWithArrayInput(request, response, user);", + ".exceptionally(throwable -> handleError(request, response, throwable));" + ); + } + + @Test + public void testGenerateHeaderParams() throws IOException { + Map additionalProperties = new HashMap<>(); + additionalProperties.put("useAbstractClass", true); + final CodegenConfigurator configurator = codegenConfigurator(additionalProperties); + generator.opts(configurator.toClientOptInput()).generate(); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/server/api/PetService.java")) + .assertMethod("deletePet", "ServerRequest", "ServerResponse") + .bodyContainsLines( + "String apiKey = request.headers().value(\"api_key\").orElse(null);", + "Long headerLong = request.headers().value(\"headerLong\").map(Long::valueOf).orElse(null);", + "ValidatorUtils.checkNonNull(headerLong);" + ); + } + + @Test + public void testGenerateCookiesParams() throws IOException { + Map additionalProperties = new HashMap<>(); + additionalProperties.put("useAbstractClass", true); + final CodegenConfigurator configurator = codegenConfigurator(additionalProperties); + generator.opts(configurator.toClientOptInput()).generate(); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/server/api/PetService.java")) + .assertMethod("deletePet", "ServerRequest", "ServerResponse") + .bodyContainsLines( + "String cookieString = request.headers().cookies().toMap().getOrDefault(\"cookieString\", List.of" + + "()).stream().findFirst().orElse(null);", + "ValidatorUtils.checkNonNull(cookieString);", + "Integer cookieInt = request.headers().cookies().toMap().getOrDefault(\"cookieInt\", List.of())" + + ".stream().findFirst().map(Integer::valueOf).orElse(null);", + "List cookieIntArray = Optional.ofNullable(request.headers().cookies().toMap().get" + + "(\"cookieIntArray\")).orElse(null);", + "List cookieStringArray = Optional.ofNullable(request.headers().cookies().toMap().get" + + "(\"cookieStringArray\")).orElse(null);" + ); + } + + @Test + public void testGenerateFormParams() throws IOException { + Map additionalProperties = new HashMap<>(); + additionalProperties.put("useAbstractClass", true); + final CodegenConfigurator configurator = codegenConfigurator(additionalProperties); + generator.opts(configurator.toClientOptInput()).generate(); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/server/api/PetService.java")) + .assertMethod("addPets", "ServerRequest", "ServerResponse") + .bodyContainsLines( + "Map> nonFileFormContent = new HashMap<>();", + "Map> fileFormContent = new HashMap<>();", + " Single formSingle = request.content().asStream(ReadableBodyPart.class)", + "if (\"images[]\".equals(name)) {", + "processFileFormField(name, fileFormContent, part);", + "if (\"image\".equals(name)) {", + "if (\"titles[]\".equals(name)) {", + "processNonFileFormField(name, nonFileFormContent, part);", + "if (\"longArray\".equals(name)) {", + "if (\"stringParam\".equals(name)) {", + "if (\"intParam\".equals(name)) {", + "List images = Optional.ofNullable(fileFormContent.get(\"images[]\")).orElse(null);", + "InputStream image = Optional.ofNullable(fileFormContent.get(\"image\")).flatMap(list->list" + + ".stream().findFirst()).orElse(null);", + "List titles = Optional.ofNullable(nonFileFormContent.get(\"titles[]\")).orElse(null);", + "List longArray = Optional.ofNullable(nonFileFormContent.get(\"longArray\")).orElse(null);", + "Integer intParam = Optional.ofNullable(nonFileFormContent.get(\"intParam\")).flatMap(list->list" + + ".stream().findFirst()).map(Integer::valueOf).orElse(null);" + ); + } + + @Test + public void testGenerateParamsValidation() throws IOException { + Map additionalProperties = new HashMap<>(); + additionalProperties.put("useAbstractClass", true); + final CodegenConfigurator configurator = codegenConfigurator(additionalProperties); + generator.opts(configurator.toClientOptInput()).generate(); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/server/api/PetService.java")) + .assertMethod("findPetsByStatus") + .bodyContainsLines( + "ValidatorUtils.checkNonNull(status);", + "List status = Optional.ofNullable(request.queryParams().toMap().get(\"status\")).orElse" + + "(null);" + ) + .toFileAssert() + .assertMethod("findPetsByTags") + .bodyContainsLines( + "List tags = Optional.ofNullable(request.queryParams().toMap().get(\"tags\")).orElse" + + "(null);", + "ValidatorUtils.checkNonNull(tags);" + ); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/server/api/UserService.java")) + .assertMethod("loginUser") + .bodyContainsLines( + "ValidatorUtils.validatePattern(username, \"^[a-zA-Z0-9]+[a-zA-Z0-9\\\\" + + ".\\\\-_]*[a-zA-Z0-9]+$\");", + "" + ); + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalBase.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalBase.java new file mode 100644 index 00000000000..c7094725ad1 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalBase.java @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates + * + * 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.java.helidon.functional; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import org.openapitools.codegen.DefaultGenerator; +import org.openapitools.codegen.config.CodegenConfigurator; +import org.openapitools.codegen.languages.JavaHelidonCommonCodegen; +import org.testng.SkipException; + +import static java.util.Objects.requireNonNull; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +abstract class FunctionalBase { + + private static final Logger LOGGER = Logger.getLogger(FunctionalBase.class.getName()); + + private static final String MAVEN_SHIM_TARGET = "libexec/bin/mvn"; + private static final String MAVEN_HOME_VAR = "MAVEN_HOME"; + private static final String MVN_HOME_VAR = "MVN_HOME"; + private static final String PATH_VAR = "PATH"; + private static final String MAVEN_BINARY_NAME; + private static final boolean IS_WINDOWS_OS; + private static final List> DEFAULT_HELIDON_VERSIONS_FOR_JAVA_VERSIONS = new ArrayList<>(); + + protected static final String FULL_PROJECT = "fullProject"; + protected static final String USE_ABSTRACT_CLASS = "useAbstractClass"; + + static { + /* + The inferred Helidon version for tests is from the entry for which the Java major version does not exceed + the current runtime Java major version. + + For example, for Java 8 or 9 or 11: 2.5.3. For Java 13 or later: 3.0.1. + */ + DEFAULT_HELIDON_VERSIONS_FOR_JAVA_VERSIONS.add(new AbstractMap.SimpleEntry<>(11, "2.5.3")); + DEFAULT_HELIDON_VERSIONS_FOR_JAVA_VERSIONS.add(new AbstractMap.SimpleEntry<>(13, "3.0.1")); + } + + private String library; + private String generatorName; + private String inputSpec; + protected Path outputPath; + + private Path mvn; + + static { + IS_WINDOWS_OS = System.getProperty("os.name", "unknown") + .toLowerCase(Locale.ENGLISH) + .contains("win"); + MAVEN_BINARY_NAME = IS_WINDOWS_OS ? "mvn.cmd" : "mvn"; + } + + protected CodegenConfigurator createConfigurator() { + try { + return createConfigurator(Files.createTempDirectory("test")); + } catch (IOException e) { + throw new UncheckedIOException("Can not create temp directory", e); + } + } + + protected CodegenConfigurator createConfigurator(Path outputPath) { + Objects.requireNonNull(inputSpec); + this.outputPath = outputPath; + String sanitizedPath = outputPath.toFile() + .getAbsolutePath() + .replace('\\', '/'); + return new CodegenConfigurator() + .setGeneratorName(generatorName) + .setLibrary(library) + .setInputSpec(inputSpec) + .setOutputDir(sanitizedPath); + + } + + protected void generate(CodegenConfigurator config) { + String helidonVersionToUse = chooseHelidonVersion(config); + enforceJavaVersion(helidonVersionToUse); + DefaultGenerator generator = new DefaultGenerator(); + generator.opts(config.toClientOptInput()); + generator.generate(); + } + + protected void generate() { + generate(createConfigurator()); + } + + protected void generate(String inputSpec) { + inputSpec(inputSpec); + generate(createConfigurator()); + } + + protected void generatorName(String generatorName) { + this.generatorName = generatorName; + } + + protected void library(String library) { + this.library = library; + } + + protected void inputSpec(String inputSpec) { + this.inputSpec = inputSpec; + } + + /** + * Run maven command with provided arguments. + * + * @param args maven command arguments + * @return a {@link ProcessReader} + */ + protected ProcessReader runMavenProcess(String... args) { + return runMavenProcess(outputPath.toFile(), args); + } + + /** + * Run maven command and causes the current thread to wait for {@link Process} to terminate. + * + * @param args maven command arguments + * @return a {@link ProcessReader} + */ + protected ProcessReader runMavenProcessAndWait(String... args) { + ProcessReader process = runMavenProcess(args); + process.waitFor(10, TimeUnit.MINUTES); + return process; + } + + /** + * Run maven command in the provided directory. + * + * @param directory from where the command is executed + * @param args maven command arguments + * @return a {@link ProcessReader} + */ + protected ProcessReader runMavenProcess(File directory, String... args) { + List command = new ArrayList<>(Collections.singleton(mavenExecutable())); + Collections.addAll(command, args); + try { + Process process = new ProcessBuilder() + .directory(directory) + .command(command) + .start(); + return new ProcessReader(process); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Finds the {@code mvn} executable. Searches using the following, in order: + *
    + *
  1. The {@code MAVEN_HOME} environment variable
  2. + *
  3. The {@code MVN_HOME} environment variable
  4. + *
  5. The {@code PATH} environment variable
  6. + *
+ * + * @return The path. + */ + public String mavenExecutable() { + if (mvn == null) { + Path maven; + Optional path = findExecutableInPath(); + if (path.isPresent()) { + maven = path.get(); + } else { + maven = toMavenExecutable(MAVEN_HOME_VAR); + if (maven == null) { + maven = toMavenExecutable(MVN_HOME_VAR); + } + } + try { + assumeTrue( "Maven not found, test is skipped", maven != null); + maven = maven.toRealPath(); + Path shimmed = maven.getParent().getParent().resolve(MAVEN_SHIM_TARGET); + if (Files.exists(shimmed)) { + maven = shimmed; + } + mvn = maven.toRealPath(); + } catch (IOException ex) { + throw new IllegalStateException(ex.getMessage()); + } + } + return mvn.toString(); + } + + private String chooseHelidonVersion(CodegenConfigurator config) { + Map unprocessedAdditionalProperties = config.toContext() + .getGeneratorSettings() + .getAdditionalProperties(); + if (unprocessedAdditionalProperties.containsKey(JavaHelidonCommonCodegen.HELIDON_VERSION)) { + return unprocessedAdditionalProperties.get(JavaHelidonCommonCodegen.HELIDON_VERSION).toString(); + } + String result = inferredHelidonVersion(); + config.addAdditionalProperty(JavaHelidonCommonCodegen.HELIDON_VERSION, result); + return result; + } + + private void enforceJavaVersion(String helidonVersionToUse) { + int currentJavaVersion = getCurrentJavaMajorVersion(); + int requiredJavaVersion = getRequiredJavaVersion(helidonVersionToUse); + String errorJavaVersion = String.format(Locale.ROOT, "Java version must be %s, test is skipped", requiredJavaVersion); + assumeTrue(errorJavaVersion, currentJavaVersion == requiredJavaVersion); + } + + private int getRequiredJavaVersion(String helidonVersionToUse) { + return helidonVersionToUse + .startsWith("3.") ? 17 : 11; + } + + private int getCurrentJavaMajorVersion() { + String[] versionElements = System.getProperty("java.version").split("\\."); + int firstElement = Integer.parseInt(versionElements[0]); + if (firstElement == 1) { + return Integer.parseInt(versionElements[1]); + } else { + return firstElement; + } + } + + private String inferredHelidonVersion() { + int javaMajorVersion = getCurrentJavaMajorVersion(); + String result = null; + for (Map.Entry javaToHelidonVersionMapping : DEFAULT_HELIDON_VERSIONS_FOR_JAVA_VERSIONS) { + if (javaToHelidonVersionMapping.getKey() <= javaMajorVersion) { + result = javaToHelidonVersionMapping.getValue(); + } + } + if (result == null) { + String message = String.format(Locale.ROOT, "Unable to infer Helidon version from current Java major version %d using mapping %s", + javaMajorVersion, DEFAULT_HELIDON_VERSIONS_FOR_JAVA_VERSIONS); + LOGGER.log(Level.WARNING, message); + throw new SkipException(message); + } + return result; + } + + /** + * Find an executable in the {@code PATH} environment variable, if present. + * + * @return The path. + */ + private Optional findExecutableInPath() { + return Arrays.stream(requireNonNull(System.getenv(PATH_VAR)).split(File.pathSeparator)) + .map(Paths::get) + .map(path -> path.resolve(FunctionalBase.MAVEN_BINARY_NAME)) + .filter(Files::isExecutable) + .findFirst(); + } + + private Path toMavenExecutable(String mavenHomeEnvVar) { + Path mavenHome = envVarPath(mavenHomeEnvVar); + if (mavenHome != null) { + if (Files.isDirectory(mavenHome)) { + Path executable = mavenHome.resolve("bin").resolve(MAVEN_BINARY_NAME); + if (Files.exists(executable) && (IS_WINDOWS_OS || Files.isExecutable(executable))) { + return executable; + } + } + } + return null; + } + + private static Path envVarPath(String var) { + final String path = System.getenv(var); + return path == null ? null : Paths.get(path); + } + + /** + * Allow junit to skip test without throwing an exception and report tests as failed. + * + * @param message warning message + * @param condition to be checked + */ + protected static void assumeTrue(String message, boolean condition) { + if (!condition) { + LOGGER.log(Level.WARNING, message); + throw new SkipException(message); + } + } + + /** + * Convenience method to build project using Maven and verify test output. + * + * @param jarPath path to expected jar file + */ + protected void buildAndVerify(String jarPath) { + ProcessReader reader = runMavenProcessAndWait("package"); + Path executableJar = outputPath.resolve(jarPath); + String output = reader.readOutputConsole(); + assertThat(output, containsString("BUILD SUCCESS")); + assertThat(output, containsString("Errors: 0")); + assertThat(output, containsString("Failures: 0")); + assertThat(output, containsString("Skipped: 0")); + assertThat(Files.exists(executableJar), is(true)); + } + + /** + * {@link Process} wrapper to read I/O Stream. + */ + static class ProcessReader { + + private final Process process; + private final BufferedReader consoleReader; + + ProcessReader(Process process) { + this.process = process; + this.consoleReader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)); + } + + public String readOutputConsole() { + return consoleReader.lines().collect(Collectors.joining("\n")); + } + + @SuppressWarnings("UnusedReturnValue") + public boolean waitFor(long timeout, TimeUnit unit) { + try { + return process.waitFor(timeout, unit); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonClientBase.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonClientBase.java new file mode 100644 index 00000000000..a3997aea8e3 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonClientBase.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * 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 + * + * http://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.java.helidon.functional; + +import org.testng.annotations.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +public class FunctionalHelidonClientBase extends FunctionalBase { + @Test + void buildPetstore() { + generate("src/test/resources/3_0/petstore.yaml"); + buildAndVerify("target/openapi-java-client.jar"); + } + + @Test + void buildPetstoreWithFakeEndpoints() { + generate("src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml"); + buildAndVerify("target/openapi-java-client.jar"); + } + + @Test + void buildPetstoreNoMultipart() { + generate("src/test/resources/3_0/helidon/petstore-no-multipart-for-testing.yaml"); + buildAndVerify("target/openapi-java-client.jar"); + } + + @Test + void verifyFullProjectSemantics() { + inputSpec("src/test/resources/3_0/petstore.yaml"); + + // Generate project for first time and record pom's timestamp + generate(createConfigurator()); + buildAndVerify("target/openapi-java-client.jar"); + Path pom1 = outputPath.resolve("pom.xml"); + assertThat(Files.exists(pom1), is(true)); + long lastModified = pom1.toFile().lastModified(); + + // Re-generate project over same directory with fullProject unspecified + generate(createConfigurator(outputPath)); + Path pom2 = outputPath.resolve("pom.xml"); + assertThat(Files.exists(pom2), is(true)); + assertThat(pom2.toFile().lastModified(), is(lastModified)); // not overwritten + + // Re-generate project over same directory with fullProject false + generate(createConfigurator(outputPath).addAdditionalProperty(FULL_PROJECT, "false")); + Path pom3 = outputPath.resolve("pom.xml"); + assertThat(Files.exists(pom3), is(true)); + assertThat(pom3.toFile().lastModified(), is(lastModified)); // not overwritten + + // Re-generate project over same directory with fullProject true + generate(createConfigurator(outputPath).addAdditionalProperty(FULL_PROJECT, "true")); + Path pom4 = outputPath.resolve("pom.xml"); + assertThat(Files.exists(pom4), is(true)); + assertThat(pom4.toFile().lastModified(), is(not(lastModified))); // overwritten + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonMPClientTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonMPClientTest.java new file mode 100644 index 00000000000..2837e5f69b8 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonMPClientTest.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates + * + * 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.java.helidon.functional; + +import org.testng.annotations.BeforeClass; + +public class FunctionalHelidonMPClientTest extends FunctionalHelidonClientBase { + + @BeforeClass + public void setup() { + library("mp"); + generatorName("java-helidon-client"); + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonMPServerTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonMPServerTest.java new file mode 100644 index 00000000000..2f62ffcab7d --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonMPServerTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates + * + * 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.java.helidon.functional; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.openapitools.codegen.CodegenConstants.SERIALIZATION_LIBRARY; + +public class FunctionalHelidonMPServerTest extends FunctionalBase { + + @BeforeClass + public void setup() { + library("mp"); + generatorName("java-helidon-server"); + inputSpec("src/test/resources/3_0/helidon/petstore-for-testing.yaml"); + } + + @Test + void buildProjectDefaultOptions() { + generate(); + buildAndVerify("target/openapi-java-server.jar"); + } + + @Test + void buildProjectAbstractClasses() { + generate(createConfigurator().addAdditionalProperty(USE_ABSTRACT_CLASS, "true")); + buildAndVerify("target/openapi-java-server.jar"); + } + + @Test + void buildFullProject() { + generate(createConfigurator().addAdditionalProperty(FULL_PROJECT, "true")); + buildAndVerify("target/openapi-java-server.jar"); + } + + @Test + void verifyFullProjectSemantics() { + // Generate project for first time and record pom's timestamp + generate(createConfigurator()); + buildAndVerify("target/openapi-java-server.jar"); + Path pom1 = outputPath.resolve("pom.xml"); + assertThat(Files.exists(pom1), is(true)); + long lastModified = pom1.toFile().lastModified(); + + // Re-generate project over same directory with fullProject unspecified + generate(createConfigurator(outputPath)); + Path pom2 = outputPath.resolve("pom.xml"); + assertThat(Files.exists(pom2), is(true)); + assertThat(pom2.toFile().lastModified(), is(lastModified)); // not overwritten + + // Re-generate project over same directory with fullProject false + generate(createConfigurator(outputPath).addAdditionalProperty(FULL_PROJECT, "false")); + Path pom3 = outputPath.resolve("pom.xml"); + assertThat(Files.exists(pom3), is(true)); + assertThat(pom3.toFile().lastModified(), is(lastModified)); // not overwritten + + // Re-generate project over same directory with fullProject true + generate(createConfigurator(outputPath).addAdditionalProperty(FULL_PROJECT, "true")); + Path pom4 = outputPath.resolve("pom.xml"); + assertThat(Files.exists(pom4), is(true)); + assertThat(pom4.toFile().lastModified(), is(not(lastModified))); // overwritten + } + + @Test + void buildJsonbProject() { + generate(createConfigurator().addAdditionalProperty(SERIALIZATION_LIBRARY, "jsonb")); + buildAndVerify("target/openapi-java-server.jar"); + } + + @Test + void buildJacksonProject() { + generate(createConfigurator().addAdditionalProperty(SERIALIZATION_LIBRARY, "jackson")); + buildAndVerify("target/openapi-java-server.jar"); + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonSEClientTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonSEClientTest.java new file mode 100644 index 00000000000..1a4341e5c65 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonSEClientTest.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates + * + * 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.java.helidon.functional; + + +import org.testng.annotations.BeforeClass; + +public class FunctionalHelidonSEClientTest extends FunctionalHelidonClientBase { + + @BeforeClass + public void setup() { + library("se"); + generatorName("java-helidon-client"); + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonSeServerTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonSeServerTest.java new file mode 100644 index 00000000000..3ef3bb45e95 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/helidon/functional/FunctionalHelidonSeServerTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates + * + * 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.java.helidon.functional; + +import java.nio.file.Files; +import java.nio.file.Path; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +public class FunctionalHelidonSeServerTest extends FunctionalBase { + + @BeforeClass + public void setup() { + library("se"); + generatorName("java-helidon-server"); + } + + @Test + void buildPetstoreWithDefaultOptions() { + generate("src/test/resources/3_0/petstore.yaml"); + buildAndVerify("target/openapi-java-server.jar"); + } + + @Test + void buildPetstoreWithAbstractClasses() { + inputSpec("src/test/resources/3_0/petstore.yaml"); + generate(createConfigurator().addAdditionalProperty(FunctionalBase.USE_ABSTRACT_CLASS, "true")); + buildAndVerify("target/openapi-java-server.jar"); + } + + @Test + void verifyFullProject() { + inputSpec("src/test/resources/3_0/petstore.yaml"); + + // Generate project for first time and record pom's timestamp + generate(createConfigurator()); + buildAndVerify("target/openapi-java-server.jar"); + Path pom1 = outputPath.resolve("pom.xml"); + assertThat(Files.exists(pom1), is(true)); + long lastModified = pom1.toFile().lastModified(); + + // Re-generate project over same directory with fullProject unspecified + generate(createConfigurator(outputPath)); + Path pom2 = outputPath.resolve("pom.xml"); + assertThat(Files.exists(pom2), is(true)); + assertThat(pom2.toFile().lastModified(), is(lastModified)); // not overwritten + + // Re-generate project over same directory with fullProject false + generate(createConfigurator(outputPath).addAdditionalProperty(FULL_PROJECT, "false")); + Path pom3 = outputPath.resolve("pom.xml"); + assertThat(Files.exists(pom3), is(true)); + assertThat(pom3.toFile().lastModified(), is(lastModified)); // not overwritten + + // Re-generate project over same directory with fullProject true + generate(createConfigurator(outputPath).addAdditionalProperty(FULL_PROJECT, "true")); + Path pom4 = outputPath.resolve("pom.xml"); + assertThat(Files.exists(pom4), is(true)); + assertThat(pom4.toFile().lastModified(), is(not(lastModified))); // overwritten + } +} diff --git a/modules/openapi-generator/src/test/resources/3_0/helidon/petstore-for-testing.yaml b/modules/openapi-generator/src/test/resources/3_0/helidon/petstore-for-testing.yaml new file mode 100644 index 00000000000..7384758e953 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/helidon/petstore-for-testing.yaml @@ -0,0 +1,860 @@ +openapi: 3.0.0 +servers: + - url: 'http://petstore.helidon.io:8080/v2' +info: + description: >- + This spec is mainly for testing Petstore server and contains fake endpoints, + models. Please do not use this for any other purpose. For this sample, you can use the api key + `special-key` to test the authorization filters. Special characters: " + \ + version: 1.0.0 + title: OpenAPI Petstore + license: + name: Apache-2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0.html' +tags: + - name: pet + description: Everything about your Pets + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + $ref: '#/components/requestBodies/Pet' + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + $ref: '#/components/schemas/Pet' + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + style: form + explode: false + schema: + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: >- + Multiple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + style: form + explode: false + schema: + type: array + items: + type: integer + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - 'read:pets' + deprecated: true + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + required: false + schema: + type: string + - name: headerLong + in: header + required: true + schema: + type: integer + format: int64 + - name: cookieString + in: cookie + schema: + type: string + required: true + - name: cookieInt + in: cookie + schema: + type: integer + format: int32 + required: false + - name: cookieIntArray + in: cookie + schema: + type: array + items: + type: integer + required: false + - name: cookieStringArray + in: cookie + schema: + type: array + items: + type: string + required: false + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet/{petId}/uploadImage': + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + minimum: 1 + maximum: 5 + - name: petDate + in: path + description: Date for test + required: true + schema: + type: integer + format: int64 + minimum: 1 + maximum: 5 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + additionalMetadata: + description: Additional data to pass to server + type: string + file: + description: file to upload + type: string + format: binary + /pet/upload: + post: + tags: + - pet + summary: Add a few pets using form + description: '' + operationId: addPets + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/PetsForm' + description: Object that that contains info about pets + required: true + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid Order + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + description: order placed for purchasing the pet + required: true + '/store/order/{orderId}': + get: + tags: + - store + summary: Find purchase order by ID + description: >- + For valid response try integer IDs with value <= 5 or > 10. Other values + will generated exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + type: number + minimum: 1 + maximum: 5 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: >- + For valid response try integer IDs with value < 1000. Anything above + 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Created user object + required: true + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + schema: + type: string + pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' + - name: password + in: query + description: The password for login in clear text + required: true + schema: + type: string + - name: sizeString + in: query + description: String param to test Size constraint + required: true + schema: + type: string + minLength: 5 + maxLength: 10 + - name: minLong + in: query + description: Min Long param for test + required: true + schema: + type: integer + format: int64 + minimum: 10 + - name: minDecimal + in: query + description: Min Decimal param for test + required: true + schema: + type: number + minimum: 0 + exclusiveMinimum: true + responses: + '200': + description: successful operation + headers: + Set-Cookie: + description: >- + Cookie authentication key for use with the `api_key` + apiKey authentication. + schema: + type: string + example: AUTH_KEY=abcde12345; Path=/; HttpOnly + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + responses: + default: + description: successful operation + security: + - api_key: [] + '/user/{username}': + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing. + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + security: + - api_key: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Updated user object + required: true + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found + security: + - api_key: [] +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' +components: + requestBodies: + UserArray: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + description: List of user object + required: true + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + required: true + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog' + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + type: apiKey + name: api_key + in: header + schemas: + AnyValue: {} + Color: + type: string + enum: + - black + - white + - red + - green + - blue + Order: + title: Pet Order + description: An order for a pets from the pet store + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + Category: + title: Pet category + description: A category for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' + xml: + name: Category + User: + title: a User + description: A User who is purchasing from the pet store + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Tag: + title: Pet Tag + description: A tag for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + title: a Pet + description: A pet for sale in the pet store + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + ApiResponse: + title: An uploaded response + description: Describes the result of uploading an image resource + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + PetsForm: + type: object + properties: + images[]: + type: array + items: + type: string + format: binary + image: + type: string + format: binary + titles[]: + type: array + items: + type: string + longArray: + type: array + items: + type: integer + format: int64 + stringParam: + type: string + intParam: + type: integer + format: int32 + required: + - stringParam \ No newline at end of file diff --git a/modules/openapi-generator/src/test/resources/3_0/helidon/petstore-no-multipart-for-testing.yaml b/modules/openapi-generator/src/test/resources/3_0/helidon/petstore-no-multipart-for-testing.yaml new file mode 100644 index 00000000000..bcf99067930 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/helidon/petstore-no-multipart-for-testing.yaml @@ -0,0 +1,789 @@ +openapi: 3.0.0 +servers: + - url: 'http://petstore.helidon.io:8080/v2' +info: + description: >- + This spec is mainly for testing Petstore server and contains fake endpoints, + models. Please do not use this for any other purpose. For this sample, you can use the api key + `special-key` to test the authorization filters. Special characters: " + \ + version: 1.0.0 + title: OpenAPI Petstore + license: + name: Apache-2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0.html' +tags: + - name: pet + description: Everything about your Pets + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + $ref: '#/components/requestBodies/Pet' + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + $ref: '#/components/schemas/Pet' + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + style: form + explode: false + schema: + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: >- + Multiple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + style: form + explode: false + schema: + type: array + items: + type: integer + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - 'read:pets' + deprecated: true + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + required: false + schema: + type: string + - name: headerLong + in: header + required: true + schema: + type: integer + format: int64 + - name: cookieString + in: cookie + schema: + type: string + required: true + - name: cookieInt + in: cookie + schema: + type: integer + format: int32 + required: false + - name: cookieIntArray + in: cookie + schema: + type: array + items: + type: integer + required: false + - name: cookieStringArray + in: cookie + schema: + type: array + items: + type: string + required: false + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid Order + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + description: order placed for purchasing the pet + required: true + '/store/order/{orderId}': + get: + tags: + - store + summary: Find purchase order by ID + description: >- + For valid response try integer IDs with value <= 5 or > 10. Other values + will generated exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + type: number + minimum: 1 + maximum: 5 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: >- + For valid response try integer IDs with value < 1000. Anything above + 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Created user object + required: true + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + schema: + type: string + pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' + - name: password + in: query + description: The password for login in clear text + required: true + schema: + type: string + - name: sizeString + in: query + description: String param to test Size constraint + required: true + schema: + type: string + minLength: 5 + maxLength: 10 + - name: minLong + in: query + description: Min Long param for test + required: true + schema: + type: integer + format: int64 + minimum: 10 + - name: minDecimal + in: query + description: Min Decimal param for test + required: true + schema: + type: number + minimum: 0 + exclusiveMinimum: true + responses: + '200': + description: successful operation + headers: + Set-Cookie: + description: >- + Cookie authentication key for use with the `api_key` + apiKey authentication. + schema: + type: string + example: AUTH_KEY=abcde12345; Path=/; HttpOnly + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + responses: + default: + description: successful operation + security: + - api_key: [] + '/user/{username}': + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing. + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + security: + - api_key: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Updated user object + required: true + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found + security: + - api_key: [] +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' +components: + requestBodies: + UserArray: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + description: List of user object + required: true + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + required: true + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog' + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + type: apiKey + name: api_key + in: header + schemas: + AnyValue: {} + Color: + type: string + enum: + - black + - white + - red + - green + - blue + Order: + title: Pet Order + description: An order for a pets from the pet store + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + Category: + title: Pet category + description: A category for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' + xml: + name: Category + User: + title: a User + description: A User who is purchasing from the pet store + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Tag: + title: Pet Tag + description: A tag for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + title: a Pet + description: A pet for sale in the pet store + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + ApiResponse: + title: An uploaded response + description: Describes the result of uploading an image resource + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + PetsForm: + type: object + properties: + images[]: + type: array + items: + type: string + format: binary + image: + type: string + format: binary + titles[]: + type: array + items: + type: string + longArray: + type: array + items: + type: integer + format: int64 + stringParam: + type: string + intParam: + type: integer + format: int32 + required: + - stringParam \ No newline at end of file diff --git a/pom.xml b/pom.xml index 54b36bd4bce..7ca26d656a7 100644 --- a/pom.xml +++ b/pom.xml @@ -744,6 +744,30 @@ samples/server/petstore/java-micronaut-server + + java-helidon-client + + + env + java + + + + samples/client/petstore/java-helidon-client + + + + java-helidon-server + + + env + java + + + + samples/server/petstore/java-helidon-server + + java-msf4j-server