Repository: swagger-api/swagger-parser
Pull request #1774 (2022-07-29 15:15:50)

Action to validate a definition using parser cli
Action to validate a definition using parser cli


Patch:
diff --git a/.github/actions/action.yml b/.github/actions/action.yml
new file mode 100644
index 00000000..ae316464
--- /dev/null
+++ b/.github/actions/action.yml
@@ -0,0 +1,40 @@
+name: 'Swagger Parser'
+description: 'Parses, validates, dereferences an OpenAPI definition'
+inputs:
+  inputSpec:
+    description: 'path to OpenAPI definition file'
+    required: true
+  options:
+    description: 'parser options'
+    required: false
+  serializationType:
+    description: 'result file serialization type'
+    required: false
+  logsPath:
+    description: 'path to validation result file'
+    required: false
+  parserSpecPath:
+    description: 'output path of the serialized parsed definition'
+    required: false
+  parserVersion:
+    description: 'parser version'
+    required: false
+runs:
+  using: "composite"
+  steps:
+    - id: java-version
+      run: java -version 2>&1 | fgrep -i version | cut -d'"' -f2 | sed -e 's/^1\./1\%/' -e 's/\..*//' -e 's/%/./'
+      shell: bash
+    - name: Build Java
+      if: steps.java-version == null || steps.java-version < 1.8
+      uses: actions/setup-java@v3
+      with:
+        distribution: 'zulu'
+        java-version: '11'
+    - name: Download JAR
+      run: curl -L 'https://repository.sonatype.org/service/local/artifact/maven/content?r=central-proxy&g=io.swagger.parser.v3&a=swagger-parser-cli&e=jar&v=${{ inputs.parserVersion }}' -o swagger-parser-cli.jar
+      shell: bash
+    - id: execute
+      name: Execute Jar
+      run: java -jar swagger-parser-cli.jar -i ${{ inputs.inputSpec }} ${{ inputs.options }} ${{ inputs.serializationType }} -o ${{ inputs.parserSpecPath }} -l ${{ inputs.logsPath }}
+      shell: bash
\ No newline at end of file
diff --git a/.github/workflows/validate-workflow.yml b/.github/workflows/validate-workflow.yml
new file mode 100644
index 00000000..8c35c43e
--- /dev/null
+++ b/.github/workflows/validate-workflow.yml
@@ -0,0 +1,25 @@
+name: SwaggerParserCLI Test
+
+on: [push]
+
+jobs:
+  validate_job:
+    runs-on: ubuntu-latest
+    name: A job to validate a definition
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          ref: action
+      - uses: ./.github/actions/
+        with:
+          inputSpec: '/home/runner/work/swagger-parser/swagger-parser/modules/swagger-parser-cli/src/test/resources/fileWithNoErrorMessages.yaml'
+          options: '-resolve -resolveFully'
+          parserVersion: '2.1.2'
+          serializationType: '-json'
+          logsPath: '/home/runner/work/swagger-parser/swagger-parser/modules/swagger-parser-cli/target/test-classes/parserLogs.yaml'
+          parserSpecPath: '/home/runner/work/swagger-parser/swagger-parser/modules/swagger-parser-cli/target/test-classes/specParsed.json'
+      - run: cat /home/runner/work/swagger-parser/swagger-parser/modules/swagger-parser-cli/target/test-classes/specParsed.json
+        shell: bash
+
+
+
diff --git a/modules/swagger-parser-cli/pom.xml b/modules/swagger-parser-cli/pom.xml
index 5ba74616..00f993ae 100644
--- a/modules/swagger-parser-cli/pom.xml
+++ b/modules/swagger-parser-cli/pom.xml
@@ -76,6 +76,16 @@
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
         </dependency>
+        <dependency>
+            <groupId>net.sourceforge.argparse4j</groupId>
+            <artifactId>argparse4j</artifactId>
+            <version>0.9.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>2.0.0</version>
+        </dependency>
     </dependencies>
 
     <properties>
diff --git a/modules/swagger-parser-cli/src/main/java/io/swagger/v3/parser/SwaggerParser.java b/modules/swagger-parser-cli/src/main/java/io/swagger/v3/parser/SwaggerParser.java
index 8fa2ec40..904a3136 100644
--- a/modules/swagger-parser-cli/src/main/java/io/swagger/v3/parser/SwaggerParser.java
+++ b/modules/swagger-parser-cli/src/main/java/io/swagger/v3/parser/SwaggerParser.java
@@ -1,31 +1,144 @@
 package io.swagger.v3.parser;
 
+import io.swagger.v3.core.util.Json;
+import io.swagger.v3.core.util.Yaml;
+
+import io.swagger.v3.parser.core.models.ParseOptions;
 import io.swagger.v3.parser.core.models.SwaggerParseResult;
+import net.sourceforge.argparse4j.ArgumentParsers;
+import net.sourceforge.argparse4j.impl.Arguments;
+import net.sourceforge.argparse4j.inf.ArgumentParser;
+import net.sourceforge.argparse4j.inf.ArgumentParserException;
+import net.sourceforge.argparse4j.inf.Namespace;
+
+
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
 
+
 public class SwaggerParser {
+
+    public static final String RESOLVE = "resolve";
+    public static final String RESOLVEFULLY = "resolvefully";
+    public static final String FLATTEN = "flatten";
+
     public static void main(String[] args) {
         if (args.length > 0){
-            List<String> messages = readFromLocation(args[0]);
-            if ( messages.size() > 0){
-                messages.forEach(System.out::println);
+            ArgumentParser parser = ArgumentParsers.newFor("swagger-parser").build()
+                    .defaultHelp(true);
+            parser.addArgument("-i")
+                    .dest("i")
+                    .required(true)
+                    .type(String.class)
+                    .help("input file to be parsed");
+            parser.addArgument("-resolve")
+                    .dest(RESOLVE)
+                    .type(Boolean.class)
+                    .action(Arguments.storeTrue())
+                    .setDefault(false)
+                    .help("resolve remote or local references");
+            parser.addArgument("-resolveFully")
+                    .dest(RESOLVEFULLY)
+                    .type(Boolean.class)
+                    .action(Arguments.storeTrue())
+                    .setDefault(false)
+                    .help("");
+            parser.addArgument("-flatten")
+                    .dest(FLATTEN)
+                    .type(Boolean.class)
+                    .action(Arguments.storeTrue())
+                    .setDefault(false)
+                    .help("");
+            parser.addArgument("-o")
+                    .dest("o")
+                    .type(String.class)
+                    .help("output file parsed");
+            parser.addArgument("-l")
+                    .dest("l")
+                    .type(String.class)
+                    .help("output error logs");
+            parser.addArgument("-json")
+                    .dest("json")
+                    .type(String.class)
+                    .help("generate file as JSON");
+            parser.addArgument("-yaml")
+                    .dest("yaml")
+                    .type(String.class)
+                    .help("generate file as YAML");
+            try{
+                readFromLocation(parser.parseArgs(args));
+            }catch (ArgumentParserException e) {
+                parser.handleError(e);
                 System.exit(1);
             }
         }
     }
 
-    public static List<String> readFromLocation(String location) {
+    private static void generateMessagesFile(List<String> messages, Namespace arguments) {
+        if ( messages != null && !messages.isEmpty() && arguments != null && arguments.getString("l") != null){
+            if(arguments.getString("l") != null) {
+                generateParsedFile(arguments, "l", messages.toString());
+            }
+        }
+    }
+
+    public static List<String> readFromLocation(Namespace args) {
         List<String> messages = new ArrayList<>();
+        ParseOptions options;
         try {
-            final SwaggerParseResult result = new OpenAPIV3Parser().readLocation(location, null, null);
+            options = setOptions(args);
+            final SwaggerParseResult result = new OpenAPIV3Parser().readLocation(args.get("i"), null, options);
+            if(args.getString("o") != null) {
+                if (result.getOpenAPI() != null){
+                    String output;
+                    if(args.getString("json") != null){
+                        output = Json.pretty(result.getOpenAPI());
+                    }else if(args.getString("yaml") != null){
+                        output = Yaml.pretty(result.getOpenAPI());
+                    }else{
+                        output= Yaml.pretty(result.getOpenAPI());
+                    }
+                    generateParsedFile(args, "o", output );
+                }
+            }
             if(result.getOpenAPI() == null || !result.getMessages().isEmpty()){
                 messages = result.getMessages();
+                generateMessagesFile(messages, args);
             }
         }catch (Exception e){
             e.printStackTrace();
-            System.exit(1);
         }
         return messages;
     }
+
+    private static void generateParsedFile(Namespace args, String o, String result) {
+        try {
+            if(result != null) {
+                OutputStream out = Files.newOutputStream(Paths.get(args.getString(o)));
+                byte[] specBytes = result.getBytes();
+                out.write(specBytes);
+                out.close();
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    private static ParseOptions setOptions(Namespace parseOptions) {
+        ParseOptions options  = new ParseOptions();
+
+        if (parseOptions.getString(RESOLVE) !=null && parseOptions.getString(RESOLVE).equals("true")) {
+            options.setResolve(true);
+        }
+        if (parseOptions.getString(RESOLVEFULLY) != null && parseOptions.getString(RESOLVEFULLY).equals("true")) {
+            options.setResolveFully(true);
+        }
+        if (parseOptions.getString(FLATTEN) != null && parseOptions.getString(FLATTEN).equals("true")) {
+            options.setFlatten(true);
+        }
+        return options;
+    }
 }
\ No newline at end of file


Test patch:
diff --git a/modules/swagger-parser-cli/src/test/java/SwaggerParserCLITest.java b/modules/swagger-parser-cli/src/test/java/SwaggerParserCLITest.java
index fa68bd95..064d2010 100644
--- a/modules/swagger-parser-cli/src/test/java/SwaggerParserCLITest.java
+++ b/modules/swagger-parser-cli/src/test/java/SwaggerParserCLITest.java
@@ -1,28 +1,120 @@
 import io.swagger.v3.parser.SwaggerParser;
+import net.sourceforge.argparse4j.inf.ArgumentParserException;
+import net.sourceforge.argparse4j.inf.Namespace;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 public class SwaggerParserCLITest {
     @Test
     public void validateOKFromLocationTest(){
-        String []args = new String[1];
-        args[0] = "src/test/resources/fileWithNoErrorMessages.yaml";
-        Assert.assertTrue(SwaggerParser.readFromLocation(args[0]).size() == 0);
+        Map<String,Object> args = new HashMap();
+        args.put("i","src/test/resources/fileWithNoErrorMessages.yaml");
+        Namespace namespace = new Namespace(args);
+        Assert.assertTrue(SwaggerParser.readFromLocation(namespace).size() == 0);
     }
 
     @Test
     public void validateErrorFromLocationTest(){
-        String []args = new String[1];
-        args[0] = "src/test/resources/fileWithValidationErrorMessages.yaml";
-        Assert.assertEquals(SwaggerParser.readFromLocation(args[0]).get(0), "attribute info.version is missing");
-        Assert.assertEquals(SwaggerParser.readFromLocation(args[0]).get(1), "attribute paths.'/cu'(post).responses.200.description is missing");
+        Map<String,Object> args = new HashMap();
+        args.put("i","src/test/resources/fileWithValidationErrorMessages.yaml");
+        Namespace namespace = new Namespace(args);
+        Assert.assertEquals(SwaggerParser.readFromLocation(namespace).toString(), "[attribute info.version is missing, attribute paths.'/cu'(post).responses.200.description is missing]");
+    }
+
+    @Test
+    public void validateErrorTest(){
+        Map<String,Object> args = new HashMap();
+        args.put("i","src/test/resources/fileWithValidationErrorMessages.yaml");
+        args.put("resolve", "true");
+        args.put("resolvefully", "true");
+        args.put("o", "target/test-classes/parsedSpec.yaml");
+        args.put("l", "target/test-classes/errorLogs.yaml");
+        Namespace namespace = new Namespace(args);
+        Assert.assertEquals(SwaggerParser.readFromLocation(namespace).toString(), "[attribute info.version is missing, attribute paths.'/cu'(post).responses.200.description is missing]");
     }
 
     @Test
     public void validateFileNotFoundInLocationTest(){
-        String []args = new String[1];
-        args[0] = "src/test/resources/WrongLocation.yaml";
-        Assert.assertTrue(SwaggerParser.readFromLocation(args[0]).size() == 1);
-        Assert.assertEquals(SwaggerParser.readFromLocation(args[0]).get(0), "Unable to read location `src/test/resources/WrongLocation.yaml`");
+        Map<String,Object> args = new HashMap();
+        args.put("i","src/test/resources/WrongLocation.yaml");
+        args.put("l", "target/test-classes/errorLogs.yaml");
+        Namespace namespace = new Namespace(args);
+        List<String> messages = new ArrayList<>();
+        try {
+            messages   = SwaggerParser.readFromLocation(namespace);
+        }catch (Exception e){
+            Assert.fail("error");
+        }
+        Assert.assertTrue( messages.size() == 1);
+        Assert.assertEquals(messages.toString(), "[Unable to read location `src/test/resources/WrongLocation.yaml`]");
+    }
+
+    @Test
+    public void validateOKFromLocationWithResolveOptionTest(){
+        Map<String,Object> args = new HashMap();
+        args.put("i","src/test/resources/internal-references-in-external-files/main.yaml");
+        args.put("resolve", "true");
+        Namespace namespace = new Namespace(args);
+        Assert.assertTrue(SwaggerParser.readFromLocation(namespace).size() == 0);
+    }
+
+    @Test
+    public void validateOKFromLocationWithResolveFullyOptionTestYaml(){
+        Map<String,Object> args = new HashMap();
+        args.put("i","src/test/resources/internal-references-in-external-files/main.yaml");
+        args.put("resolve", "true");
+        args.put("resolvefully", "true");
+        args.put("yaml", "true");
+        args.put("o", "target/test-classes/parsedSpec.yaml");
+        Namespace namespace = new Namespace(args);
+        Path path = Paths.get("target/test-classes/parsedSpec.yaml");
+
+        Assert.assertTrue(SwaggerParser.readFromLocation(namespace).size() == 0);
+        Assert.assertTrue(Files.exists(path));
+    }
+
+    @Test
+    public void validateOKFromLocationWithResolveFullyOptionTestJson(){
+        Map<String,Object> args = new HashMap();
+        args.put("i","src/test/resources/internal-references-in-external-files/main.yaml");
+        args.put("resolve", "true");
+        args.put("resolvefully", "true");
+        args.put("json", "true");
+        args.put("o", "target/test-classes/parsedSpec.json");
+        Namespace namespace = new Namespace(args);
+        Path path = Paths.get("target/test-classes/parsedSpec.json");
+
+        Assert.assertTrue(SwaggerParser.readFromLocation(namespace).size() == 0);
+        Assert.assertTrue(Files.exists(path));
+    }
+
+    @Test
+    public void validateOKFromLocationWithOnlyResolveFullyOptionTest(){
+        Map<String,Object> args = new HashMap();
+        args.put("i","src/test/resources/internal-references-in-external-files/main.yaml");
+        args.put("resolvefully","true");
+        Namespace namespace = new Namespace(args);
+        Assert.assertTrue(SwaggerParser.readFromLocation(namespace).size() == 0);
+    }
+
+    @Test
+    public void validateOKFromLocationWithFlattenOptionTest() throws ArgumentParserException {
+        Map<String,Object> args = new HashMap();
+        args.put("i","src/test/resources/internal-references-in-external-files/main.yaml");
+        args.put("resolve", "true");
+        args.put("resolvefully", "true");
+        args.put("flatten", "true");
+        args.put("o", "target/test-classes/parsedSpec.yaml");
+        args.put("l", "target/test-classes/errorLogs.yaml");
+        Namespace namespace = new Namespace(args);
+        Assert.assertTrue(SwaggerParser.readFromLocation(namespace).size() == 0);
     }
 }
\ No newline at end of file
diff --git a/modules/swagger-parser-cli/src/test/resources/internal-references-in-external-files/external.yaml b/modules/swagger-parser-cli/src/test/resources/internal-references-in-external-files/external.yaml
new file mode 100644
index 00000000..361c56ff
--- /dev/null
+++ b/modules/swagger-parser-cli/src/test/resources/internal-references-in-external-files/external.yaml
@@ -0,0 +1,27 @@
+common:
+  allOf:
+  - $ref: "#/core"
+  - type: object
+    properties:
+      attribute:
+        type: string
+      direct:
+        $ref: "#/core"
+      referenced:
+        type: array
+        items:
+          $ref: "#/core"
+
+core:
+  type: object
+  properties:
+    coreAttribute:
+      type: string
+    inner:
+      $ref: "#/innerCore"
+
+innerCore:
+  type: object
+  properties:
+    innerCoreAttribute:
+      type: string
diff --git a/modules/swagger-parser-cli/src/test/resources/internal-references-in-external-files/main.yaml b/modules/swagger-parser-cli/src/test/resources/internal-references-in-external-files/main.yaml
new file mode 100644
index 00000000..95e501c6
--- /dev/null
+++ b/modules/swagger-parser-cli/src/test/resources/internal-references-in-external-files/main.yaml
@@ -0,0 +1,29 @@
+openapi: 3.0.0
+info:
+  version: 0.0.1
+  title: API
+paths:
+  /:
+    post:
+      summary: Create
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/create'
+      responses:
+        '200':
+          description: Successful response
+components:
+  schemas:
+    create:
+      type: object
+      properties:
+        attribute:
+          type: string
+        direct:
+          $ref: "./external.yaml#/common"
+        referenced:
+          type: array
+          items:
+            $ref: "./external.yaml#/common"