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

New module: openapi-adoc #1233

Merged
merged 13 commits into from
Oct 25, 2023
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
4 changes: 2 additions & 2 deletions MAINTAINING.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ The consequence of having both approaches in place is that we get multiple depen
`micronaut-build` via our automation, and one or many (one per dependency) created by Renovate. When merging those, it
is better to prefer the `micronaut-build` ones, if possible, for 2 reasons: a) they attempt to upgrade multiple dependencies
in a single PR, which creates less noise in the Git history; b) Once you merge that, Renovate will react and automatically
close its own PRs if the dependecy is up-to-date.
close its own PRs if the dependency is up-to-date.

When an upgrade to a new version arrives, we need to be careful when merging, so that we don't introduce an
unnecessary upgrade burden on our users. Read the
Expand Down Expand Up @@ -162,7 +162,7 @@ First of all, all the repos have an automatic changelog generation mechanism: wh
release notes will contain pull requests merged and issues closed since the last release.

When the module is ready for a new release, check the generated release notes, and make changes if needed (for example,
you can add an introduction paragraph highligting some items included in the release). If the version you are going to
you can add an introduction paragraph highlighting some items included in the release). If the version you are going to
publish is not a new patch version, but a new minor or major, update the release notes text to reflect the new version.
If you are publishing a milestone or release candidate, check the pre-release checkbox.

Expand Down
1 change: 1 addition & 0 deletions config/checkstyle/custom-suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
<suppress checks="." files="io[\\/]micronaut[\\/]openapi[\\/]swagger[\\/]core[\\/]" />
<suppress checks="FileLength" files=".*" />
<suppress checks="ParameterNumber" files=".*" />
<suppress checks="." files="io[\\/]micronaut[\\/]openapi[\\/]adoc[\\/]md" />
</suppressions>
6 changes: 6 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ managed-swagger = "2.2.17"
managed-javadoc-parser = "0.3.1"
managed-jsoup = "1.16.1"
managed-html2md-converter = "0.64.8"
managed-parboiled = "1.4.1"
managed-freemarker = "2.3.32"
managed-pegdown = "1.6.0"

kotlin = "1.9.10"
jspecify = "0.3.0"
Expand Down Expand Up @@ -52,6 +55,9 @@ managed-swagger-models = { module = "io.swagger.core.v3:swagger-models", version
managed-javadoc-parser = { module = "com.github.chhorz:javadoc-parser", version.ref = "managed-javadoc-parser" }
managed-html2md-converter = { module = "com.vladsch.flexmark:flexmark-html2md-converter", version.ref = "managed-html2md-converter" }
managed-jsoup = { module = "org.jsoup:jsoup", version.ref = "managed-jsoup" }
managed-parboiled = { module = "org.parboiled:parboiled-java", version.ref = "managed-parboiled" }
managed-freemarker = { module = "org.freemarker:freemarker", version.ref = "managed-freemarker" }
managed-pegdown = {module = "org.pegdown:pegdown", version.ref = "managed-pegdown"}

# Versions from BOM
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine" }
Expand Down
27 changes: 27 additions & 0 deletions openapi-adoc/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
plugins {
id 'io.micronaut.build.internal.openapi-simple-module'
}

micronautBuild {
binaryCompatibility {
enabled = false
}
}

dependencies {

api libs.managed.pegdown
api libs.managed.parboiled
api libs.managed.jsoup
api libs.managed.freemarker

implementation projects.micronautOpenapiCommon

testImplementation mnTest.micronaut.test.junit5

testRuntimeOnly libs.junit.jupiter.engine
}

test {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2017-2023 original authors
*
* 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 io.micronaut.openapi.adoc;

/**
* Configuration properties for Openapi-to-adoc converter.
*
* @since 5.2.0
*/
public interface OpenApiToAdocConfigProperty {

/**
* Prefix for custom sub-template names.
*/
String MICRONAUT_OPENAPI_ADOC_TEMPLATE_PREFIX = "micronaut.openapi.adoc.templates.";
/**
* Is conversion to Asciidoc enabled.
*/
String MICRONAUT_OPENAPI_ADOC_ENABLED = "micronaut.openapi.adoc.enabled";
/**
* Custom template directory.
*/
String MICRONAUT_OPENAPI_ADOC_TEMPLATES_DIR_PATH = "micronaut.openapi.adoc.template.dir";
/**
* Custom final template filename.
*/
String MICRONAUT_OPENAPI_ADOC_TEMPLATE_FILENAME = "micronaut.openapi.adoc.template.filename";
/**
* Result adoc file output directory.
*/
String MICRONAUT_OPENAPI_ADOC_OUTPUT_DIR_PATH = "micronaut.openapi.adoc.output.dir";
/**
* Result adoc filename.
*/
String MICRONAUT_OPENAPI_ADOC_OUTPUT_FILENAME = "micronaut.openapi.adoc.output.filename";
/**
* OpenAPI file path.
*/
String MICRONAUT_OPENAPI_ADOC_OPENAPI_PATH = "micronaut.openapi.adoc.openapi.path";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/*
* Copyright 2017-2023 original authors
*
* 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 io.micronaut.openapi.adoc;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import io.micronaut.openapi.OpenApiUtils;
import io.micronaut.openapi.adoc.md.MdToAdocConverter;
import io.micronaut.openapi.adoc.utils.SwaggerUtils;
import io.swagger.v3.oas.models.OpenAPI;

import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModelException;

import static io.micronaut.openapi.adoc.TemplatePaths.CONTENT;
import static io.micronaut.openapi.adoc.TemplatePaths.DEFINITIONS;
import static io.micronaut.openapi.adoc.TemplatePaths.EXAMPLES;
import static io.micronaut.openapi.adoc.TemplatePaths.EXTERNAL_DOCS;
import static io.micronaut.openapi.adoc.TemplatePaths.HEADERS;
import static io.micronaut.openapi.adoc.TemplatePaths.LINKS;
import static io.micronaut.openapi.adoc.TemplatePaths.OVERVIEW;
import static io.micronaut.openapi.adoc.TemplatePaths.PARAMETERS;
import static io.micronaut.openapi.adoc.TemplatePaths.PATHS;
import static io.micronaut.openapi.adoc.TemplatePaths.PROPERTIES;
import static io.micronaut.openapi.adoc.TemplatePaths.PROPERTY_DESCRIPTION;
import static io.micronaut.openapi.adoc.TemplatePaths.REQUEST_BODY;
import static io.micronaut.openapi.adoc.TemplatePaths.RESPONSES;
import static io.micronaut.openapi.adoc.TemplatePaths.SCHEMA_TYPE;
import static io.micronaut.openapi.adoc.TemplatePaths.SECURITY_REQUIREMENTS;
import static io.micronaut.openapi.adoc.TemplatePaths.SERVERS;
import static io.micronaut.openapi.adoc.utils.FileUtils.CLASSPATH_SCHEME;
import static io.micronaut.openapi.adoc.utils.FileUtils.FILE_SCHEME;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
* OpenAPI to Asciidoc converter.
*
* @since 4.2.0
*/
public final class OpenApiToAdocConverter {

private static final String TEMPLATE_PREFIX = "template_";
private static final String TEMPLATES_DIR = "/template";

private OpenApiToAdocConverter() {
}

/**
* Conversion from openAPI format to asciidoc format.
*
* @throws TemplateException som problems with freemarker templates
* @throws IOException some problems with files
*/
public static void convert() throws TemplateException, IOException {
var openApiFile = System.getProperty(OpenApiToAdocConfigProperty.MICRONAUT_OPENAPI_ADOC_OPENAPI_PATH);
if (openApiFile == null || openApiFile.isBlank()) {
throw new IllegalArgumentException("OpenAPI file path not set");
}
var openApi = SwaggerUtils.readOpenApiFromLocation(openApiFile);

var outputPath = Paths.get(System.getProperty(OpenApiToAdocConfigProperty.MICRONAUT_OPENAPI_ADOC_OUTPUT_DIR_PATH, "build/generated"))
.resolve(System.getProperty(OpenApiToAdocConfigProperty.MICRONAUT_OPENAPI_ADOC_OUTPUT_FILENAME, "openApiDoc.adoc"));

if (outputPath.getParent() != null) {
try {
Files.createDirectories(outputPath.getParent());
} catch (IOException e) {
throw new RuntimeException("Failed create directory", e);
}
}

var fileExists = Files.exists(outputPath);
try (var writer = fileExists ? Files.newBufferedWriter(outputPath, UTF_8, StandardOpenOption.APPEND) : Files.newBufferedWriter(outputPath, UTF_8)) {
convert(openApi, System.getProperties(), writer);
}
}

/**
* Conversion from openAPI format to asciidoc format.
*
* @param openApi openAPI object
* @param props converter config properties
* @param writer writer for rendered template
*
* @throws TemplateException som problems with freemarker templates
* @throws IOException some problems with files
*/
public static void convert(OpenAPI openApi, Map props, Writer writer) throws TemplateException, IOException {

MdToAdocConverter.convert(openApi);

var model = new HashMap<String, Object>();
model.put("info", openApi.getInfo());
model.put("externalDocs", openApi.getExternalDocs());
model.put("servers", openApi.getServers());
model.put("tags", openApi.getTags());
model.put("openApi", openApi.getOpenapi());
model.put("globalSecurityRequirements", openApi.getSecurity());
model.put("paths", openApi.getPaths());
model.put("components", openApi.getComponents());

model.put(template(DEFINITIONS), "definitions.ftl");
model.put(template(OVERVIEW), "overview.ftl");
model.put(template(PATHS), "paths.ftl");
model.put(template(CONTENT), "content.ftl");
model.put(template(EXAMPLES), "examples.ftl");
model.put(template(EXTERNAL_DOCS), "externalDocs.ftl");
model.put(template(HEADERS), "headers.ftl");
model.put(template(LINKS), "links.ftl");
model.put(template(PARAMETERS), "parameters.ftl");
model.put(template(PROPERTIES), "properties.ftl");
model.put(template(PROPERTY_DESCRIPTION), "propertyDescription.ftl");
model.put(template(REQUEST_BODY), "requestBody.ftl");
model.put(template(RESPONSES), "responses.ftl");
model.put(template(SCHEMA_TYPE), "schemaType.ftl");
model.put(template(SECURITY_REQUIREMENTS), "securityRequirements.ftl");
model.put(template(SERVERS), "servers.ftl");

for (var entry : System.getProperties().entrySet()) {
var key = entry.getKey().toString();
if (key.startsWith(OpenApiToAdocConfigProperty.MICRONAUT_OPENAPI_ADOC_TEMPLATE_PREFIX)) {
model.put(key.replace(OpenApiToAdocConfigProperty.MICRONAUT_OPENAPI_ADOC_TEMPLATE_PREFIX, TEMPLATE_PREFIX), entry.getValue());
}
}

var templateFilename = System.getProperty(OpenApiToAdocConfigProperty.MICRONAUT_OPENAPI_ADOC_TEMPLATE_FILENAME, "openApiDoc.ftl");
var customTemplatesDirsStr = System.getProperty(OpenApiToAdocConfigProperty.MICRONAUT_OPENAPI_ADOC_TEMPLATES_DIR_PATH);
String[] customTemplatesDirs = null;
if (customTemplatesDirsStr != null && !customTemplatesDirsStr.isBlank()) {
customTemplatesDirs = customTemplatesDirsStr.split(",");
}

var cfg = getFreemarkerConfig(customTemplatesDirs);
var template = cfg.getTemplate(templateFilename);

template.process(model, writer);
}

private static Configuration getFreemarkerConfig(String[] customTemplatesDirs) throws IOException, TemplateModelException {
TemplateLoader templateLoader = new ClassTemplateLoader(OpenApiToAdocConverter.class, TEMPLATES_DIR);
if (customTemplatesDirs != null && customTemplatesDirs.length > 0) {
var templateLoaders = new ArrayList<TemplateLoader>();
for (var templateDir : customTemplatesDirs) {
templateDir = templateDir.strip()
.replace("\\", "/");
if (templateDir.startsWith(CLASSPATH_SCHEME)) {
templateLoaders.add(new ClassTemplateLoader(OpenApiToAdocConverter.class, templateDir.substring(CLASSPATH_SCHEME.length())));
} else {
if (templateDir.startsWith(FILE_SCHEME)) {
templateDir = templateDir.substring(FILE_SCHEME.length());
if (templateDir.startsWith("//")) {
templateDir = templateDir.substring(2);
}
}
templateLoaders.add(new FileTemplateLoader(new File(templateDir)));
}
}
templateLoaders.add(templateLoader);
templateLoader = new MultiTemplateLoader(templateLoaders.toArray(new TemplateLoader[0]));
}

var cfg = new Configuration(Configuration.VERSION_2_3_32);
cfg.setTemplateLoader(templateLoader);
cfg.setDefaultEncoding(UTF_8.displayName());
cfg.setSharedVariable("JSON", OpenApiUtils.getJsonMapper());
return cfg;
}

private static String template(String templateName) {
return TEMPLATE_PREFIX + templateName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2017-2023 original authors
*
* 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 io.micronaut.openapi.adoc;

/**
* Freemarker component template names.
*
* @since 5.2.0
*/
public interface TemplatePaths {

String DEFINITIONS = "definitions";
String OVERVIEW = "overview";
String PATHS = "paths";

String CONTENT = "content";
String EXAMPLES = "examples";
String EXTERNAL_DOCS = "externalDocs";
String HEADERS = "headers";
String LINKS = "links";
String PARAMETERS = "parameters";
String PROPERTIES = "properties";
String PROPERTY_DESCRIPTION = "propertyDescription";
String REQUEST_BODY = "requestBody";
String RESPONSES = "responses";
String SCHEMA_TYPE = "schemaType";
String SECURITY_REQUIREMENTS = "securityRequirements";
String SERVERS = "servers";
}
Loading
Loading