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
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class Decomposer implements Transformer {
public OpenAPI transform(OpenAPI openAPI, Map<String, Object> options) {

List<Schema> composedSchemas = openAPI.getComponents().getSchemas().values().stream()
.filter(schema -> schema instanceof ComposedSchema)
.filter(ComposedSchema.class::isInstance)
.collect(Collectors.toList());

composedSchemas.forEach(composedSchema -> mergeComposedSchema(openAPI, composedSchema));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,62 +51,66 @@ private void fileInputExecute(File inputSpecFile) throws MojoExecutionException,
inputSpecs = findAllOpenApiSpecs(inputSpecFile);

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

List<File> success = new ArrayList<>();
List<File> failed = new ArrayList<>();
for (File f : inputSpecs) {
inputSpec = f.getPath();
output = new File(outPutDirectory.getPath(), f.getName().substring(0, f.getName().lastIndexOf(".")));

if (!output.exists()) {
try {
Files.createDirectory(output.toPath());
} catch (IOException e) {
log.error("Failed to create output directory", e);
}
List<File> success = new ArrayList<>();
List<File> failed = new ArrayList<>();
for (File inputSpec : inputSpecs) {
executeInputFile(outPutDirectory, success, failed, inputSpec);
}
writeMarkers(success, failed);
}
} else {
log.info("inputSpec being read as a single file");
super.execute();
}
}

log.info(" Generating docs for spec {} in directory", f.getName());
try {
super.execute();
success.add(f);
} catch (MojoExecutionException | MojoFailureException e) {
log.error("Failed to generate doc for spec: {}", inputSpec);
failed.add(f);
private void writeMarkers(List<File> success, List<File> failed) throws MojoExecutionException {
if (markersDirectory != null) {
try {
if (!markersDirectory.exists()) {
Files.createDirectory(markersDirectory.toPath());
}
}

if (markersDirectory != null) {
try {
if (!markersDirectory.exists()) {
Files.createDirectory(markersDirectory.toPath());
}

Files.write(new File(markersDirectory, "success.lst").toPath(),
listOfFilesToString(success).getBytes(StandardCharsets.UTF_8),
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
Files.write(new File(markersDirectory, "failed.lst").toPath(),
listOfFilesToString(failed).getBytes(StandardCharsets.UTF_8),
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);

} catch (IOException e) {
log.error("Failed to write BOAT markers to: {}", markersDirectory, e);
throw new MojoExecutionException("Failed to write BOAT markers", e);
Files.write(new File(markersDirectory, "success.lst").toPath(),
listOfFilesToString(success).getBytes(StandardCharsets.UTF_8),
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
Files.write(new File(markersDirectory, "failed.lst").toPath(),
listOfFilesToString(failed).getBytes(StandardCharsets.UTF_8),
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);

}
} catch (IOException e) {
log.error("Failed to write BOAT markers to: {}", markersDirectory, e);
throw new MojoExecutionException("Failed to write BOAT markers", e);

}

}
}

} else {
private void executeInputFile(File outPutDirectory, List<File> success, List<File> failed, File f) {
inputSpec = f.getPath();
output = new File(outPutDirectory.getPath(), f.getName().substring(0, f.getName().lastIndexOf(".")));

log.info("inputSpec being read as a single file");
super.execute();
if (!output.exists()) {
try {
Files.createDirectory(output.toPath());
} catch (IOException e) {
log.error("Failed to create output directory", e);
}
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,9 +509,7 @@ public void execute() throws MojoExecutionException, MojoFailureException {
default:
String message = format("Input spec %s matches more than one single file", inputSpec);
getLog().error(message);
Stream.of(files).forEach(f -> {
getLog().error(format(" %s", f));
});
Stream.of(files).forEach(f -> getLog().error(format(" %s", f)));
throw new MojoExecutionException(
format("Input spec %s matches more than one single file", inputSpec));
}
Expand Down Expand Up @@ -966,7 +964,7 @@ private URL inputSpecRemoteUrl() {
/**
* Get specification hash file.
*
* @param inputSpecFile - Openapi specification input file to calculate it's hash.
* @param inputSpecFile - Openapi specification input file to calculate it's hash.
* Does not taken into account if input spec is hosted on remote resource
* @return a file with previously calculated hash
*/
Expand Down Expand Up @@ -1044,7 +1042,7 @@ private void adjustAdditionalProperties(final CodegenConfig config) {

private static boolean isValidURI(String urlString) {
try {
URI uri = new URI(urlString);
new URI(urlString);
return true;
} catch (Exception exception) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,31 @@

import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import java.util.List;
import io.swagger.v3.oas.models.responses.ApiResponse;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Slf4j
@UtilityClass
@SuppressWarnings("java:S3740")
public class BoatExampleUtils {

private static final String PATHS_REF_PREFIX = "#/paths";
private static final String COMPONENTS_EXAMPLES_REF_PREFIX = "#/components/examples/";


public static void convertExamples(OpenAPI openAPI, MediaType mediaType, String responseCode, String contentType, List<BoatExample> examples) {
if (mediaType.getExample() != null) {
Object example = mediaType.getExample();
Expand Down Expand Up @@ -62,27 +73,126 @@ private static boolean isJson(String contentType) {
}

public static void inlineExamples(String name, List<BoatExample> examples, OpenAPI openAPI) {
List<BoatExample> nonExistingExamples = new ArrayList<>();

examples.stream()
.filter(boatExample -> boatExample.getExample().get$ref() != null)
.forEach(boatExample -> {
String ref = boatExample.getExample().get$ref();
if (ref.startsWith("#/components/examples")) {
ref = StringUtils.substringAfterLast(ref, "/");
if (openAPI.getComponents() != null && openAPI.getComponents().getExamples() != null) {
Example example = openAPI.getComponents().getExamples().get(ref);
if (example == null) {
log.warn("Example ref: {} used in: {} refers to an example that does not exist", ref, name);
} else {
log.debug("Replacing Example ref: {} used in: {} with example from components: {}", ref, name, example);
boatExample.setExample(example);
}
.filter(boatExample -> {
Example example = boatExample.getExample();
if (example == null) {
log.warn("Example :{} refers to an example that does not exist", boatExample.getKey());
nonExistingExamples.add(boatExample);
}
return example != null && example.get$ref() != null;
})
.forEach(boatExample -> {
String ref = boatExample.getExample().get$ref();
if (ref.startsWith(COMPONENTS_EXAMPLES_REF_PREFIX)) {
resolveComponentsExamples(name, openAPI, boatExample, ref);
} else if (ref.startsWith(PATHS_REF_PREFIX)) {
resolvePathsExamples(name, openAPI, boatExample, ref);
} else {
log.warn("Example ref: {} used in: {} refers to an example that does not exist", ref, name);
log.warn("Example ref: {} used in: {} refers to an example that does not exist", ref, name);
}
} else {
log.warn("Example ref: {} used in: {} refers to an example that does not exist", ref, name);
}
});
});
// Ensure non existing examples are removed to prevent further errors down the road
examples.removeAll(nonExistingExamples);
}

private static void resolveComponentsExamples(
String name, OpenAPI openAPI, BoatExample boatExample, String ref) {
String exampleName = ref.replace(COMPONENTS_EXAMPLES_REF_PREFIX, "");
if (openAPI.getComponents() == null || openAPI.getComponents().getExamples() == null) {
log.warn("Example ref: {} used in: {} refers to an example that does not exist", ref, name);
return;
}
Example example = openAPI.getComponents().getExamples().get(exampleName);
if (example == null) {
log.warn("Example ref: {} used in: {} refers to an example that does not exist", ref, name);
return;
}
log.debug("Replacing Example ref: {} used in: {} with example from components: {}", ref, name, example);
boatExample.setExample(example);
}

private static void resolvePathsExamples(
String name, OpenAPI openAPI, BoatExample boatExample, String ref) {

// #/paths/
// ~1client-api~1v2~1accounts~1balance-history~1%7BarrangementIds%7D/
// get/
// responses/
// 200/
// content/
// text~1csv/
// example
if (openAPI.getPaths() == null) {
log.warn("Example ref: {} refers to '/paths' but it is not there.", ref);
return;
}
String[] refParts = Arrays.stream(ref.replace(PATHS_REF_PREFIX, "").split("/"))
.map(s -> s.replace("~1", "/"))
.toArray(String[]::new);

String pathName = refParts[1];
PathItem pathItem = openAPI.getPaths().get(pathName);
if (pathItem == null) {
log.warn("Example ref: {} refers to path {} but it is not defined.", ref, pathName);
return;
}

String operationName = refParts[2];
Operation operation = findOperation(pathItem, operationName);
if (operation == null) {
log.warn("Example ref: {} refers to operation {} but it is not defined.", ref, operationName);
return;
}

Content content = null;
String mediaTypeName = null;
if ("requestBody".equals(refParts[3])) {
content = operation.getRequestBody().getContent();
mediaTypeName = refParts[5];
} else {
ApiResponse apiResponse = operation.getResponses().get(refParts[4]);
if (apiResponse == null) {
log.warn("Example ref: {} refers to response that is not defined.", ref);
return;
}
content = apiResponse.getContent();
mediaTypeName = refParts[6];
}
if (content == null) {
log.warn("Example ref: {} refers to content that is not defined.", ref);
return;
}

MediaType mediaType = content.get(mediaTypeName);
if (mediaType == null) {
log.warn("Example ref: {} refers to mediaType {} that is not defined.", ref, mediaTypeName);
return;
}

Example example = new Example().value(mediaType.getExample());
log.warn("Incorrect example reference found! Replacing Example ref: {} used in: {} with example from components", ref, name);
boatExample.setExample(example);
}

private static Operation findOperation(PathItem pathItem, String operationName) {
String o = operationName.toLowerCase();
switch (o) {
case "get":
return pathItem.getGet();
case "post":
return pathItem.getPost();
case "put":
return pathItem.getPut();
case "patch":
return pathItem.getPatch();
case "delete":
return pathItem.getDelete();
default:
throw new IllegalArgumentException("Unsupported operationName " + o);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ void testOpenAPiWithExamples() throws OpenAPILoaderException {
assertDoesNotThrow(() -> generateDocs(getFile("/openapi-with-examples/openapi.yaml")));
}

@Test
public void testGenerateDocsExampleRefs() {
assertDoesNotThrow(() -> generateDocs(getFile("/oas-examples/petstore-example-refs.yaml")));
}

@Test
void testGenerateDocs() throws IOException {
generateDocs(getFile("/openapi-with-examples/openapi-with-json.yaml"));
Expand Down
2 changes: 2 additions & 0 deletions boat-scaffold/src/test/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<configuration>
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />

<appender name="COLOR" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%highlight([%level]) [%logger{10}] %msg%n</pattern>
Expand Down
Loading