Skip to content
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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ The project is very much Work In Progress and will be published on maven central
# Release Notes
BOAT is still under development and subject to change.

## 0.14.4

* *Boat Marina*
* Added a now BOAT Scaffold template called Marina, as that is where the models hang out. The Boat Marina template outputs a single JSON file that is used to offer a rich user interface built from the specs including search, page per operation and many more features!

* *Maven Plugin*
* The boat:doc goal now recursively search OpenAPI specs in a directory to generate docs for each found spec.

## 0.14.3

* *Maven Plugin*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import java.nio.charset.StandardCharsets;

@Log
@SuppressWarnings({"java:S3740", "rawtypes"})
@SuppressWarnings({"java:S3740", "rawtypes", "java:S2755"})
public class XmlSchemaToOpenApi {
public static final String NAME = "name";
public static final String TYPE = "type";
Expand Down
2 changes: 1 addition & 1 deletion boat-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@
<localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
<settingsFile>src/it/settings.xml</settingsFile>
<extraArtifacts>
<extraArtifact>org.jacoco:org.jacoco.agent:0.8.4:jar:runtime</extraArtifact>
<extraArtifact>org.jacoco:org.jacoco.agent:${jacoco-maven-plugin.version}:jar:runtime</extraArtifact>
</extraArtifacts>
</configuration>
<executions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@

@Mojo(name = "doc", threadSafe = true)
@Slf4j
public class GenerateDocMojo extends GenerateFromDirectoryDocMojo {
public class GenerateDocMojo extends GenerateFromDirectoryDocMojo {

@Override
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info("Generating Boat Docs");
generatorName = "boat-docs";
if (generatorName == null) {
generatorName = "boat-docs";
}
super.execute();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.util.DirectoryScanner;

import java.io.File;
import java.util.Arrays;
import java.util.stream.Collectors;

/**
* Allows generate::Doc to accept inputSpec as a directory
Expand All @@ -13,6 +17,9 @@
@Slf4j
public class GenerateFromDirectoryDocMojo extends GenerateMojo {

@Parameter(defaultValue = "**/*-api-*.yaml")
protected String openApiFileFilters;


@Override
public void execute() throws MojoExecutionException, MojoFailureException {
Expand All @@ -33,22 +40,26 @@ private void fileInputExecute(File inputSpecFile) throws MojoExecutionException,
File[] inputSpecs;
File outPutDirectory = output;

inputSpecs = inputSpecFile.listFiles(pathname -> pathname.getName().endsWith(".yaml"));
inputSpecs = findAllOpenApiSpecs(inputSpecFile);

if (inputSpecs == null || inputSpecs.length == 0) {
if (inputSpecs.length == 0) {
throw new MojoExecutionException("No OpenAPI specs found in: " + inputSpec);
}

for (File f : inputSpecs) {
inputSpec = f.getPath();
output = new File(outPutDirectory.getPath(), f.getName().substring(0, f.getName().lastIndexOf(".")).concat("-docs"));
output = new File(outPutDirectory.getPath(), f.getName().substring(0, f.getName().lastIndexOf(".")));

if (!output.exists()) {
output.mkdir();
}

log.info(" Generating docs for spec {} in directory", f.getName());
super.execute();
try {
super.execute();
} catch (MojoExecutionException | MojoFailureException e) {
log.error("Failed to generate doc for spec: {}", inputSpec);
}
}

} else {
Expand All @@ -58,4 +69,15 @@ private void fileInputExecute(File inputSpecFile) throws MojoExecutionException,

}
}

private File[] findAllOpenApiSpecs(File specDirectory) {
DirectoryScanner directoryScanner = new DirectoryScanner();
directoryScanner.setBasedir(specDirectory);
directoryScanner.setIncludes(openApiFileFilters.replace(" ", "").split(","));
directoryScanner.scan();

String[] includedFiles = directoryScanner.getIncludedFiles();
return Arrays.stream(includedFiles).map(pathname -> new File(specDirectory, pathname))
.collect(Collectors.toList()).toArray(new File[]{});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,12 @@ class ExportBomMojoTests {
@Mock
Metadata metadatamock;

@Captor
ArgumentCaptor<List> argCaptor;


@Test
void testExportBomEmptyMeta() throws MojoFailureException, MojoExecutionException, ArtifactResolutionException {
void testExportBomEmptyMeta() throws MojoExecutionException {
artifactResolver = mock(ArtifactResolver.class);
artifactResult = mock( ArtifactResult.class);
metadataResolver = mock(MetadataResolver.class);
org.eclipse.aether.artifact.Artifact artifact = mock(org.eclipse.aether.artifact.Artifact.class);

when(metadataResolver.resolveMetadata(any(),any())).thenReturn(Collections.singletonList(metadataResult));

Expand All @@ -77,13 +73,14 @@ void testExportBomEmptyMeta() throws MojoFailureException, MojoExecutionExceptio
build.setDirectory("target");

MavenProject project = new MavenProject();
mojo.remoteRepositories = Collections.EMPTY_LIST;
mojo.remoteRepositories = Collections.emptyList();
project.setBuild(build);
mojo.project = project;
mojo.repositorySession = mock(RepositorySystemSession.class);
mojo.execute();

assertThat(output.list()).isEmpty();
assertThat(output).isEmptyDirectory();

}

@Test
Expand All @@ -98,7 +95,7 @@ void testExportBomUseOfArtifactResolver() throws MojoFailureException, MojoExecu
artifactResolver = mock(ArtifactResolver.class);
artifactResult = mock( ArtifactResult.class);
org.eclipse.aether.artifact.Artifact artifact; //= mock(org.eclipse.aether.artifact.Artifact.class);
artifact = new DefaultArtifact(groupId, artifactId, "", type, version,Collections.EMPTY_MAP, pomFile);
artifact = new DefaultArtifact(groupId, artifactId, "", type, version,Collections.emptyMap(), pomFile);


when(artifactResolver.resolveArtifact(any(),any())).thenReturn(artifactResult);
Expand Down Expand Up @@ -138,14 +135,14 @@ void testExportBomUseOfArtifactResolver() throws MojoFailureException, MojoExecu
build.setDirectory("target");

MavenProject project = new MavenProject();
mojo.remoteRepositories = Collections.EMPTY_LIST;
mojo.remoteRepositories = Collections.emptyList();

project.setBuild(build);
mojo.project = project;
mojo.repositorySession = mock(RepositorySystemSession.class);
mojo.execute();

assertThat(output.list()).isEmpty();
assertThat(output).isEmptyDirectory();

}
private File getFile(String fileName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,9 @@ void testBoatDocsWithDirectory() throws MojoExecutionException, MojoFailureExcep
mojo.skipIfSpecIsUnchanged = false;
mojo.bundleSpecs = true;
mojo.dereferenceComponents = true;
mojo.openApiFileFilters = "**/*.yaml";
mojo.execute();
assertThat(output.list()).containsExactlyInAnyOrder("link-docs", "petstore-docs", "petstore-new-non-breaking-docs", "upto-docs");
assertThat(output.list()).contains("link", "petstore", "petstore-new-non-breaking", "upto");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.backbase.oss.codegen;

import org.openapitools.codegen.DefaultGenerator;

public class BoatDefaultGenerator extends DefaultGenerator {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package com.backbase.oss.codegen;

import com.backbase.oss.codegen.doc.BoatCodegenParameter;
import com.backbase.oss.codegen.doc.BoatCodegenResponse;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.node.ArrayNode;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;
import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.CodegenParameter;
import org.openapitools.codegen.CodegenResponse;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@SuppressWarnings("common-java:DuplicatedBlocks")
public class BoatStaticDocsGenerator extends org.openapitools.codegen.languages.StaticHtml2Generator {

private static final ObjectMapper objectMapper = new ObjectMapper();
private static final ObjectReader paramReader = BoatStaticDocsGenerator.objectMapper.readerFor(new TypeReference<List<String>>() {
});
public BoatStaticDocsGenerator() {
super();
}

@Override
public void preprocessOpenAPI(OpenAPI openAPI) {
super.preprocessOpenAPI(openAPI);

// Add responses to additional properties
if (openAPI.getComponents().getResponses() != null) {
additionalProperties.put("responses", openAPI.getComponents().getResponses().entrySet().stream()
.map(this::mapCodegenResponse)
.collect(Collectors.toList()));
}

// Add parameters to additonal properties
if (openAPI.getComponents().getParameters() != null) {
Set<String> imports = new HashSet<>();
additionalProperties.put("parameters", openAPI.getComponents().getParameters().entrySet().stream()
.map(nameParameter -> mapComponentParameter(imports, nameParameter))
.collect(Collectors.toList()));
}

// Add requests to addtional properties
if (openAPI.getComponents().getRequestBodies() != null) {
Set<String> imports = new HashSet<>();
additionalProperties.put("requestBodies", openAPI.getComponents().getRequestBodies().entrySet().stream()
.map(namedRequestBody -> mapComponentRequestBody(imports, namedRequestBody))
.collect(Collectors.toList()));
}

if (openAPI.getPaths() != null)
// Ensure single tags for operations
openAPI.getPaths().forEach((path, pathItem) ->
pathItem.readOperations().forEach(operation -> {
if (operation.getTags() != null && operation.getTags().size() > 1) {
String tag = operation.getTags().get(operation.getTags().size() - 1);
log.warn("Operation: {} contains multiple tags {} which hinders rendering documentation. Rep" +
"lacing it with a single tag: {}", operation.getOperationId(), operation.getTags(), tag);
operation.tags(Collections.singletonList(tag));
}
}));
}

private CodegenParameter mapComponentRequestBody(Set<String> imports, java.util.Map.Entry<String, RequestBody> namedRequestBody) {
String name = namedRequestBody.getKey();
RequestBody requestBody = namedRequestBody.getValue();
return fromRequestBody(requestBody, imports, name);
}

private CodegenParameter mapComponentParameter(Set<String> imports, java.util.Map.Entry<String, Parameter> nameParameter) {
Parameter parameter = nameParameter.getValue();
return fromParameter(parameter, imports);
}

private CodegenResponse mapCodegenResponse(java.util.Map.Entry<String, ApiResponse> codeResponse) {
String responseCode = codeResponse.getKey();
// try to resolve response code from key. otherwise use default
responseCode = responseCode.replaceAll("\\D+", "");
if (responseCode.length() != 3) {
responseCode = "default";
}
ApiResponse response = codeResponse.getValue();
return fromResponse(responseCode, response);
}

@Override
public String toModelName(String name) {
String modelName = super.toModelName(name);
if (!name.equals(modelName)) {
log.debug("NOT converting toModelName: {} to: {}", name, modelName);
}
return name;
}

@Override
public String toVarName(String name) {
String varName = super.toVarName(name);
if (!name.equals(varName)) {
log.debug("NOT converting varName: {} to: {}", name, varName);
}
return name;
}

@Override
public String toApiVarName(String name) {
String apiVarName = super.toApiVarName(name);
if (!name.equals(apiVarName)) {
log.debug("NOT converting apiVarName: {} to: {}", name, apiVarName);
}
return name;
}

@Override
public String toParamName(String name) {
String paramName = super.toParamName(name);
if (!name.equals(paramName)) {
log.debug("NOT converting apiVarName: {} to: {}", name, paramName);
}
return name;
}

@Override
public CodegenParameter fromParameter(Parameter parameter, Set<String> imports) {
CodegenParameter codegenParameter = super.fromParameter(parameter, imports);
log.debug("Created CodegenParameter model for parameter: {}", parameter.getName());
return BoatCodegenParameter.fromCodegenParameter(parameter, codegenParameter, openAPI);
}

@Override
public CodegenParameter fromRequestBody(RequestBody body, Set<String> imports, String bodyParameterName) {
CodegenParameter codegenParameter = super.fromRequestBody(body, imports, bodyParameterName);
log.debug("Created CodegenParameter model for request body: {} with bodyParameterName: {}", codegenParameter.baseName, bodyParameterName);
return BoatCodegenParameter.fromCodegenParameter(codegenParameter, body, openAPI);
}

@Override
public CodegenResponse fromResponse(String responseCode, ApiResponse response) {
CodegenResponse r = super.fromResponse(responseCode, response);
r.message = StringUtils.replace(r.message, "`", "\\`");

return new BoatCodegenResponse(r, responseCode, response, openAPI);
}

@Override
public CodegenModel fromModel(String name, Schema schema) {
CodegenModel codegenModel = super.fromModel(name, schema);
log.debug("Created CodegenModel for name: {}, schema: {} resulting in: {}", name, schema.getName(), codegenModel.getName());
codegenModel.isAlias = false;
return codegenModel;
}

@Override
public void setParameterExampleValue(CodegenParameter codegenParameter, Parameter parameter) {
super.setParameterExampleValue(codegenParameter, parameter);

Object example = parameter.getExample();

if (parameter.getStyle() != null
&& parameter.getStyle() == Parameter.StyleEnum.FORM
&& example instanceof ArrayNode && codegenParameter.isQueryParam) {
try {
List<String> values = BoatStaticDocsGenerator.paramReader.readValue((ArrayNode) example);
List<BasicNameValuePair> params = values.stream()
.map(value -> new BasicNameValuePair(codegenParameter.paramName, value))
.collect(Collectors.toList());
codegenParameter.example = URLEncodedUtils.format(params, Charset.defaultCharset());
} catch (IOException e) {
log.warn("Failed to format query string parameter: {}", codegenParameter.example);
}
}
}
}
Loading