Skip to content

Commit

Permalink
issue-4
Browse files Browse the repository at this point in the history
  • Loading branch information
ethlo committed Apr 25, 2022
1 parent 2f4ee31 commit 9a1d4a0
Show file tree
Hide file tree
Showing 6 changed files with 377 additions and 32 deletions.
11 changes: 0 additions & 11 deletions src/main/java/com/ethlo/zally/ApiReporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,11 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

Expand Down Expand Up @@ -64,15 +62,6 @@ private Map<Method, Operation> getOperations(PathItem value)
return operations;
}

private Optional<Map.Entry<Method, Operation>> getOperationByMethod(Method method, Operation operation)
{
if (operation != null)
{
return Optional.of(new AbstractMap.SimpleEntry<>(method, operation));
}
return Optional.empty();
}

public String render()
{
final Paths paths = this.openAPI.getPaths();
Expand Down
199 changes: 199 additions & 0 deletions src/main/java/com/ethlo/zally/ExtractMojo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package com.ethlo.zally;/*-
* #%L
* zally-maven-plugin
* %%
* Copyright (C) 2021 Morten Haraldsen (ethlo)
* %%
* 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
*
* http://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.
* #L%
*/

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import io.swagger.v3.core.filter.AbstractSpecFilter;
import io.swagger.v3.core.filter.SpecFilter;
import io.swagger.v3.core.model.ApiDescription;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;

@Mojo(threadSafe = true, name = "extract", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
public class ExtractMojo extends AbstractMojo
{
@Parameter(required = true, defaultValue = "${project.basedir}/src/main/resources/api.yaml", property = "zally.source")
private String source;

@Parameter(property = "targetFile")
private File targetFile;

@Parameter(property = "zally.skip", defaultValue = "false")
private boolean skip;

@Parameter(property = "filters")
private List<String> filters;

@Parameter(property = "name", required = true)
private String name;

@Parameter(property = "title")
private String title;

@Parameter(defaultValue = "${project}", required = true, readonly = true)
private MavenProject project;

private static final ObjectMapper mapper = new ObjectMapper();

@Override
public void execute() throws MojoFailureException
{
final Optional<OpenAPI> loaded = load(getLog(), skip, source);

if (targetFile == null)
{
targetFile = Paths.get(project.getBuild().getOutputDirectory()).resolve(name + ".yaml").toFile();
}

loaded.ifPresent(openAPI ->
{
getLog().info(String.format("Processing filter %s", name));
final OpenAPI filtered = new SpecFilter().filter(openAPI, new AbstractSpecFilter()
{
@Override
public Optional<Operation> filterOperation(final Operation operation, final ApiDescription api, final Map<String, List<String>> params, final Map<String, String> cookies, final Map<String, List<String>> headers)
{
final String path = api.getPath();
final PathItem pathItem = openAPI.getPaths().get(path);
final Map<String, Object> extensionMap = new LinkedHashMap<>(Optional.ofNullable(pathItem.getExtensions()).orElse(Collections.emptyMap()));
extensionMap.putAll(operation.getExtensions());
final OperationData operationData = new OperationData(operation, api, extensionMap);
if (filters.stream().anyMatch(filter -> match(filter, operationData)))
{
getLog().info(String.format("Including operation %s in %s", operation.getOperationId(), name));
return Optional.of(operation);
}
return Optional.empty();
}

@Override
public boolean isRemovingUnreferencedDefinitions()
{
return true;
}
}, null, null, null);


// Set title of document
Optional.ofNullable(title).ifPresent(t -> filtered.getInfo().setTitle(t));

try
{
getLog().info("Writing extracted APIs to " + targetFile);
Files.createDirectories(targetFile.toPath().getParent());
new ObjectMapper(new YAMLFactory()
.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES)).
setSerializationInclusion(JsonInclude.Include.NON_EMPTY).writeValue(targetFile, filtered);
}
catch (IOException e)
{
throw new UncheckedIOException(e);
}
});
}

public static boolean match(final String filter, final OperationData data)
{
final String[] expression = filter.split("=");
final String path = expression[0].startsWith("/") ? expression[0] : "/" + expression[0];
final String regexp = expression[1];
try
{
final JsonNode jsonNode = mapper.readTree(mapper.writeValueAsString(data));
final JsonNode value = jsonNode.at(JsonPointer.compile(path));
if (value.isObject())
{
final Iterator<String> fieldNames = value.fieldNames();
while (fieldNames.hasNext())
{
if (fieldNames.next().matches(regexp))
{
return true;
}
}
}
else if (value.isArray())
{
final Iterator<JsonNode> elements = value.elements();
while (elements.hasNext())
{
if (elements.next().textValue().matches(regexp))
{
return true;
}
}
}
else if (value.isTextual())
{
return value.textValue().matches(regexp);
}

return false;
}
catch (IOException exc)
{
throw new UncheckedIOException(exc);
}
}

public static Optional<OpenAPI> load(final Log log, final boolean skip, final String source) throws MojoFailureException
{
if (skip)
{
log.info("Skipping execution as requested");
return Optional.empty();
}

final boolean existsOnClassPath = ExtractMojo.class.getClassLoader().getResourceAsStream(source) != null;
final boolean existsOnFilesystem = Files.exists(Paths.get(source));
if (!existsOnClassPath && !existsOnFilesystem)
{
throw new MojoFailureException("The specified source file could not be found: " + source);
}

log.info("Reading file '" + source + "'");
return Optional.of(new OpenApiParser().parse(source));
}
}
77 changes: 77 additions & 0 deletions src/main/java/com/ethlo/zally/OperationData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.ethlo.zally;

/*-
* #%L
* zally-maven-plugin
* %%
* Copyright (C) 2021 - 2022 Morten Haraldsen (ethlo)
* %%
* 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
*
* http://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.
* #L%
*/

import io.swagger.v3.core.model.ApiDescription;
import io.swagger.v3.oas.models.Operation;

import java.util.List;
import java.util.Map;

public class OperationData
{
private final Map<String, Object> extensions;
private final String method;
private final String path;
private final Boolean deprecated;
private final String operationId;
private final List<String> tags;

public OperationData(final Operation operation, final ApiDescription api, final Map<String, Object> extensions)
{
this.method = api.getMethod();
this.path = api.getPath();
this.deprecated = operation.getDeprecated();
this.operationId = operation.getOperationId();
this.tags = operation.getTags();
this.extensions = extensions;
}

public Map<String, Object> getExtensions()
{
return extensions;
}

public String getMethod()
{
return method;
}

public String getPath()
{
return path;
}

public Boolean getDeprecated()
{
return deprecated;
}

public String getOperationId()
{
return operationId;
}

public List<String> getTags()
{
return tags;
}
}
34 changes: 14 additions & 20 deletions src/main/java/com/ethlo/zally/ReportingMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
* #L%
*/

import java.nio.file.Files;
import java.nio.file.Paths;
import static com.ethlo.zally.ExtractMojo.load;

import java.util.Arrays;
import java.util.Optional;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoFailureException;
Expand All @@ -29,6 +30,8 @@
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import io.swagger.v3.oas.models.OpenAPI;

@Mojo(threadSafe = true, name = "report", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
public class ReportingMojo extends AbstractMojo
{
Expand All @@ -44,25 +47,16 @@ public class ReportingMojo extends AbstractMojo
@Override
public void execute() throws MojoFailureException
{
if (skip)
{
getLog().info("Skipping execution as requested");
return;
}
final Optional<OpenAPI> loaded = load(getLog(), skip, source);

final boolean existsOnClassPath = getClass().getClassLoader().getResourceAsStream(source) != null;
final boolean existsOnFilesystem = Files.exists(Paths.get(source));
if (!existsOnClassPath && !existsOnFilesystem)
loaded.ifPresent(openAPI ->
{
throw new MojoFailureException("The specified source file could not be found: " + source);
}

getLog().info("Analyzing file '" + source + "'");

getLog().info("");
getLog().info("API path hierarchy:");
final String hierarchy = new ApiReporter(new OpenApiParser().parse(source)).render();
Arrays.stream(hierarchy.split("\n")).forEach(line -> getLog().info(line));
getLog().info("");
getLog().info("Analyzing file '" + source + "'");
getLog().info("");
getLog().info("API path hierarchy:");
final String hierarchy = new ApiReporter(new OpenApiParser().parse(source)).render();
Arrays.stream(hierarchy.split("\n")).forEach(line -> getLog().info(line));
getLog().info("");
});
}
}
Loading

0 comments on commit 9a1d4a0

Please sign in to comment.