Skip to content

Commit

Permalink
#693 - Allow YAML for Server Generation (#701)
Browse files Browse the repository at this point in the history
* Fixes #693, update documentation and tests

* Reformated

* Update documentaiton

* Removed integration-tests again
  • Loading branch information
lizzyTheLizard authored Apr 28, 2024
1 parent 6305876 commit e710dd6
Show file tree
Hide file tree
Showing 10 changed files with 937 additions and 43 deletions.
15 changes: 11 additions & 4 deletions docs/modules/ROOT/pages/includes/server-getting-started.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,24 @@ WARNING: You probably already have this configuration if you created your applic
</plugin>
----

Now, create the directory `openapi` under your `src/main/resources` path and add the OpenAPI spec files there. We support JSON, YAML and YML extensions.
Now, create the directory `openapi` under your `src/main/resources` path and add the OpenAPI spec files there. We support JSON, YAML and YML extensions. You have to define the specification used for code generation with the following property:

If a base package name is not provided, it will be used the default `io.apicurio.api`.
[source,properties]
----
quarkus.openapi.generator.spec=petstore-openapi.json
----

You can customize the specification used for code generation with the following property:

If you want to change the directory where OpenAPI files must be found, use the property `quarkus.openapi.input-base-dir`.
IMPORTANT: it is relative to the project base directory. For example, if you have a project called `MyJavaProject` and decide to place them in `MyJavaProject/openapi-definitions`, use the following property:

[source,properties]
----
quarkus.openapi.generator.spec=petstore-openapi.json
quarkus.openapi.input-base-dir=openapi-definitions
----

If a base package name is not provided, it will be used the default `io.apicurio.api`. You can customize it with the following property:

[source,properties]
----
quarkus.openapi.generator.base-package=io.petstore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class CodegenConfig {
static final String CODEGEN_TIME_CONFIG_PREFIX = "quarkus.openapi.generator";
private static final String CODEGEN_BASE_PACKAGE = CODEGEN_TIME_CONFIG_PREFIX + ".base-package";
private static final String CODEGEN_SPEC = CODEGEN_TIME_CONFIG_PREFIX + ".spec";
private static final String INPUT_BASE_DIR = CODEGEN_TIME_CONFIG_PREFIX + ".input-base-dir";

public static String getBasePackagePropertyName() {
return CODEGEN_BASE_PACKAGE;
Expand All @@ -17,4 +18,8 @@ public static String getBasePackagePropertyName() {
public static String getSpecPropertyName() {
return CODEGEN_SPEC;
}

public static String getInputBaseDirPropertyName() {
return INPUT_BASE_DIR;
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package io.quarkiverse.openapi.server.generator.deployment.codegen;

import java.io.IOException;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import java.util.Arrays;

import org.eclipse.microprofile.config.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

import io.quarkiverse.openapi.server.generator.deployment.CodegenConfig;
import io.quarkus.bootstrap.prebuild.CodeGenException;
import io.quarkus.deployment.CodeGenContext;
Expand All @@ -24,52 +27,82 @@ public String providerId() {
}

@Override
public String inputExtension() {
return "json";
public String[] inputExtensions() {
return new String[] { "json", "yaml", "yml" };
}

@Override
public String inputDirectory() {
return "resources";
}

private Path getInputBaseDir(final Path sourceDir, final Config config) {
return config.getOptionalValue(CodegenConfig.getInputBaseDirPropertyName(), String.class)
.map(inputBaseDir -> {
int srcIndex = sourceDir.toString().lastIndexOf("src");
return Path.of(sourceDir.toString().substring(0, srcIndex), inputBaseDir);
}).orElse(Path.of(sourceDir.toString(), "openapi"));
}

@Override
public boolean shouldRun(Path sourceDir, Config config) {
return sourceDir != null && config.getOptionalValue(CodegenConfig.getSpecPropertyName(), String.class)
.isPresent();
if (config.getOptionalValue(CodegenConfig.getSpecPropertyName(), String.class).isEmpty()) {
return false;
}
Path path = getInputBaseDir(sourceDir, config);
return Files.isDirectory(path);
}

@Override
public boolean trigger(CodeGenContext context) throws CodeGenException {
final Path openApiDir = context.inputDir();
final Path openApiDir = getInputBaseDir(context.inputDir(), context.config());
final Path outDir = context.outDir();
final ApicurioCodegenWrapper apicurioCodegenWrapper = new ApicurioCodegenWrapper(context.config(), outDir.toFile());

if (Files.isDirectory(openApiDir)) {

try (Stream<Path> openApiFilesPaths = Files.walk(openApiDir)) {
openApiFilesPaths
.filter(Files::isRegularFile)
.map(Path::toString)
.filter(s -> s.endsWith(this.inputExtension()))
.map(Path::of).forEach(openApiResource -> {
if (openApiResource.toFile().getName().equals(context.config()
.getOptionalValue(CodegenConfig.getSpecPropertyName(), String.class).get())) {
try {
apicurioCodegenWrapper.generate(openApiResource);
} catch (CodeGenException e) {
log.warn("Exception found processing specification with name: {}",
openApiResource.getFileName());
}
}
});
final ApicurioCodegenWrapper apicurioCodegenWrapper = new ApicurioCodegenWrapper(
context.config(), outDir.toFile());
final String specPropertyName = context.config()
.getOptionalValue(CodegenConfig.getSpecPropertyName(), String.class)
.orElseThrow();
final File openApiResource = new File(openApiDir.toFile(), specPropertyName);
if (!openApiResource.exists()) {
throw new CodeGenException(
"Specification file not found: " + openApiResource.getAbsolutePath());
}
if (!openApiResource.isFile()) {
throw new CodeGenException(
"Specification file is not a file: " + openApiResource.getAbsolutePath());
}
if (!openApiResource.canRead()) {
throw new CodeGenException(
"Specification file is not readable: " + openApiResource.getAbsolutePath());
}
if (Arrays.stream(this.inputExtensions()).noneMatch(specPropertyName::endsWith)) {
throw new CodeGenException(
"Specification file must have one of the following extensions: " + Arrays.toString(
this.inputExtensions()));
}
// Apicurio only supports JSON => convert yaml to JSON
final File jsonSpec = specPropertyName.endsWith("json") ? openApiResource
: convertToJSON(openApiResource.toPath());
try {
apicurioCodegenWrapper.generate(jsonSpec.toPath());
} catch (CodeGenException e) {
log.warn("Exception found processing specification with name: {}",
openApiResource.getAbsolutePath());
}
return true;
}

} catch (IOException e) {
throw new CodeGenException("Failed to generate java files from OpenApi file in " + openApiDir.toAbsolutePath(),
e);
}
return true;
private File convertToJSON(Path yamlPath) throws CodeGenException {
try {
ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
Object obj = yamlReader.readValue(yamlPath.toFile(), Object.class);
ObjectMapper jsonWriter = new ObjectMapper();
File jsonFile = File.createTempFile(yamlPath.toFile().getName(), ".json");
jsonFile.deleteOnExit();
jsonWriter.writeValue(jsonFile, obj);
return jsonFile;
} catch (Exception e) {
throw new CodeGenException("Error converting YAML to JSON", e);
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package io.quarkiverse.openapi.server.generator.deployment;

import java.net.URISyntaxException;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import org.apache.commons.io.FileUtils;
import org.eclipse.microprofile.config.Config;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import io.quarkiverse.openapi.server.generator.deployment.codegen.ApicurioOpenApiServerCodegen;
Expand All @@ -12,14 +18,46 @@

public class CodegenTest {

private final static Path WORK_DIR = Path.of("target/generated-test-sources");
private final static Path INPUT_DIR = Path.of("src/test/resources");
private final static String OUT_DIR = "target/generated-test-sources";

@BeforeAll
public static void setup() throws IOException {
FileUtils.deleteDirectory(new File("target/generated-test-sources"));
}

@Test
public void testGeneration() throws CodeGenException, URISyntaxException {
Config config = MockConfigUtils.getTestConfig("application.properties");
public void testJSON() throws CodeGenException {
Config config = MockConfigUtils.getTestConfig("json.application.properties");
CodeGenContext codeGenContext = new CodeGenContext(null, Path.of(OUT_DIR, "json"), WORK_DIR,
INPUT_DIR, false, config, true);
ApicurioOpenApiServerCodegen apicurioOpenApiServerCodegen = new ApicurioOpenApiServerCodegen();
apicurioOpenApiServerCodegen.trigger(codeGenContext);
assertTrue(
Files.exists(Path.of("target/generated-test-sources/json/io/petstore/PetResource.java")));
}

CodeGenContext codeGenContext = new CodeGenContext(null, Path.of("target/generated-test-sources"),
Path.of("target/generated-test-sources"), Path.of("generated-test-classes"), false, config, true);
@Test
public void testYaml() throws CodeGenException {
Config config = MockConfigUtils.getTestConfig("yaml.application.properties");
CodeGenContext codeGenContext = new CodeGenContext(null, Path.of(OUT_DIR, "yaml"), WORK_DIR,
INPUT_DIR, false, config, true);
ApicurioOpenApiServerCodegen apicurioOpenApiServerCodegen = new ApicurioOpenApiServerCodegen();
apicurioOpenApiServerCodegen.trigger(codeGenContext);
assertTrue(
Files.exists(Path.of("target/generated-test-sources/yaml/io/petstore/PetResource.java")));
}

@Test
public void testInputDir() throws CodeGenException {
Config config = MockConfigUtils.getTestConfig("inputDir.application.properties");
CodeGenContext codeGenContext = new CodeGenContext(null, Path.of(OUT_DIR, "inputDir"), WORK_DIR,
INPUT_DIR, false, config, true);
ApicurioOpenApiServerCodegen apicurioOpenApiServerCodegen = new ApicurioOpenApiServerCodegen();
apicurioOpenApiServerCodegen.trigger(codeGenContext);
assertTrue(Files.exists(
Path.of("target/generated-test-sources/inputDir/io/petstore/PetResource.java")));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
quarkus.openapi.generator.spec=petstore-openapi-2.json
quarkus.openapi.generator.input-base-dir=src/test/resources2
quarkus.openapi.generator.base-package=io.petstore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
quarkus.openapi.generator.spec=petstore-openapi.yaml
quarkus.openapi.generator.base-package=io.petstore
Loading

0 comments on commit e710dd6

Please sign in to comment.