b/160263407 Create command line client and arguments parser Change-Id: I775f340cd69e4f464946894d8c3f32f6ee2a561b
diff --git a/build.gradle b/build.gradle index 5b3649f..76a7207 100644 --- a/build.gradle +++ b/build.gradle
@@ -21,12 +21,16 @@ // Logging "flogger": "0.5.1", // 22 June 2020 + "floggerBackend": "0.5.1", // 25 June 2020 "logback": "1.2.3", // 16 January 2020 "slf4j": "1.7.30", // 16 January 2020 // Client Libraries "httpClient": "4.5.12", // 22 June 2020 + // Helper Libraries + "apacheCommonsValidator": "1.4.0", // 25 June 2020 + // Build Utilities "errorprone":"2.4.0", // 22 June 2020 "errorproneJavacVersion":"9+181-r4173-1", // 22 June 2020 @@ -43,7 +47,8 @@ // OpenAPI Libraries "openapiParser": "1.0.1", // 23 June 2020 - "openapiCore": "1.0.1" // 23 June 2020 + "openapiCore": "1.0.1", // 23 June 2020 + "openapiSchemaValidator": "1.0.2" // 7 July 2020 ] allprojects { @@ -76,8 +81,10 @@ errorprone "com.google.errorprone:error_prone_core:${libVersions.errorprone}" implementation "com.google.flogger:flogger:${libVersions.flogger}" + implementation "com.google.flogger:flogger-system-backend:${libVersions.floggerBackend}" implementation "com.google.guava:guava:${libVersions.guava}" implementation "com.google.inject:guice:${libVersions.guice}" + implementation "commons-validator:commons-validator:${libVersions.apacheCommonsValidator}" //testCompile project(':oas-test') @@ -103,8 +110,7 @@ spotbugs { toolVersion = libVersions.spotbugs - // TODO(ttourani): Create spotbugs-exclude.xml - //excludeFilter = file("$rootDir/spotbugs-exclude.xml") + excludeFilter = file("$rootDir/spotbugs-exclude.xml") effort = "max" reportLevel = "low" }
diff --git a/oas-cli/build.gradle b/oas-cli/build.gradle index f2a89ff..f00b209 100644 --- a/oas-cli/build.gradle +++ b/oas-cli/build.gradle
@@ -1,5 +1,16 @@ +plugins { + id 'application' +} + dependencies { implementation 'com.beust:jcommander:1.78' testImplementation project(':oas-test') -} \ No newline at end of file +} + +jar { + manifest { + attributes('Main-Class': 'com.apigee.security.oas.CommandLineClient') + } +} +mainClassName = "com.apigee.security.oas.CommandLineClient" \ No newline at end of file
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineArgs.java b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineArgs.java new file mode 100644 index 0000000..20f9747 --- /dev/null +++ b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineArgs.java
@@ -0,0 +1,32 @@ +package com.apigee.security.oas; + +import com.apigee.security.oas.converters.FileConverter; +import com.apigee.security.oas.validators.FileValidator; +import com.beust.jcommander.Parameter; +import java.io.File; + +/** JCommander Command Line arguments. */ +public class CommandLineArgs { + + @Parameter( + names = {"--file", "-F"}, + description = "File location of the OpenAPI Specification Document", + required = true, + validateWith = {FileValidator.class}, + converter = FileConverter.class) + private File oasFile; + + @Parameter( + names = {"--help", "-h"}, + description = "List of all possible options", + help = true) + private boolean help = false; + + public boolean isHelp() { + return help; + } + + public File getOasFile() { + return oasFile; + } +}
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineBaseParser.java b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineBaseParser.java new file mode 100644 index 0000000..06208e7 --- /dev/null +++ b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineBaseParser.java
@@ -0,0 +1,40 @@ +package com.apigee.security.oas; + +import com.beust.jcommander.JCommander; +import java.io.File; +import javax.inject.Inject; + +/** + * Parses and validates command line arguments. + * + * <p>{@code oasFile} should be existent and accessible. + */ +final class CommandLineBaseParser implements CommandLineParser { + + private final CommandLineArgs commandLineArgs; + + @Inject + CommandLineBaseParser(CommandLineArgs commandLineArgs) { + this.commandLineArgs = commandLineArgs; + } + + /** + * Parses {@code args}. + * + * <p>Provides command line usage if help parameter is true. + */ + @Override + public void parseArguments(String[] args) { + JCommander jCommander = JCommander.newBuilder().addObject(commandLineArgs).build(); + jCommander.parse(args); + + if (commandLineArgs.isHelp()) { + jCommander.usage(); + } + } + + @Override + public File getOasFile() { + return commandLineArgs.getOasFile(); + } +}
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineBaseRunner.java b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineBaseRunner.java new file mode 100644 index 0000000..168084e --- /dev/null +++ b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineBaseRunner.java
@@ -0,0 +1,44 @@ +package com.apigee.security.oas; + +import com.beust.jcommander.ParameterException; +import com.google.common.flogger.FluentLogger; +import com.google.inject.Provider; +import java.io.PrintWriter; +import javax.inject.Inject; + +/** Class that handles the execution. */ +final class CommandLineBaseRunner implements CommandLineRunner { + + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + private final Provider<PrintWriter> printWriterProvider; + private final CommandLineParser commandLineParser; + + @Inject + CommandLineBaseRunner( + Provider<PrintWriter> printWriterProvider, CommandLineParser commandLineParser) { + this.printWriterProvider = printWriterProvider; + this.commandLineParser = commandLineParser; + } + + /** + * Calls different methods for parsing & validity of arguments, OpenAPI Specification Document + * (v3), and its security features. + * + * <p>Takes a {@code File} to a OpenAPI Specification (v3) document and outputs its Security + * Validity. + * + * @param args Arguments that are passed through command line interface. + */ + @Override + public void run(String[] args) { + PrintWriter printWriter = printWriterProvider.get(); + try { + commandLineParser.parseArguments(args); + } catch (ParameterException e) { + logger.atSevere().withCause(e).log("Unable to parse arguments"); + printWriter.println(e.getLocalizedMessage()); + } finally { + printWriter.close(); + } + } +}
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineClient.java b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineClient.java new file mode 100644 index 0000000..0b5330d --- /dev/null +++ b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineClient.java
@@ -0,0 +1,22 @@ +package com.apigee.security.oas; + +import com.google.inject.Guice; +import com.google.inject.Injector; + +/** + * The main command line interface serves as the entry point that accepts arguments and performs + * argument validation, OpenAPI security validation, and rules enforcement. + */ +public final class CommandLineClient { + + private CommandLineClient() {} + + /** + * Creates an instance of {@link CommandLineRunner} and calls the {@link + * CommandLineRunner#run(String[])} method. + */ + public static void main(String[] args) { + Injector injector = Guice.createInjector(new CommandLineModule()); + injector.getInstance(CommandLineRunner.class).run(args); + } +}
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineInnerModule.java b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineInnerModule.java new file mode 100644 index 0000000..db4e36a --- /dev/null +++ b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineInnerModule.java
@@ -0,0 +1,21 @@ +package com.apigee.security.oas; + +import com.apigee.security.oas.providers.PrintWriterProvider; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import java.io.PrintWriter; + +/** Module with instructions for instantiating instances of {@link CommandLineParser}. */ +public final class CommandLineInnerModule extends AbstractModule { + @Override + protected void configure() { + bind(CommandLineParser.class).to(CommandLineBaseParser.class); + bind(CommandLineRunner.class).to(CommandLineBaseRunner.class); + bind(PrintWriter.class).toProvider(PrintWriterProvider.class); + } + + @Provides + public CommandLineArgs provideCommandLineArgs() { + return new CommandLineArgs(); + } +}
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineModule.java b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineModule.java new file mode 100644 index 0000000..8ec27a0 --- /dev/null +++ b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineModule.java
@@ -0,0 +1,13 @@ +package com.apigee.security.oas; + +import com.google.inject.AbstractModule; + +/** Top level module that imports other Guice modules relied upon. */ +public final class CommandLineModule extends AbstractModule { + + @Override + protected void configure() { + install(new CommandLineInnerModule()); + binder().requireExplicitBindings(); + } +}
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineParser.java b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineParser.java index 2b2bd6c..5b0b46d 100644 --- a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineParser.java +++ b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineParser.java
@@ -1,3 +1,13 @@ package com.apigee.security.oas; -public class CommandLineParser {} +import java.io.File; + +/** Parses and validates {@code args} while providing helper functions. */ +interface CommandLineParser { + + /** Parses and validates {@code args}. */ + void parseArguments(String[] args); + + /** Returns {@code File} parsed through {@link #parseArguments(String[])}. */ + File getOasFile(); +}
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineRunner.java b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineRunner.java new file mode 100644 index 0000000..c1452cf --- /dev/null +++ b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineRunner.java
@@ -0,0 +1,8 @@ +package com.apigee.security.oas; + +/** Performs end to end application logic. */ +interface CommandLineRunner { + + /** Executes end to end application logic. */ + void run(String[] args); +}
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/converters/FileConverter.java b/oas-cli/src/main/java/com/apigee/security/oas/converters/FileConverter.java new file mode 100644 index 0000000..2cc65f5 --- /dev/null +++ b/oas-cli/src/main/java/com/apigee/security/oas/converters/FileConverter.java
@@ -0,0 +1,13 @@ +package com.apigee.security.oas.converters; + +import com.beust.jcommander.IStringConverter; +import java.io.File; + +/** JCommander converter class from {@link String} to {@link File}. */ +public final class FileConverter implements IStringConverter<File> { + + @Override + public File convert(String value) { + return new File(value); + } +}
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/providers/PrintWriterProvider.java b/oas-cli/src/main/java/com/apigee/security/oas/providers/PrintWriterProvider.java new file mode 100644 index 0000000..0e6cb85 --- /dev/null +++ b/oas-cli/src/main/java/com/apigee/security/oas/providers/PrintWriterProvider.java
@@ -0,0 +1,17 @@ +package com.apigee.security.oas.providers; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.inject.Provider; +import java.io.BufferedWriter; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + +/** Provides {@link PrintWriter} with {@code System.out} output stream. */ +public class PrintWriterProvider implements Provider<PrintWriter> { + + @Override + public PrintWriter get() { + return new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out, UTF_8))); + } +}
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/validators/FileValidator.java b/oas-cli/src/main/java/com/apigee/security/oas/validators/FileValidator.java new file mode 100644 index 0000000..07edfde --- /dev/null +++ b/oas-cli/src/main/java/com/apigee/security/oas/validators/FileValidator.java
@@ -0,0 +1,19 @@ +package com.apigee.security.oas.validators; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.ParameterException; +import java.io.File; +import java.io.FileNotFoundException; + +/** Validates whether File exists. */ +public final class FileValidator implements IParameterValidator { + + @Override + public void validate(String name, String value) { + File file = new File(value); + if (!file.exists()) { + throw new ParameterException( + "The file is either not valid or not accessible", new FileNotFoundException()); + } + } +}
diff --git a/oas-cli/src/test/java/com/apigee/security/oas/CommandLineBaseParserTest.java b/oas-cli/src/test/java/com/apigee/security/oas/CommandLineBaseParserTest.java new file mode 100644 index 0000000..af4d0b0 --- /dev/null +++ b/oas-cli/src/test/java/com/apigee/security/oas/CommandLineBaseParserTest.java
@@ -0,0 +1,79 @@ +package com.apigee.security.oas; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.catchThrowable; + +import com.beust.jcommander.ParameterException; +import com.google.common.io.Resources; +import com.google.inject.Guice; +import com.google.inject.Injector; +import java.io.FileNotFoundException; +import java.net.URISyntaxException; +import java.net.URL; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CommandLineBaseParserTest { + private static final String VALID_FILE_NAME = "validOpenApi3DemoSpec.yaml"; + private static final URL VALID_FILE_URL = Resources.getResource(VALID_FILE_NAME); + private static final String INVALID_FILE_URI_PARAM = "demoOpenSpec.yaml"; + + private String validFileUriPath; + + private CommandLineParser commandLineParser; + + /** Creates a CommandLineParser Object and does other necessary initializations. */ + @Before + public void setup() throws URISyntaxException { + validFileUriPath = VALID_FILE_URL.toURI().getPath(); + + Injector injector = Guice.createInjector(new CommandLineModule()); + commandLineParser = injector.getInstance(CommandLineParser.class); + } + + @Test + public void parseArguments_noParameter_throwsParameterExceptionWithMessage() { + String[] testArgs = new String[] {}; + assertThatThrownBy(() -> commandLineParser.parseArguments(testArgs)) + .isInstanceOf(ParameterException.class) + .hasMessageContainingAll("option", "required"); + } + + @Test + public void parseArguments_helpParameterPassed_doesNotThrowException() { + String[] testArgs = new String[] {"--help"}; + + assertThat(catchThrowable(() -> commandLineParser.parseArguments(testArgs))) + .doesNotThrowAnyException(); + } + + @Test + public void parseArguments_validFilePath_shouldNotThrowException() { + String[] testArgs = new String[] {"--file", validFileUriPath}; + + assertThat(catchThrowable(() -> commandLineParser.parseArguments(testArgs))) + .doesNotThrowAnyException(); + } + + @Test + public void parseArguments_invalidFilePath_throwsParameterExceptionWithCauseAndMessage() { + String[] testArgs = new String[] {"--file", INVALID_FILE_URI_PARAM}; + assertThatThrownBy(() -> commandLineParser.parseArguments(testArgs)) + .isInstanceOf(ParameterException.class) + .hasRootCauseInstanceOf(FileNotFoundException.class) + .hasMessageContaining("not valid"); + } + + @Test + public void parseArguments_emptyFilePath_throwsParameterExceptionWithMessage() { + String[] testArgs = new String[] {"--file"}; + + assertThatThrownBy(() -> commandLineParser.parseArguments(testArgs)) + .isInstanceOf(ParameterException.class) + .hasMessageContainingAll("Expected", "value"); + } +}
diff --git a/oas-cli/src/test/java/com/apigee/security/oas/CommandLineBaseRunnerTest.java b/oas-cli/src/test/java/com/apigee/security/oas/CommandLineBaseRunnerTest.java new file mode 100644 index 0000000..2fc0284 --- /dev/null +++ b/oas-cli/src/test/java/com/apigee/security/oas/CommandLineBaseRunnerTest.java
@@ -0,0 +1,56 @@ +package com.apigee.security.oas; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.beust.jcommander.ParameterException; +import com.google.inject.Provider; +import java.io.PrintWriter; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(JUnit4.class) +public class CommandLineBaseRunnerTest { + @Rule public MockitoRule rule = MockitoJUnit.rule(); + + @Mock private Provider<PrintWriter> printWriterProvider; + @Mock private CommandLineParser commandLineParser; + @Mock private PrintWriter printWriter; + @InjectMocks private CommandLineBaseRunner commandLineRunner; + + @Before + public void setup() { + when(printWriterProvider.get()).thenReturn(printWriter); + } + + @Test + public void run_callsParseArguments() { + commandLineRunner.run(new String[] {""}); + + verify(commandLineParser, atLeastOnce()).parseArguments(any(String[].class)); + } + + @Test + public void run_onException_printsExceptionMessageAndClosesPrintWriter() { + String exceptionMessage = "No Parameters received"; + doThrow(new ParameterException(exceptionMessage)) + .when(commandLineParser) + .parseArguments(any(String[].class)); + + commandLineRunner.run(new String[] {""}); + + verify(printWriter, atLeastOnce()).println(contains(exceptionMessage)); + verify(printWriter, atLeastOnce()).close(); + } +}
diff --git a/oas-cli/src/test/java/com/apigee/security/oas/CommandLineModuleTest.java b/oas-cli/src/test/java/com/apigee/security/oas/CommandLineModuleTest.java new file mode 100644 index 0000000..d5cf534 --- /dev/null +++ b/oas-cli/src/test/java/com/apigee/security/oas/CommandLineModuleTest.java
@@ -0,0 +1,41 @@ +package com.apigee.security.oas; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import java.io.PrintWriter; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CommandLineModuleTest { + + private Injector injector; + + @Before + public void setup() { + injector = Guice.createInjector(new CommandLineModule()); + } + + @Test + public void createCommandLineParser_shouldNotThrowException() { + assertThat(catchThrowable(() -> injector.getInstance(CommandLineParser.class))) + .doesNotThrowAnyException(); + } + + @Test + public void createCommandLineArgs_shouldNotThrowException() { + assertThat(catchThrowable(() -> injector.getInstance(CommandLineArgs.class))) + .doesNotThrowAnyException(); + } + + @Test + public void createPrintWriter_shouldNotThrowException() { + assertThat(catchThrowable(() -> injector.getInstance(PrintWriter.class).close())) + .doesNotThrowAnyException(); + } +}
diff --git a/oas-cli/src/test/java/com/apigee/security/oas/CommandLineParserTest.java b/oas-cli/src/test/java/com/apigee/security/oas/CommandLineParserTest.java deleted file mode 100644 index 7727d03..0000000 --- a/oas-cli/src/test/java/com/apigee/security/oas/CommandLineParserTest.java +++ /dev/null
@@ -1,3 +0,0 @@ -package com.apigee.security.oas; - -public class CommandLineParserTest {}
diff --git a/oas-cli/src/test/java/com/apigee/security/oas/FileConverterTest.java b/oas-cli/src/test/java/com/apigee/security/oas/FileConverterTest.java new file mode 100644 index 0000000..d2f789e --- /dev/null +++ b/oas-cli/src/test/java/com/apigee/security/oas/FileConverterTest.java
@@ -0,0 +1,34 @@ +package com.apigee.security.oas; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.apigee.security.oas.converters.FileConverter; +import com.google.common.io.Resources; +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class FileConverterTest { + + private static final String VALID_FILE_NAME = "validOpenApi3DemoSpec.yaml"; + private static final URL VALID_FILE_URL = Resources.getResource(VALID_FILE_NAME); + private String validFileUriPath; + + @Before + public void setup() throws URISyntaxException { + validFileUriPath = VALID_FILE_URL.toURI().getPath(); + } + + @Test + public void fileConverter_validFilePath_returnsExactFile() { + FileConverter fileConverter = new FileConverter(); + File actualFile = new File(validFileUriPath); + + assertThat(fileConverter.convert(validFileUriPath)).isEqualTo(actualFile); + } +}
diff --git a/oas-cli/src/test/java/com/apigee/security/oas/FileValidatorTest.java b/oas-cli/src/test/java/com/apigee/security/oas/FileValidatorTest.java new file mode 100644 index 0000000..2416039 --- /dev/null +++ b/oas-cli/src/test/java/com/apigee/security/oas/FileValidatorTest.java
@@ -0,0 +1,41 @@ +package com.apigee.security.oas; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.catchThrowable; + +import com.apigee.security.oas.validators.FileValidator; +import com.beust.jcommander.ParameterException; +import com.google.common.io.Resources; +import java.net.URISyntaxException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class FileValidatorTest { + private static final String VALID_FILE_NAME = "validOpenApi3DemoSpec.yaml"; + + private String validFileUriParam; + private FileValidator fileValidator; + + @Before + public void setup() throws URISyntaxException { + validFileUriParam = Resources.getResource(VALID_FILE_NAME).toURI().getPath(); + fileValidator = new FileValidator(); + } + + @Test + public void validate_nonExistentFile_throwsParameterExceptionWithMessage() { + assertThatThrownBy(() -> fileValidator.validate("file", "nonExistentFile.yaml")) + .isInstanceOf(ParameterException.class) + .hasMessageContainingAll("file", "not valid"); + } + + @Test + public void validate_existentFile_doesNotThrowException() { + assertThat(catchThrowable(() -> fileValidator.validate("file", validFileUriParam))) + .doesNotThrowAnyException(); + } +}
diff --git a/oas-cli/src/test/java/com/apigee/security/oas/PrintWriterProviderTest.java b/oas-cli/src/test/java/com/apigee/security/oas/PrintWriterProviderTest.java new file mode 100644 index 0000000..5fcc42b --- /dev/null +++ b/oas-cli/src/test/java/com/apigee/security/oas/PrintWriterProviderTest.java
@@ -0,0 +1,20 @@ +package com.apigee.security.oas; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.apigee.security.oas.providers.PrintWriterProvider; +import java.io.PrintWriter; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class PrintWriterProviderTest { + + @Test + public void get_returnsPrintWriter() { + PrintWriterProvider printWriterProvider = new PrintWriterProvider(); + + assertThat(printWriterProvider.get()).isInstanceOf(PrintWriter.class); + } +}
diff --git a/oas-cli/src/test/resources/validOpenApi3DemoSpec.yaml b/oas-cli/src/test/resources/validOpenApi3DemoSpec.yaml new file mode 100644 index 0000000..f87c231 --- /dev/null +++ b/oas-cli/src/test/resources/validOpenApi3DemoSpec.yaml
@@ -0,0 +1,820 @@ +openapi: 3.0.0 +info: + title: OpenAPI Petstore + description: This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. For OAuth2 flow, you may use `user` as both username and password when asked to login. + license: + name: Apache-2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.0 +externalDocs: + description: Find out more about OpenAPI generator + url: https://openapi-generator.tech +tags: +- name: pet + description: Everything about your Pets +- name: store + description: Access to Petstore orders +- name: user + description: Operations about user +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet + operationId: updatePet + requestBody: + $ref: '#/components/requestBodies/Pet' + responses: + 400: + description: Invalid ID supplied + 404: + description: Pet not found + 405: + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + x-accepts: application/json + x-tags: + - tag: pet + x-contentType: application/json + post: + tags: + - pet + summary: Add a new pet to the store + operationId: addPet + requestBody: + $ref: '#/components/requestBodies/Pet' + responses: + 405: + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + x-accepts: application/json + x-tags: + - tag: pet + x-contentType: application/json + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + style: form + explode: false + schema: + type: array + items: + type: string + default: available + enum: + - available + - pending + - sold + responses: + 200: + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + 400: + description: Invalid status value + security: + - petstore_auth: + - write:pets + - read:pets + x-accepts: application/json + x-tags: + - tag: pet + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + style: form + explode: false + schema: + type: array + items: + type: string + responses: + 200: + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + 400: + description: Invalid tag value + deprecated: true + security: + - petstore_auth: + - write:pets + - read:pets + x-accepts: application/json + x-tags: + - tag: pet + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + style: simple + explode: false + schema: + type: integer + format: int64 + responses: + 200: + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + 400: + description: Invalid ID supplied + 404: + description: Pet not found + security: + - api_key: [] + x-accepts: application/json + x-tags: + - tag: pet + post: + tags: + - pet + summary: Updates a pet in the store with form data + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + style: simple + explode: false + schema: + type: integer + format: int64 + requestBody: + content: + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/body' + responses: + 405: + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + x-accepts: application/json + x-tags: + - tag: pet + x-contentType: application/x-www-form-urlencoded + delete: + tags: + - pet + summary: Deletes a pet + operationId: deletePet + parameters: + - name: api_key + in: header + required: false + style: simple + explode: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + style: simple + explode: false + schema: + type: integer + format: int64 + responses: + 400: + description: Invalid pet value + security: + - petstore_auth: + - write:pets + - read:pets + x-accepts: application/json + x-tags: + - tag: pet + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: uploads an image + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + style: simple + explode: false + schema: + type: integer + format: int64 + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/body_1' + responses: + 200: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - write:pets + - read:pets + x-accepts: application/json + x-tags: + - tag: pet + x-contentType: multipart/form-data + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + 200: + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + x-accepts: application/json + x-tags: + - tag: store + /store/order: + post: + tags: + - store + summary: Place an order for a pet + operationId: placeOrder + requestBody: + description: order placed for purchasing the pet + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + required: true + responses: + 200: + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + 400: + description: Invalid Order + x-accepts: application/json + x-tags: + - tag: store + x-contentType: application/json + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID + description: For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + style: simple + explode: false + schema: + maximum: 5 + minimum: 1 + type: integer + format: int64 + responses: + 200: + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + 400: + description: Invalid ID supplied + 404: + description: Order not found + x-accepts: application/json + x-tags: + - tag: store + delete: + tags: + - store + summary: Delete purchase order by ID + description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + style: simple + explode: false + schema: + type: string + responses: + 400: + description: Invalid ID supplied + 404: + description: Order not found + x-accepts: application/json + x-tags: + - tag: store + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + required: true + responses: + default: + description: successful operation + x-accepts: application/json + x-tags: + - tag: user + x-contentType: application/json + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + operationId: createUsersWithArrayInput + requestBody: + $ref: '#/components/requestBodies/UserArray' + responses: + default: + description: successful operation + x-accepts: application/json + x-tags: + - tag: user + x-contentType: application/json + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + operationId: createUsersWithListInput + requestBody: + $ref: '#/components/requestBodies/UserArray' + responses: + default: + description: successful operation + x-accepts: application/json + x-tags: + - tag: user + x-contentType: application/json + /user/login: + get: + tags: + - user + summary: Logs user into the system + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + style: form + explode: true + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: true + style: form + explode: true + schema: + type: string + responses: + 200: + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + style: simple + explode: false + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when toekn expires + style: simple + explode: false + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + 400: + description: Invalid username/password supplied + x-accepts: application/json + x-tags: + - tag: user + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + operationId: logoutUser + responses: + default: + description: successful operation + x-accepts: application/json + x-tags: + - tag: user + /user/{username}: + get: + tags: + - user + summary: Get user by user name + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing. + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + 400: + description: Invalid username supplied + 404: + description: User not found + x-accepts: application/json + x-tags: + - tag: user + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + style: simple + explode: false + schema: + type: string + requestBody: + description: Updated user object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + required: true + responses: + 400: + description: Invalid user supplied + 404: + description: User not found + x-accepts: application/json + x-tags: + - tag: user + x-contentType: application/json + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + style: simple + explode: false + schema: + type: string + responses: + 400: + description: Invalid username supplied + 404: + description: User not found + x-accepts: application/json + x-tags: + - tag: user +components: + schemas: + Order: + title: Pet Order + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + description: An order for a pets from the pet store + example: + petId: 6 + quantity: 1 + id: 0 + shipDate: 2000-01-23T04:56:07.000+00:00 + complete: false + status: placed + xml: + name: Order + Category: + title: Pet category + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + description: A category for a pet + example: + name: name + id: 6 + xml: + name: Category + User: + title: a User + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + description: User Status + format: int32 + description: A User who is purchasing from the pet store + example: + firstName: firstName + lastName: lastName + password: password + userStatus: 6 + phone: phone + id: 0 + email: email + username: username + xml: + name: User + Tag: + title: Pet Tag + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + description: A tag for a pet + example: + name: name + id: 1 + xml: + name: Tag + Pet: + title: a Pet + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + description: A pet for sale in the pet store + example: + photoUrls: + - photoUrls + - photoUrls + name: doggie + id: 0 + category: + name: name + id: 6 + tags: + - name: name + id: 1 + - name: name + id: 1 + status: available + xml: + name: Pet + ApiResponse: + title: An uploaded response + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + description: Describes the result of uploading an image resource + example: + code: 0 + type: type + message: message + body: + type: object + properties: + name: + type: string + description: Updated name of the pet + status: + type: string + description: Updated status of the pet + body_1: + type: object + properties: + additionalMetadata: + type: string + description: Additional data to pass to server + file: + type: string + description: file to upload + format: binary + requestBodies: + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + required: true + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + required: true + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: /api/oauth/dialog + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header \ No newline at end of file
diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml new file mode 100644 index 0000000..28895ee --- /dev/null +++ b/spotbugs-exclude.xml
@@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<FindBugsFilter + xmlns="https://github.com/spotbugs/filter/3.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd"> + <Match> + <!-- Ignore all generated classes --> + <Or> + <Package name="~com\.apigee\.security\.oas\..*" /> + <Package name="com.apigee.security.oas" /> + </Or> + </Match> + <Match> + <Or> + <!-- Does not play well with @Setup methods. --> + <Bug pattern="SE_TRANSIENT_FIELD_NOT_RESTORED" /> + + <!-- This pattern is not really appropriate in Dataflow + or when working with annotation processors. --> + <Bug pattern="UMAC_UNCALLABLE_METHOD_OF_ANONYMOUS_CLASS" /> + + <!-- Relying on errorprone's instead as it's more robust. --> + <Bug pattern="NP_NONNULL_RETURN_VIOLATION" /> + + <!-- This does not play nicely with the google-style builders. --> + <Bug pattern="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE" /> + </Or> + </Match> + <Match> + <!-- Within Dataflow Projects --> + <Package name="~com\.apigee\.security\.oas\..*" /> + <Or> + <!-- Spotbugs thinks Serializable Functions are inner classes and gets very confused. --> + <Bug pattern="SE_INNER_CLASS" /> + <Bug pattern="SIC_INNER_SHOULD_BE_STATIC_ANON" /> + </Or> + </Match> + <Match> + <!-- Ignore matches in test classes except for those relating to tests. --> + <Class name="~.*\.*Test" /> + <Not> + <Bug code="IJU" /> + </Not> + </Match> +</FindBugsFilter> \ No newline at end of file