Skip to content

Feature Request (and design proposal): swagger-parser-cli to implement conversion of v2 to v3 #2145

Open
@alexanderankin

Description

Unless there is a better or already existing home for a ready-to-use out-of-the-box cli for converting v2 to v3 openapi specs, I think changing the logic in io.swagger.v3.parser.SwaggerParser#readFromLocation in modules/swagger-parser-cli/src/main/java/io/swagger/v3/parser/SwaggerParser.java, which currently uses this pattern:

final SwaggerParseResult result = new OpenAPIV3Parser().readLocation(args.get(INPUT_FILE), null, options);

to use the pattern from io.swagger.parser.OpenAPIParser#readLocation (in modules/swagger-parser/src/main/java/io/swagger/parser/OpenAPIParser.java):

        for(SwaggerParserExtension extension : OpenAPIV3Parser.getExtensions()) {
            output = extension.readLocation(url, auth, options);
            if(output != null && output.getOpenAPI() != null) {
                return output;
            }
        }

should do the trick.

it would also require adding the v2-converter module as a dependency which includes this class:

//io.swagger.v3.parser.converter.SwaggerConverter
public class SwaggerConverter implements SwaggerParserExtension {
    // ...
}
preview of implementation:
diff --git a/modules/swagger-parser-cli/pom.xml b/modules/swagger-parser-cli/pom.xml
index 942c53ee..0e66e8c6 100644
--- a/modules/swagger-parser-cli/pom.xml
+++ b/modules/swagger-parser-cli/pom.xml
@@ -88,6 +88,16 @@
     </build>
 
     <dependencies>
+        <dependency>
+            <groupId>io.swagger.parser.v3</groupId>
+            <artifactId>swagger-parser</artifactId>
+            <version>2.1.24</version>
+        </dependency>
+        <dependency>
+            <groupId>io.swagger.parser.v3</groupId>
+            <artifactId>swagger-parser-v2-converter</artifactId>
+            <version>2.1.24</version>
+        </dependency>
         <dependency>
             <groupId>io.swagger.parser.v3</groupId>
             <artifactId>swagger-parser-v3</artifactId>
@@ -109,7 +119,7 @@
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-simple</artifactId>
             <version>${slf4j-version}</version>
-            <scope>test</scope>
+            <!--<scope>test</scope>-->
         </dependency>
         <dependency>
             <groupId>org.testng</groupId>
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 4eb9a4a1..aa973a0d 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,5 +1,6 @@
 package io.swagger.v3.parser;
 
+import io.swagger.parser.OpenAPIParser;
 import io.swagger.v3.core.util.Json;
 import io.swagger.v3.core.util.Yaml;
 
@@ -100,7 +101,20 @@ public class SwaggerParser {
         ParseOptions options;
         try {
             options = setOptions(args);
-            final SwaggerParseResult result = new OpenAPIV3Parser().readLocation(args.get(INPUT_FILE), null, options);
+            SwaggerParseResult result;
+
+            // new code for parsing the result trying all the extension versions
+            try {
+                result = new OpenAPIParser().readLocation(args.get(INPUT_FILE), null, options);
+            } catch (Exception e) {
+                result = SwaggerParseResult.ofError(e.getMessage());
+            }
+
+            // in case of error, provide familiar error message
+            if (result.getOpenAPI() == null) {
+                result = new OpenAPIV3Parser().readLocation(args.get(INPUT_FILE), null, options);
+            }
+
             if(args.getString(OUTPUT_FILE) != null) {
                 if (result.getOpenAPI() != null){
                     String output;
@@ -151,4 +165,4 @@ public class SwaggerParser {
         }
         return options;
     }
-}
\ No newline at end of file
+}
diff --git a/modules/swagger-parser-cli/src/test/java/SwaggerParserCLITest.java b/modules/swagger-parser-cli/src/test/java/SwaggerParserCLITest.java
index 623c79d1..faa414e2 100644
--- a/modules/swagger-parser-cli/src/test/java/SwaggerParserCLITest.java
+++ b/modules/swagger-parser-cli/src/test/java/SwaggerParserCLITest.java
@@ -145,4 +145,18 @@ public class SwaggerParserCLITest {
         SwaggerParser.main(args);
         Assert.assertTrue(Files.exists(path));
     }
-}
\ No newline at end of file
+
+    @Test
+    public void validateOKWithSwaggerFormat(){
+        String[] args = new String[5];
+        args[0]="-i=src/test/resources/fileWithSwagger.yaml";
+        args[1]="-resolve";
+        args[2]="-resolveFully";
+        args[3]="-json";
+        args[4]="-o=target/test-classes/fileWithSwagger.json";
+
+        Path path = Paths.get(args[4].substring(3));
+        SwaggerParser.main(args);
+        Assert.assertTrue(Files.exists(path));
+    }
+}
diff --git a/modules/swagger-parser-cli/src/test/resources/fileWithSwagger.yaml b/modules/swagger-parser-cli/src/test/resources/fileWithSwagger.yaml
new file mode 100644
index 00000000..5eab5e79
--- /dev/null
+++ b/modules/swagger-parser-cli/src/test/resources/fileWithSwagger.yaml
@@ -0,0 +1,711 @@
+"swagger": "2.0"
+"info":
+  "description": "This is a sample server Petstore server.  You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).  For this sample, you can use the api key `special-key` to test the authorization filters."
+  "version": "1.0.7"
+  "title": "Swagger Petstore"
+  "termsOfService": "http://swagger.io/terms/"
+  "contact":
+    "email": "[email protected]"
+  "license":
+    "name": "Apache 2.0"
+    "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+"host": "petstore.swagger.io"
+"basePath": "/v2"
+"tags":
+  - "name": "pet"
+    "description": "Everything about your Pets"
+    "externalDocs":
+      "description": "Find out more"
+      "url": "http://swagger.io"
+  - "name": "store"
+    "description": "Access to Petstore orders"
+  - "name": "user"
+    "description": "Operations about user"
+    "externalDocs":
+      "description": "Find out more about our store"
+      "url": "http://swagger.io"
+"schemes":
+  - "https"
+  - "http"
+"paths":
+  "/pet/{petId}/uploadImage":
+    "post":
+      "tags":
+        - "pet"
+      "summary": "uploads an image"
+      "description": ""
+      "operationId": "uploadFile"
+      "consumes":
+        - "multipart/form-data"
+      "produces":
+        - "application/json"
+      "parameters":
+        - "name": "petId"
+          "in": "path"
+          "description": "ID of pet to update"
+          "required": true
+          "type": "integer"
+          "format": "int64"
+        - "name": "additionalMetadata"
+          "in": "formData"
+          "description": "Additional data to pass to server"
+          "required": false
+          "type": "string"
+        - "name": "file"
+          "in": "formData"
+          "description": "file to upload"
+          "required": false
+          "type": "file"
+      "responses":
+        "200":
+          "description": "successful operation"
+          "schema":
+            "$ref": "#/definitions/ApiResponse"
+      "security":
+        - "petstore_auth":
+            - "write:pets"
+            - "read:pets"
+  "/pet":
+    "post":
+      "tags":
+        - "pet"
+      "summary": "Add a new pet to the store"
+      "description": ""
+      "operationId": "addPet"
+      "consumes":
+        - "application/json"
+        - "application/xml"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "in": "body"
+          "name": "body"
+          "description": "Pet object that needs to be added to the store"
+          "required": true
+          "schema":
+            "$ref": "#/definitions/Pet"
+      "responses":
+        "405":
+          "description": "Invalid input"
+      "security":
+        - "petstore_auth":
+            - "write:pets"
+            - "read:pets"
+    "put":
+      "tags":
+        - "pet"
+      "summary": "Update an existing pet"
+      "description": ""
+      "operationId": "updatePet"
+      "consumes":
+        - "application/json"
+        - "application/xml"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "in": "body"
+          "name": "body"
+          "description": "Pet object that needs to be added to the store"
+          "required": true
+          "schema":
+            "$ref": "#/definitions/Pet"
+      "responses":
+        "400":
+          "description": "Invalid ID supplied"
+        "404":
+          "description": "Pet not found"
+        "405":
+          "description": "Validation exception"
+      "security":
+        - "petstore_auth":
+            - "write:pets"
+            - "read:pets"
+  "/pet/findByStatus":
+    "get":
+      "tags":
+        - "pet"
+      "summary": "Finds Pets by status"
+      "description": "Multiple status values can be provided with comma separated strings"
+      "operationId": "findPetsByStatus"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "name": "status"
+          "in": "query"
+          "description": "Status values that need to be considered for filter"
+          "required": true
+          "type": "array"
+          "items":
+            "type": "string"
+            "enum":
+              - "available"
+              - "pending"
+              - "sold"
+            "default": "available"
+          "collectionFormat": "multi"
+      "responses":
+        "200":
+          "description": "successful operation"
+          "schema":
+            "type": "array"
+            "items":
+              "$ref": "#/definitions/Pet"
+        "400":
+          "description": "Invalid status value"
+      "security":
+        - "petstore_auth":
+            - "write:pets"
+            - "read:pets"
+  "/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"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "name": "tags"
+          "in": "query"
+          "description": "Tags to filter by"
+          "required": true
+          "type": "array"
+          "items":
+            "type": "string"
+          "collectionFormat": "multi"
+      "responses":
+        "200":
+          "description": "successful operation"
+          "schema":
+            "type": "array"
+            "items":
+              "$ref": "#/definitions/Pet"
+        "400":
+          "description": "Invalid tag value"
+      "security":
+        - "petstore_auth":
+            - "write:pets"
+            - "read:pets"
+      "deprecated": true
+  "/pet/{petId}":
+    "get":
+      "tags":
+        - "pet"
+      "summary": "Find pet by ID"
+      "description": "Returns a single pet"
+      "operationId": "getPetById"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "name": "petId"
+          "in": "path"
+          "description": "ID of pet to return"
+          "required": true
+          "type": "integer"
+          "format": "int64"
+      "responses":
+        "200":
+          "description": "successful operation"
+          "schema":
+            "$ref": "#/definitions/Pet"
+        "400":
+          "description": "Invalid ID supplied"
+        "404":
+          "description": "Pet not found"
+      "security":
+        - "api_key": [ ]
+    "post":
+      "tags":
+        - "pet"
+      "summary": "Updates a pet in the store with form data"
+      "description": ""
+      "operationId": "updatePetWithForm"
+      "consumes":
+        - "application/x-www-form-urlencoded"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "name": "petId"
+          "in": "path"
+          "description": "ID of pet that needs to be updated"
+          "required": true
+          "type": "integer"
+          "format": "int64"
+        - "name": "name"
+          "in": "formData"
+          "description": "Updated name of the pet"
+          "required": false
+          "type": "string"
+        - "name": "status"
+          "in": "formData"
+          "description": "Updated status of the pet"
+          "required": false
+          "type": "string"
+      "responses":
+        "405":
+          "description": "Invalid input"
+      "security":
+        - "petstore_auth":
+            - "write:pets"
+            - "read:pets"
+    "delete":
+      "tags":
+        - "pet"
+      "summary": "Deletes a pet"
+      "description": ""
+      "operationId": "deletePet"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "name": "api_key"
+          "in": "header"
+          "required": false
+          "type": "string"
+        - "name": "petId"
+          "in": "path"
+          "description": "Pet id to delete"
+          "required": true
+          "type": "integer"
+          "format": "int64"
+      "responses":
+        "400":
+          "description": "Invalid ID supplied"
+        "404":
+          "description": "Pet not found"
+      "security":
+        - "petstore_auth":
+            - "write:pets"
+            - "read:pets"
+  "/store/inventory":
+    "get":
+      "tags":
+        - "store"
+      "summary": "Returns pet inventories by status"
+      "description": "Returns a map of status codes to quantities"
+      "operationId": "getInventory"
+      "produces":
+        - "application/json"
+      "parameters": [ ]
+      "responses":
+        "200":
+          "description": "successful operation"
+          "schema":
+            "type": "object"
+            "additionalProperties":
+              "type": "integer"
+              "format": "int32"
+      "security":
+        - "api_key": [ ]
+  "/store/order":
+    "post":
+      "tags":
+        - "store"
+      "summary": "Place an order for a pet"
+      "description": ""
+      "operationId": "placeOrder"
+      "consumes":
+        - "application/json"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "in": "body"
+          "name": "body"
+          "description": "order placed for purchasing the pet"
+          "required": true
+          "schema":
+            "$ref": "#/definitions/Order"
+      "responses":
+        "200":
+          "description": "successful operation"
+          "schema":
+            "$ref": "#/definitions/Order"
+        "400":
+          "description": "Invalid Order"
+  "/store/order/{orderId}":
+    "get":
+      "tags":
+        - "store"
+      "summary": "Find purchase order by ID"
+      "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions"
+      "operationId": "getOrderById"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "name": "orderId"
+          "in": "path"
+          "description": "ID of pet that needs to be fetched"
+          "required": true
+          "type": "integer"
+          "maximum": 10
+          "minimum": 1
+          "format": "int64"
+      "responses":
+        "200":
+          "description": "successful operation"
+          "schema":
+            "$ref": "#/definitions/Order"
+        "400":
+          "description": "Invalid ID supplied"
+        "404":
+          "description": "Order not found"
+    "delete":
+      "tags":
+        - "store"
+      "summary": "Delete purchase order by ID"
+      "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors"
+      "operationId": "deleteOrder"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "name": "orderId"
+          "in": "path"
+          "description": "ID of the order that needs to be deleted"
+          "required": true
+          "type": "integer"
+          "minimum": 1
+          "format": "int64"
+      "responses":
+        "400":
+          "description": "Invalid ID supplied"
+        "404":
+          "description": "Order not found"
+  "/user/createWithList":
+    "post":
+      "tags":
+        - "user"
+      "summary": "Creates list of users with given input array"
+      "description": ""
+      "operationId": "createUsersWithListInput"
+      "consumes":
+        - "application/json"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "in": "body"
+          "name": "body"
+          "description": "List of user object"
+          "required": true
+          "schema":
+            "type": "array"
+            "items":
+              "$ref": "#/definitions/User"
+      "responses":
+        "default":
+          "description": "successful operation"
+  "/user/{username}":
+    "get":
+      "tags":
+        - "user"
+      "summary": "Get user by user name"
+      "description": ""
+      "operationId": "getUserByName"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "name": "username"
+          "in": "path"
+          "description": "The name that needs to be fetched. Use user1 for testing. "
+          "required": true
+          "type": "string"
+      "responses":
+        "200":
+          "description": "successful operation"
+          "schema":
+            "$ref": "#/definitions/User"
+        "400":
+          "description": "Invalid username supplied"
+        "404":
+          "description": "User not found"
+    "put":
+      "tags":
+        - "user"
+      "summary": "Updated user"
+      "description": "This can only be done by the logged in user."
+      "operationId": "updateUser"
+      "consumes":
+        - "application/json"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "name": "username"
+          "in": "path"
+          "description": "name that need to be updated"
+          "required": true
+          "type": "string"
+        - "in": "body"
+          "name": "body"
+          "description": "Updated user object"
+          "required": true
+          "schema":
+            "$ref": "#/definitions/User"
+      "responses":
+        "400":
+          "description": "Invalid user supplied"
+        "404":
+          "description": "User not found"
+    "delete":
+      "tags":
+        - "user"
+      "summary": "Delete user"
+      "description": "This can only be done by the logged in user."
+      "operationId": "deleteUser"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "name": "username"
+          "in": "path"
+          "description": "The name that needs to be deleted"
+          "required": true
+          "type": "string"
+      "responses":
+        "400":
+          "description": "Invalid username supplied"
+        "404":
+          "description": "User not found"
+  "/user/login":
+    "get":
+      "tags":
+        - "user"
+      "summary": "Logs user into the system"
+      "description": ""
+      "operationId": "loginUser"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "name": "username"
+          "in": "query"
+          "description": "The user name for login"
+          "required": true
+          "type": "string"
+        - "name": "password"
+          "in": "query"
+          "description": "The password for login in clear text"
+          "required": true
+          "type": "string"
+      "responses":
+        "200":
+          "description": "successful operation"
+          "headers":
+            "X-Expires-After":
+              "type": "string"
+              "format": "date-time"
+              "description": "date in UTC when token expires"
+            "X-Rate-Limit":
+              "type": "integer"
+              "format": "int32"
+              "description": "calls per hour allowed by the user"
+          "schema":
+            "type": "string"
+        "400":
+          "description": "Invalid username/password supplied"
+  "/user/logout":
+    "get":
+      "tags":
+        - "user"
+      "summary": "Logs out current logged in user session"
+      "description": ""
+      "operationId": "logoutUser"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters": [ ]
+      "responses":
+        "default":
+          "description": "successful operation"
+  "/user/createWithArray":
+    "post":
+      "tags":
+        - "user"
+      "summary": "Creates list of users with given input array"
+      "description": ""
+      "operationId": "createUsersWithArrayInput"
+      "consumes":
+        - "application/json"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "in": "body"
+          "name": "body"
+          "description": "List of user object"
+          "required": true
+          "schema":
+            "type": "array"
+            "items":
+              "$ref": "#/definitions/User"
+      "responses":
+        "default":
+          "description": "successful operation"
+  "/user":
+    "post":
+      "tags":
+        - "user"
+      "summary": "Create user"
+      "description": "This can only be done by the logged in user."
+      "operationId": "createUser"
+      "consumes":
+        - "application/json"
+      "produces":
+        - "application/json"
+        - "application/xml"
+      "parameters":
+        - "in": "body"
+          "name": "body"
+          "description": "Created user object"
+          "required": true
+          "schema":
+            "$ref": "#/definitions/User"
+      "responses":
+        "default":
+          "description": "successful operation"
+"securityDefinitions":
+  "api_key":
+    "type": "apiKey"
+    "name": "api_key"
+    "in": "header"
+  "petstore_auth":
+    "type": "oauth2"
+    "authorizationUrl": "https://petstore.swagger.io/oauth/authorize"
+    "flow": "implicit"
+    "scopes":
+      "read:pets": "read your pets"
+      "write:pets": "modify pets in your account"
+"definitions":
+  "ApiResponse":
+    "type": "object"
+    "properties":
+      "code":
+        "type": "integer"
+        "format": "int32"
+      "type":
+        "type": "string"
+      "message":
+        "type": "string"
+  "Category":
+    "type": "object"
+    "properties":
+      "id":
+        "type": "integer"
+        "format": "int64"
+      "name":
+        "type": "string"
+    "xml":
+      "name": "Category"
+  "Pet":
+    "type": "object"
+    "required":
+      - "name"
+      - "photoUrls"
+    "properties":
+      "id":
+        "type": "integer"
+        "format": "int64"
+      "category":
+        "$ref": "#/definitions/Category"
+      "name":
+        "type": "string"
+        "example": "doggie"
+      "photoUrls":
+        "type": "array"
+        "xml":
+          "wrapped": true
+        "items":
+          "type": "string"
+          "xml":
+            "name": "photoUrl"
+      "tags":
+        "type": "array"
+        "xml":
+          "wrapped": true
+        "items":
+          "xml":
+            "name": "tag"
+          "$ref": "#/definitions/Tag"
+      "status":
+        "type": "string"
+        "description": "pet status in the store"
+        "enum":
+          - "available"
+          - "pending"
+          - "sold"
+    "xml":
+      "name": "Pet"
+  "Tag":
+    "type": "object"
+    "properties":
+      "id":
+        "type": "integer"
+        "format": "int64"
+      "name":
+        "type": "string"
+    "xml":
+      "name": "Tag"
+  "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"
+    "xml":
+      "name": "Order"
+  "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"
+        "format": "int32"
+        "description": "User Status"
+    "xml":
+      "name": "User"
+"externalDocs":
+  "description": "Find out more about Swagger"
+  "url": "http://swagger.io"

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions