Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introducing OpenAPI Generator #3

Merged
merged 9 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 91 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,108 @@
# Quarkus - Openapi Generator
# Quarkus - OpenAPI Generator

<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->

## Welcome to Quarkiverse!
Quarkus' extension for generation of [Rest Clients](https://quarkus.io/guides/rest-client) based on OpenAPI specification files.

## Getting Started

Add the following dependency to your project's `pom.xml` file:

```xml

<dependency>
<groupId>io.quarkiverse.openapi.generator</groupId>
<artifactId>quarkus-openapi-generator</artifactId>
<version>0.1.0</version>
</dependency>
```

You will also need to add additional execution entries to the `quarkus-maven-plugin` configuration:

```xml

<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
```

Now, create the directory `openapi` under your `src/main/` path and add the OpenAPI spec files there. We support JSON, YAML and YML extensions.

To fine tune the configuration for each spec file, add the following entry to your properties file. In this example, our spec file is in `src/main/openapi/petstore.json`:

```properties
quarkus.openapi-generator.spec."petstore.json".base-package=org.acme.openapi
```

Note that the file name is used to configure the specific information for each spec.

Run `mvn compile` to generate your classes in `target/generated-sources/open-api-json` path:

```
- org.acme.openapi
- api
- PetApi.java
- StoreApi.java
- UserApi.java
- model
- Address.java
- Category.java
- Customer.java
- ModelApiResponse.java
- Order.java
- Pet.java
- Tag.java
- User.java
```

You can reference the generated code in your project, for example:

Congratulations and thank you for creating a new Quarkus extension project in Quarkiverse!
```java
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

Feel free to replace this content with the proper description of your new project and necessary instructions how to use and contribute to it.
import org.acme.openapi.api.PetApi;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;

You can find the basic info, Quarkiverse policies and conventions in [the Quarkiverse wiki](https://github.com/quarkiverse/quarkiverse/wiki).
@Produces(MediaType.APPLICATION_JSON)
@Path("/petstore")
public class PetResource {

In case you are creating a Quarkus extension project for the first time, please follow [Building My First Extension](https://quarkus.io/guides/building-my-first-extension) guide.
@RestClient
@Inject
PetApi petApi;
}
```

Other useful articles related to Quarkus extension development can be found under the [Writing Extensions](https://quarkus.io/guides/#writing-extensions) guide category on the [Quarkus.io](http://quarkus.io) website.
See the [integration-tests](integration-tests) module for more information of how to use this extension. Please be advised that the extension is on experimental, early development stage.

Thanks again, good luck and have fun!
## Known Limitations

## Documentation
These are the known limitations of this pre-release version:

The documentation for this extension should be maintained as part of this repository and it is stored in the `docs/` directory.
- No authentication support (Basic, Bearer, OAuth2)
- No reactive support
- Only Jackson support

The layout should follow the [Antora's Standard File and Directory Set](https://docs.antora.org/antora/2.3/standard-directories/).
We will work in the next few releases to address these use cases, until there please provide feedback for the current state of this extension. We also love contributions :heart:

Once the docs are ready to be published, please open a PR including this repository in the [Quarkiverse Docs Antora playbook](https://github.com/quarkiverse/quarkiverse-docs/blob/main/antora-playbook.yml#L7). See an example [here](https://github.com/quarkiverse/quarkiverse-docs/pull/1).
## Contributors ✨

Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
Expand Down
83 changes: 81 additions & 2 deletions deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.quarkiverse.openapi.generator</groupId>
Expand All @@ -9,16 +9,95 @@
</parent>
<artifactId>quarkus-openapi-generator-deployment</artifactId>
<name>Quarkus - Openapi Generator - Deployment</name>

<properties>
<version.org.openapitools>5.4.0</version.org.openapitools>
<version.org.slf4j>1.7.29</version.org.slf4j>
<version.com.github.jknack>4.2.1</version.com.github.jknack>
</properties>

<dependencies>
<!-- Quarkus -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-jackson-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devtools-utilities</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-qute-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.openapi.generator</groupId>
<artifactId>quarkus-openapi-generator</artifactId>
<version>${project.version}</version>
</dependency>

<!-- OpenApi Generator -->
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator</artifactId>
<version>${version.org.openapitools}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
<exclusion>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</exclusion>
<exclusion>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</exclusion>
<exclusion>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-ext</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Used internally by openapi-generator-tool, we can't use JBoss Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-ext</artifactId>
<version>${version.org.slf4j}</version>
</dependency>
gastaldi marked this conversation as resolved.
Show resolved Hide resolved
<!-- the version imported from wiremock causing conflicts -->
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars-jackson2</artifactId>
<version>${version.com.github.jknack}</version>
</dependency>

<!-- Tests -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkiverse.openapi.generator.deployment;

import org.jboss.jandex.ClassInfo;

import io.quarkus.builder.item.MultiBuildItem;

public abstract class GeneratedOpenApiFile extends MultiBuildItem {
private final ClassInfo classInfo;

public GeneratedOpenApiFile(final ClassInfo classInfo) {
this.classInfo = classInfo;
}

public ClassInfo getClassInfo() {
return classInfo;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkiverse.openapi.generator.deployment;

import org.jboss.jandex.ClassInfo;

import io.quarkus.builder.item.MultiBuildItem;

/**
* {@link MultiBuildItem} produced for each domain Model generated by the OpenApi Generator tool containing the generated class
* information.
*/
public final class GeneratedOpenApiModelBuildItem extends GeneratedOpenApiFile {

public GeneratedOpenApiModelBuildItem(final ClassInfo classInfo) {
super(classInfo);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkiverse.openapi.generator.deployment;

import org.jboss.jandex.ClassInfo;

import io.quarkus.builder.item.MultiBuildItem;

/**
* {@link MultiBuildItem} produced for each Rest Client generated by the OpenApi Generator tool containing the generated class
* information.
*/
public final class GeneratedOpenApiRestClientBuildItem extends GeneratedOpenApiFile {

public GeneratedOpenApiRestClientBuildItem(final ClassInfo classInfo) {
super(classInfo);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkiverse.openapi.generator.deployment;

import java.util.Map;

import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;

@ConfigRoot(phase = ConfigPhase.BUILD_TIME, name = "openapi-generator")
public class OpenApiGeneratorConfiguration {
static final String CONFIG_PREFIX = "quarkus.openapi-generator";

/**
* Fine tune the configuration for each OpenAPI spec file in `src/openapi` directory.
* <p>
* The file name is used to index this configuration. For example:
* `quarkus.openapi-generator.spec."myfilespec.json".base-package=org.acme`.
**/
ricardozanini marked this conversation as resolved.
Show resolved Hide resolved
@ConfigItem(name = "spec")
public Map<String, SpecConfig> specs;

/**
* Increases the internal generator log output verbosity
*/
@ConfigItem(defaultValue = "false")
public String verbose;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.quarkiverse.openapi.generator.deployment;

import org.jboss.jandex.ClassInfo;

import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;

class OpenApiGeneratorProcessor {

private static final String FEATURE = "openapi-generator";

OpenApiGeneratorConfiguration configuration;

@BuildStep
void discoverGeneratedApis(BuildProducer<GeneratedOpenApiRestClientBuildItem> restClients,
BuildProducer<GeneratedOpenApiModelBuildItem> models,
BuildProducer<FeatureBuildItem> features,
CombinedIndexBuildItem index) {

boolean hasGeneratedFiles = false;
for (SpecConfig spec : configuration.specs.values()) {
for (ClassInfo classInfo : index.getIndex().getKnownClasses()) {
if (classInfo.name().packagePrefix().equals(spec.getApiPackage())) {
restClients.produce(new GeneratedOpenApiRestClientBuildItem(classInfo));
hasGeneratedFiles = true;
} else if (classInfo.name().packagePrefix().equals(spec.getModelPackage())) {
models.produce(new GeneratedOpenApiModelBuildItem(classInfo));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe I'm misreading it, but wouldn't it make slightly more sense to have only one package name defined and created a name for the other by appending .model, or just use one package for both types of generated things?
It would simplify the config for users

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to mimic the exact configuration of the underlying library, but I agree that having only one property would simplify things for users. Let's do it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, are the build items produced here used?

Copy link
Member Author

@ricardozanini ricardozanini Feb 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet, but they will soon. I was experimenting with some things and decided to let them here for now.

hasGeneratedFiles = true;
}
}
}

if (hasGeneratedFiles) {
features.produce(new FeatureBuildItem(FEATURE));
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.quarkiverse.openapi.generator.deployment;

import static io.quarkiverse.openapi.generator.deployment.OpenApiGeneratorConfiguration.CONFIG_PREFIX;

import java.nio.file.Paths;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@ConfigGroup
public class SpecConfig {
public static final String API_PKG_SUFFIX = ".api";
public static final String MODEL_PKG_SUFFIX = ".model";

/**
* Defines the base package name for the generated classes.
*/
@ConfigItem
public String basePackage;

public String getApiPackage() {
return String.format("%s%s", basePackage, API_PKG_SUFFIX);
}

public String getModelPackage() {
return String.format("%s%s", basePackage, MODEL_PKG_SUFFIX);
}

public static String getResolvedBasePackageProperty(final String openApiFilePath) {
final String fileName = Paths.get(openApiFilePath).getFileName().toString();
return CONFIG_PREFIX + ".spec.\"" + fileName + "\"" + ".base-package";
}
}
Loading