From 9a1d4a05129aa1fd0d32790b605f27e1330979c3 Mon Sep 17 00:00:00 2001 From: Morten Haraldsen Date: Mon, 25 Apr 2022 23:14:44 +0200 Subject: [PATCH 1/2] issue-4 --- .../java/com/ethlo/zally/ApiReporter.java | 11 - .../java/com/ethlo/zally/ExtractMojo.java | 199 ++++++++++++++++++ .../java/com/ethlo/zally/OperationData.java | 77 +++++++ .../java/com/ethlo/zally/ReportingMojo.java | 34 ++- .../java/com/ethlo/zally/ExtractMojoTest.java | 66 ++++++ ...ListedPluralizeNamesForArraysRuleTest.java | 22 +- 6 files changed, 377 insertions(+), 32 deletions(-) create mode 100644 src/main/java/com/ethlo/zally/ExtractMojo.java create mode 100644 src/main/java/com/ethlo/zally/OperationData.java create mode 100644 src/test/java/com/ethlo/zally/ExtractMojoTest.java diff --git a/src/main/java/com/ethlo/zally/ApiReporter.java b/src/main/java/com/ethlo/zally/ApiReporter.java index 618450a..046e2e9 100644 --- a/src/main/java/com/ethlo/zally/ApiReporter.java +++ b/src/main/java/com/ethlo/zally/ApiReporter.java @@ -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; @@ -64,15 +62,6 @@ private Map getOperations(PathItem value) return operations; } - private Optional> 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(); diff --git a/src/main/java/com/ethlo/zally/ExtractMojo.java b/src/main/java/com/ethlo/zally/ExtractMojo.java new file mode 100644 index 0000000..80bfe5c --- /dev/null +++ b/src/main/java/com/ethlo/zally/ExtractMojo.java @@ -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 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 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 filterOperation(final Operation operation, final ApiDescription api, final Map> params, final Map cookies, final Map> headers) + { + final String path = api.getPath(); + final PathItem pathItem = openAPI.getPaths().get(path); + final Map 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 fieldNames = value.fieldNames(); + while (fieldNames.hasNext()) + { + if (fieldNames.next().matches(regexp)) + { + return true; + } + } + } + else if (value.isArray()) + { + final Iterator 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 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)); + } +} diff --git a/src/main/java/com/ethlo/zally/OperationData.java b/src/main/java/com/ethlo/zally/OperationData.java new file mode 100644 index 0000000..6ff866d --- /dev/null +++ b/src/main/java/com/ethlo/zally/OperationData.java @@ -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 extensions; + private final String method; + private final String path; + private final Boolean deprecated; + private final String operationId; + private final List tags; + + public OperationData(final Operation operation, final ApiDescription api, final Map 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 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 getTags() + { + return tags; + } +} diff --git a/src/main/java/com/ethlo/zally/ReportingMojo.java b/src/main/java/com/ethlo/zally/ReportingMojo.java index a51a4c7..40e51e1 100644 --- a/src/main/java/com/ethlo/zally/ReportingMojo.java +++ b/src/main/java/com/ethlo/zally/ReportingMojo.java @@ -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; @@ -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 { @@ -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 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(""); + }); } } diff --git a/src/test/java/com/ethlo/zally/ExtractMojoTest.java b/src/test/java/com/ethlo/zally/ExtractMojoTest.java new file mode 100644 index 0000000..bc068f7 --- /dev/null +++ b/src/test/java/com/ethlo/zally/ExtractMojoTest.java @@ -0,0 +1,66 @@ +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 static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; + +import org.junit.Test; + +import io.swagger.v3.core.model.ApiDescription; +import io.swagger.v3.oas.models.Operation; + +public class ExtractMojoTest +{ + private final OperationData operationData = new OperationData(new Operation().operationId("my-operation"), new ApiDescription("/foo/bar/{baz}", "get"), Collections.singletonMap("x-foo", true)); + + @Test + public void matchOperation() + { + assertThat(ExtractMojo.match("operationId=my-operation", operationData)).isTrue(); + } + + @Test + public void testOperationRegex() + { + assertThat(ExtractMojo.match("operationId=my-ope.*", operationData)).isTrue(); + } + + @Test + public void testOperationRegexNegative() + { + assertThat(ExtractMojo.match("operationId=!^my-ope.*", operationData)).isFalse(); + } + + @Test + public void testPathRegex() + { + assertThat(ExtractMojo.match("path=/foo/.*", operationData)).isTrue(); + assertThat(ExtractMojo.match("path=/foo/baz/.*", operationData)).isFalse(); + } + + @Test + public void testMatchExtensionKey() + { + assertThat(ExtractMojo.match("extensions=x-foo", operationData)).isTrue(); + } +} diff --git a/src/test/java/com/ethlo/zally/rules/WhiteListedPluralizeNamesForArraysRuleTest.java b/src/test/java/com/ethlo/zally/rules/WhiteListedPluralizeNamesForArraysRuleTest.java index 3cfb505..798ca00 100644 --- a/src/test/java/com/ethlo/zally/rules/WhiteListedPluralizeNamesForArraysRuleTest.java +++ b/src/test/java/com/ethlo/zally/rules/WhiteListedPluralizeNamesForArraysRuleTest.java @@ -1,5 +1,25 @@ package com.ethlo.zally.rules; +/*- + * #%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 static org.assertj.core.api.Assertions.assertThat; import java.util.List; @@ -24,4 +44,4 @@ public void checkArrayPropertyNamesArePlural() final List violations = new WhiteListedPluralizeNamesForArraysRule(ConfigFactory.empty()).checkArrayPropertyNamesArePlural(context); assertThat(violations).isEmpty(); } -} \ No newline at end of file +} From 6000c2983cd4c82e76fe11e0358035881abd89ea Mon Sep 17 00:00:00 2001 From: Morten Haraldsen Date: Tue, 26 Apr 2022 18:45:05 +0200 Subject: [PATCH 2/2] issue-4 --- pom.xml | 18 +-- .../java/com/ethlo/zally/ExtractMojo.java | 106 +++++++++++++----- .../com/ethlo/zally/ExtractionDefinition.java | 54 +++++++++ .../java/com/ethlo/zally/OpenApiParser.java | 11 +- .../java/com/ethlo/zally/OperationFilter.java | 99 ++++++++++++++++ .../java/com/ethlo/zally/ReportingMojo.java | 4 +- .../java/com/ethlo/zally/ZallyRunner.java | 4 +- .../java/com/ethlo/zally/ExtractMojoTest.java | 16 +-- ...ListedPluralizeNamesForArraysRuleTest.java | 2 +- 9 files changed, 265 insertions(+), 49 deletions(-) create mode 100644 src/main/java/com/ethlo/zally/ExtractionDefinition.java create mode 100644 src/main/java/com/ethlo/zally/OperationFilter.java diff --git a/pom.xml b/pom.xml index dd32080..6a43037 100644 --- a/pom.xml +++ b/pom.xml @@ -212,33 +212,33 @@ org.jetbrains.kotlin kotlin-stdlib-jdk8 - 1.5.30-M1 + 1.6.21 org.slf4j slf4j-simple - 1.7.29 + 1.7.36 org.apache.maven maven-plugin-api - 3.6.3 + 3.8.5 org.apache.maven.plugin-tools maven-plugin-annotations - 3.6.0 + 3.6.4 provided org.apache.maven - maven-project - 2.2.1 + maven-core + 3.8.5 io.github.classgraph classgraph - 4.8.115 + 4.8.146 @@ -251,13 +251,13 @@ org.assertj assertj-core - 3.20.2 + 3.22.0 test junit junit - 4.13.1 + 4.13.2 test diff --git a/src/main/java/com/ethlo/zally/ExtractMojo.java b/src/main/java/com/ethlo/zally/ExtractMojo.java index 80bfe5c..71d424a 100644 --- a/src/main/java/com/ethlo/zally/ExtractMojo.java +++ b/src/main/java/com/ethlo/zally/ExtractMojo.java @@ -29,8 +29,10 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.plugins.annotations.LifecyclePhase; @@ -47,6 +49,7 @@ 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.core.util.Yaml; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.PathItem; @@ -57,16 +60,22 @@ 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 = "outputFile") + private File outputFile; - @Parameter(property = "zally.skip", defaultValue = "false") + @Parameter(property = "configFile") + private File configFile; + + @Parameter(property = "skip", defaultValue = "false") private boolean skip; @Parameter(property = "filters") - private List filters; + private List filters; + + @Parameter(property = "description") + private String description; - @Parameter(property = "name", required = true) + @Parameter(property = "name") private String name; @Parameter(property = "title") @@ -75,21 +84,59 @@ public class ExtractMojo extends AbstractMojo @Parameter(defaultValue = "${project}", required = true, readonly = true) private MavenProject project; + @Parameter(defaultValue = "${mojoExecution}", readonly = true, required = true) + private MojoExecution mojoExecution; + + private static final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory() + .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) + .enable(YAMLGenerator.Feature.SPLIT_LINES) + .enable(YAMLGenerator.Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS)) + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY); private static final ObjectMapper mapper = new ObjectMapper(); @Override public void execute() throws MojoFailureException { - final Optional loaded = load(getLog(), skip, source); + final Optional loaded = load(getLog(), skip, source, false); + + if (name == null) + { + name = mojoExecution.getExecutionId(); + } - if (targetFile == null) + if (outputFile == null) { - targetFile = Paths.get(project.getBuild().getOutputDirectory()).resolve(name + ".yaml").toFile(); + outputFile = Paths.get(project.getBuild().getOutputDirectory()).resolve(name + ".yaml").toFile(); + } + + if (configFile != null) + { + try + { + final ExtractionDefinition extractionDefinition = yamlMapper.readValue(configFile, ExtractionDefinition.class); + if (this.title == null) + { + this.title = extractionDefinition.getTitle(); + } + + if (this.description == null) + { + this.description = extractionDefinition.getDescription(); + } + + this.filters = extractionDefinition.getFilters(); + } + catch (IOException exc) + { + throw new UncheckedIOException(exc); + } } loaded.ifPresent(openAPI -> { - getLog().info(String.format("Processing filter %s", name)); + getLog().info(String.format("Processing extraction %s", name)); + final AtomicInteger totalEvaluated = new AtomicInteger(0); + final AtomicInteger totalMatched = new AtomicInteger(0); final OpenAPI filtered = new SpecFilter().filter(openAPI, new AbstractSpecFilter() { @Override @@ -100,9 +147,18 @@ public Optional filterOperation(final Operation operation, final ApiD final Map 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))) + if (filters.stream().anyMatch(filter -> + { + final boolean matched = match(filter, operationData); + if (matched) + { + totalMatched.incrementAndGet(); + getLog().info(String.format("Including '%s' in '%s' because '%s'", operation.getOperationId(), name, filter.asString())); + } + totalEvaluated.incrementAndGet(); + return matched; + })) { - getLog().info(String.format("Including operation %s in %s", operation.getOperationId(), name)); return Optional.of(operation); } return Optional.empty(); @@ -116,16 +172,15 @@ public boolean isRemovingUnreferencedDefinitions() }, null, null, null); - // Set title of document Optional.ofNullable(title).ifPresent(t -> filtered.getInfo().setTitle(t)); + Optional.ofNullable(description).ifPresent(t -> filtered.getInfo().setDescription(t)); + getLog().info(String.format("Included %s/%s API operations in extract", totalMatched.get(), totalEvaluated.get())); 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); + getLog().info("Writing extracted APIs to " + outputFile); + Files.createDirectories(outputFile.toPath().getParent()); + Yaml.mapper().writeValue(outputFile, filtered); } catch (IOException e) { @@ -134,11 +189,10 @@ public boolean isRemovingUnreferencedDefinitions() }); } - public static boolean match(final String filter, final OperationData data) + public static boolean match(final OperationFilter filter, final OperationData data) { - final String[] expression = filter.split("="); - final String path = expression[0].startsWith("/") ? expression[0] : "/" + expression[0]; - final String regexp = expression[1]; + final String path = filter.getPointer().startsWith("/") ? filter.getPointer() : "/" + filter.getPointer(); + final String regexp = filter.getExpression(); try { final JsonNode jsonNode = mapper.readTree(mapper.writeValueAsString(data)); @@ -148,7 +202,7 @@ public static boolean match(final String filter, final OperationData data) final Iterator fieldNames = value.fieldNames(); while (fieldNames.hasNext()) { - if (fieldNames.next().matches(regexp)) + if (filter.isMatch(fieldNames.next())) { return true; } @@ -159,7 +213,7 @@ else if (value.isArray()) final Iterator elements = value.elements(); while (elements.hasNext()) { - if (elements.next().textValue().matches(regexp)) + if (filter.isMatch(elements.next().textValue())) { return true; } @@ -167,7 +221,7 @@ else if (value.isArray()) } else if (value.isTextual()) { - return value.textValue().matches(regexp); + return filter.isMatch(value.textValue()); } return false; @@ -178,7 +232,7 @@ else if (value.isTextual()) } } - public static Optional load(final Log log, final boolean skip, final String source) throws MojoFailureException + public static Optional load(final Log log, final boolean skip, final String source, final boolean inlined) throws MojoFailureException { if (skip) { @@ -194,6 +248,6 @@ public static Optional load(final Log log, final boolean skip, final St } log.info("Reading file '" + source + "'"); - return Optional.of(new OpenApiParser().parse(source)); + return Optional.of(inlined ? new OpenApiParser().parseInlined(source) : new OpenApiParser().parse(source)); } } diff --git a/src/main/java/com/ethlo/zally/ExtractionDefinition.java b/src/main/java/com/ethlo/zally/ExtractionDefinition.java new file mode 100644 index 0000000..97214c8 --- /dev/null +++ b/src/main/java/com/ethlo/zally/ExtractionDefinition.java @@ -0,0 +1,54 @@ +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 java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ExtractionDefinition +{ + private final String title; + private final String description; + private final List filters; + + public ExtractionDefinition(@JsonProperty("title") final String title, @JsonProperty("description") final String description, @JsonProperty("filters") final List filters) + { + this.title = title; + this.description = description; + this.filters = filters; + } + + public String getTitle() + { + return title; + } + + public List getFilters() + { + return filters; + } + + public String getDescription() + { + return description; + } +} diff --git a/src/main/java/com/ethlo/zally/OpenApiParser.java b/src/main/java/com/ethlo/zally/OpenApiParser.java index 3257f0a..31f088d 100644 --- a/src/main/java/com/ethlo/zally/OpenApiParser.java +++ b/src/main/java/com/ethlo/zally/OpenApiParser.java @@ -26,7 +26,7 @@ public class OpenApiParser { - public OpenAPI parse(String url) + public OpenAPI parseInlined(String url) { final ParseOptions parseOptions = new ParseOptions(); parseOptions.setResolve(true); @@ -34,4 +34,13 @@ public OpenAPI parse(String url) new ResolverFully(true).resolveFully(parseResult); return parseResult; } + + public OpenAPI parse(final String url) + { + final ParseOptions parseOptions = new ParseOptions(); + parseOptions.setResolve(true); + final OpenAPI parseResult = new OpenAPIV3Parser().read(url); + //new ResolverFully(true).resolveFully(parseResult); + return parseResult; + } } diff --git a/src/main/java/com/ethlo/zally/OperationFilter.java b/src/main/java/com/ethlo/zally/OperationFilter.java new file mode 100644 index 0000000..11eac10 --- /dev/null +++ b/src/main/java/com/ethlo/zally/OperationFilter.java @@ -0,0 +1,99 @@ +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 java.util.Objects; +import java.util.regex.Pattern; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class OperationFilter +{ + private Pattern matcher; + private String pointer; + private String expression; + + public OperationFilter() + { + + } + + public OperationFilter(@JsonProperty("pointer") final String pointer, @JsonProperty("expression") final String expression) + { + this.pointer = pointer; + setExpression(expression); + } + + public String getPointer() + { + return pointer; + } + + public String getExpression() + { + return expression; + } + + public void setPointer(final String pointer) + { + this.pointer = pointer; + } + + public void setExpression(final String expression) + { + this.expression = expression; + this.matcher = Pattern.compile(expression); + } + + @Override + public boolean equals(final Object o) + { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final OperationFilter that = (OperationFilter) o; + return Objects.equals(pointer, that.pointer) && Objects.equals(expression, that.expression); + } + + @Override + public int hashCode() + { + return Objects.hash(pointer, expression); + } + + @Override + public String toString() + { + return "OperationFilter{" + + "pointer='" + pointer + '\'' + + ", expression='" + expression + '\'' + + '}'; + } + + public String asString() + { + return pointer + "=" + expression; + } + + public boolean isMatch(String value) + { + return matcher.matcher(value).matches(); + } +} diff --git a/src/main/java/com/ethlo/zally/ReportingMojo.java b/src/main/java/com/ethlo/zally/ReportingMojo.java index 40e51e1..80669d1 100644 --- a/src/main/java/com/ethlo/zally/ReportingMojo.java +++ b/src/main/java/com/ethlo/zally/ReportingMojo.java @@ -47,14 +47,14 @@ public class ReportingMojo extends AbstractMojo @Override public void execute() throws MojoFailureException { - final Optional loaded = load(getLog(), skip, source); + final Optional loaded = load(getLog(), skip, source, true); loaded.ifPresent(openAPI -> { getLog().info("Analyzing file '" + source + "'"); getLog().info(""); getLog().info("API path hierarchy:"); - final String hierarchy = new ApiReporter(new OpenApiParser().parse(source)).render(); + final String hierarchy = new ApiReporter(new OpenApiParser().parseInlined(source)).render(); Arrays.stream(hierarchy.split("\n")).forEach(line -> getLog().info(line)); getLog().info(""); }); diff --git a/src/main/java/com/ethlo/zally/ZallyRunner.java b/src/main/java/com/ethlo/zally/ZallyRunner.java index 280eb27..69b57b3 100644 --- a/src/main/java/com/ethlo/zally/ZallyRunner.java +++ b/src/main/java/com/ethlo/zally/ZallyRunner.java @@ -22,6 +22,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; @@ -43,7 +44,6 @@ import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; -import edu.emory.mathcs.backport.java.util.Collections; import io.github.classgraph.ClassGraph; import io.github.classgraph.ClassInfo; import io.github.classgraph.ClassInfoList; @@ -70,7 +70,7 @@ public ZallyRunner(final Config ruleConfigs, final Log logger) public Map> validate(String url, final Set skipped) throws IOException { - final OpenAPI openApi = new OpenApiParser().parse(url); + final OpenAPI openApi = new OpenApiParser().parseInlined(url); final Context context = new DefaultContext("", openApi, null); final Map> returnValue = new LinkedHashMap<>(); diff --git a/src/test/java/com/ethlo/zally/ExtractMojoTest.java b/src/test/java/com/ethlo/zally/ExtractMojoTest.java index bc068f7..9d13b7e 100644 --- a/src/test/java/com/ethlo/zally/ExtractMojoTest.java +++ b/src/test/java/com/ethlo/zally/ExtractMojoTest.java @@ -9,9 +9,9 @@ * 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. @@ -36,31 +36,31 @@ public class ExtractMojoTest @Test public void matchOperation() { - assertThat(ExtractMojo.match("operationId=my-operation", operationData)).isTrue(); + assertThat(ExtractMojo.match(new OperationFilter("operationId", "my-operation"), operationData)).isTrue(); } @Test public void testOperationRegex() { - assertThat(ExtractMojo.match("operationId=my-ope.*", operationData)).isTrue(); + assertThat(ExtractMojo.match(new OperationFilter("operationId", "my-ope.*"), operationData)).isTrue(); } @Test public void testOperationRegexNegative() { - assertThat(ExtractMojo.match("operationId=!^my-ope.*", operationData)).isFalse(); + assertThat(ExtractMojo.match(new OperationFilter("operationId", "!^my-ope.*"), operationData)).isFalse(); } @Test public void testPathRegex() { - assertThat(ExtractMojo.match("path=/foo/.*", operationData)).isTrue(); - assertThat(ExtractMojo.match("path=/foo/baz/.*", operationData)).isFalse(); + assertThat(ExtractMojo.match(new OperationFilter("path", "/foo/.*"), operationData)).isTrue(); + assertThat(ExtractMojo.match(new OperationFilter("path", "/foo/baz/.*"), operationData)).isFalse(); } @Test public void testMatchExtensionKey() { - assertThat(ExtractMojo.match("extensions=x-foo", operationData)).isTrue(); + assertThat(ExtractMojo.match(new OperationFilter("extensions", "x-foo"), operationData)).isTrue(); } } diff --git a/src/test/java/com/ethlo/zally/rules/WhiteListedPluralizeNamesForArraysRuleTest.java b/src/test/java/com/ethlo/zally/rules/WhiteListedPluralizeNamesForArraysRuleTest.java index 798ca00..746f55f 100644 --- a/src/test/java/com/ethlo/zally/rules/WhiteListedPluralizeNamesForArraysRuleTest.java +++ b/src/test/java/com/ethlo/zally/rules/WhiteListedPluralizeNamesForArraysRuleTest.java @@ -39,7 +39,7 @@ public class WhiteListedPluralizeNamesForArraysRuleTest public void checkArrayPropertyNamesArePlural() { final String url = "modified_petstore/petstore.yaml"; - final OpenAPI openApi = new OpenApiParser().parse(url); + final OpenAPI openApi = new OpenApiParser().parseInlined(url); final Context context = new DefaultContext("", openApi, null); final List violations = new WhiteListedPluralizeNamesForArraysRule(ConfigFactory.empty()).checkArrayPropertyNamesArePlural(context); assertThat(violations).isEmpty();