Repository: swagger-api/swagger-parser
Pull request #2127 (2024-10-16 08:25:11)

Enhancement/parser resolve responses flag
Due to unexpected results during parsing OAS with options - resolve and flatten set to true, we decided to create resolveResponses flag, from now responses will not be fully resolved by default with option resolve - true.
If anyone wants to keep previous behaviour needs to use option `setResolveResponses(true)`


Patch:
diff --git a/modules/swagger-parser-core/src/main/java/io/swagger/v3/parser/core/models/ParseOptions.java b/modules/swagger-parser-core/src/main/java/io/swagger/v3/parser/core/models/ParseOptions.java
index adc2ddb3..60a587af 100644
--- a/modules/swagger-parser-core/src/main/java/io/swagger/v3/parser/core/models/ParseOptions.java
+++ b/modules/swagger-parser-core/src/main/java/io/swagger/v3/parser/core/models/ParseOptions.java
@@ -15,7 +15,7 @@ public class ParseOptions {
     private boolean validateInternalRefs = true;
     private boolean legacyYamlDeserialization = false;
     private boolean resolveRequestBody = false;
-
+    private boolean resolveResponses = false;
     private boolean oaiAuthor;
     private boolean inferSchemaType = true;
     private boolean safelyResolveURL;
@@ -161,4 +161,12 @@ public class ParseOptions {
     public void setRemoteRefBlockList(List<String> remoteRefBlockList) {
         this.remoteRefBlockList = remoteRefBlockList;
     }
+
+    public boolean isResolveResponses() {
+        return resolveResponses;
+    }
+
+    public void setResolveResponses(boolean resolveResponses) {
+        this.resolveResponses = resolveResponses;
+    }
 }
diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/OperationProcessor.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/OperationProcessor.java
index 2662e3ca..c7923755 100644
--- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/OperationProcessor.java
+++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/OperationProcessor.java
@@ -70,8 +70,8 @@ public class OperationProcessor {
             for (String responseCode : responses.keySet()) {
                 ApiResponse response = responses.get(responseCode);
                 if(response != null) {
-                    //This part allows parser to put response inline without the resolveFully option set to true
-                    if (response.get$ref() != null) {
+                    //This part allows parser to put response inline when resolveResponses = true
+                    if (response.get$ref() != null && cache != null && cache.getParseOptions() != null && cache.getParseOptions().isResolveResponses()) {
 
                         responseProcessor.processResponse(response);
 


Test patch:
diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/NetworkReferenceTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/NetworkReferenceTest.java
index 081dc264..96ed91ba 100644
--- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/NetworkReferenceTest.java
+++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/NetworkReferenceTest.java
@@ -272,6 +272,7 @@ public class NetworkReferenceTest {
         OpenAPIV3Parser parser = new OpenAPIV3Parser();
         ParseOptions options = new ParseOptions();
         options.setResolve(true);
+        options.setResolveResponses(true);
         SwaggerParseResult result = parser.readLocation("http://remote1/resources/swagger.yaml", auths, options);
 
         OpenAPI swagger = result.getOpenAPI();
diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverTest.java
index 5820e237..36117dd2 100644
--- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverTest.java
+++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverTest.java
@@ -766,8 +766,9 @@ public class OpenAPIResolverTest {
 
         ParseOptions options = new ParseOptions();
         options.setResolve(true);
-        // Added this parseOption to make requestBody inline in the operation resolve processing flow.
+        // Added this parseOption to make requestBody/response inline in the operation resolve processing flow.
         options.setResolveRequestBody(true);
+        options.setResolveResponses(true);
 
         OpenAPI openAPI = new OpenAPIV3Parser().readLocation(path, null, options).getOpenAPI();
 
@@ -776,7 +777,7 @@ public class OpenAPIResolverTest {
         assertTrue(openAPI.getPaths().get("/resource").getPost().getRequestBody().getContent() != null);
         assertTrue(openAPI.getPaths().get("/resource").getPost().getRequestBody().getContent().get("application/json").getSchema() instanceof ObjectSchema);
 
-        // Responses are already by default made inline in case referenced.
+        // Responses should be inline
         assertTrue(openAPI.getPaths().get("/resource").getPost().getResponses().get("200").get$ref() == null);
         assertTrue(openAPI.getPaths().get("/resource").getPost().getResponses().get("200").getContent() != null);
         assertTrue(openAPI.getPaths().get("/resource").getPost().getResponses().get("200").getContent().get("application/json").getSchema() instanceof ObjectSchema);
@@ -1285,6 +1286,8 @@ public class OpenAPIResolverTest {
     @Test(description = "resolve top-level responses")
     public void testSharedResponses() {
         final OpenAPI swagger = new OpenAPI();
+        ParseOptions parseOptions = new ParseOptions();
+        parseOptions.setResolveResponses(true);
         List<Parameter> parameters = new ArrayList<>();
         parameters.add(0,new Parameter().$ref("username"));
         swagger.path("/fun", new PathItem()
@@ -1294,7 +1297,7 @@ public class OpenAPIResolverTest {
 
         swagger.components(new Components().addResponses("foo", new ApiResponse().description("ok!")));
 
-        final OpenAPI resolved = new OpenAPIResolver(swagger, null).resolve();
+        final OpenAPI resolved = new OpenAPIResolver(swagger, null, null, null, parseOptions).resolve();
         ApiResponse response = resolved.getPaths().get("/fun").getGet().getResponses().get("200");
         assertTrue(response.getDescription().equals("ok!"));
         assertTrue(response instanceof ApiResponse);
diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java
index 997f1c23..e7e4a555 100644
--- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java
+++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java
@@ -170,6 +170,7 @@ public class OpenAPIV3ParserTest {
     public void testParametersAndResponsesAsNumbers() throws Exception {
         ParseOptions options = new ParseOptions();
         options.setResolve(true);
+        options.setResolveResponses(true);
         SwaggerParseResult result = new OpenAPIV3Parser().readLocation("src/test/resources/parametersAsNumbers/swagger.yaml", null, options);
 
         Assert.assertNotNull(result);
@@ -1214,6 +1215,7 @@ public class OpenAPIV3ParserTest {
     public void testIssue834() {
         ParseOptions options = new ParseOptions();
         options.setResolve(true);
+        options.setResolveResponses(true);
         SwaggerParseResult result = new OpenAPIV3Parser().readLocation("issue-834/index.yaml", null, options);
         assertNotNull(result.getOpenAPI());
 
@@ -1247,8 +1249,8 @@ public class OpenAPIV3ParserTest {
     public void testIssue811() {
         ParseOptions options = new ParseOptions();
         options.setResolve(true);
+        options.setResolveResponses(true);
         final OpenAPI openAPI = new OpenAPIV3Parser().readLocation("oapi-reference-test/index.yaml", null, options).getOpenAPI();
-
         Assert.assertNotNull(openAPI);
         Assert.assertEquals(openAPI.getPaths().get("/").getGet().getResponses().get("200").getContent().get("application/json").getSchema().get$ref(),"#/components/schemas/schema-with-reference");
     }
@@ -1940,7 +1942,9 @@ public class OpenAPIV3ParserTest {
         OpenAPIV3Parser parser = new OpenAPIV3Parser();
         ParseOptions options = new ParseOptions();
         options.setResolve(true);
+        options.setResolveResponses(true);
         SwaggerParseResult readResult = parser.readLocation("src/test/resources/relative-issue/api.yaml", null, options);
+        Yaml.prettyPrint(readResult.getOpenAPI());
         Assert.assertEquals(readResult.getOpenAPI().getPaths().get("/scans").getGet().getResponses().get("500").getContent().get("application/json").getSchema().get$ref(), "#/components/schemas/ErrorMessage");
     }
 
@@ -1949,6 +1953,7 @@ public class OpenAPIV3ParserTest {
         OpenAPIV3Parser parser = new OpenAPIV3Parser();
         ParseOptions options = new ParseOptions();
         options.setResolve(true);
+        options.setResolveResponses(true);
         SwaggerParseResult readResult = parser.readLocation("src/test/resources/codegen-remote-responses/openapi.yaml", null, options);
         Assert.assertEquals(readResult.getOpenAPI().getPaths().get("/pet/findByTags").getGet().getResponses().get("default").getContent().get("application/json").getSchema().get$ref(), "#/components/schemas/ErrorModel");
     }
@@ -1957,6 +1962,7 @@ public class OpenAPIV3ParserTest {
         OpenAPIV3Parser parser = new OpenAPIV3Parser();
         ParseOptions options = new ParseOptions();
         options.setResolve(true);
+        options.setResolveResponses(true);
         SwaggerParseResult readResult = parser.readLocation(location, null, options);
 
         if (readResult.getMessages().size() > 0) {
@@ -2769,6 +2775,7 @@ public class OpenAPIV3ParserTest {
         ParseOptions options = new ParseOptions();
         options.setResolve(true);
         options.setResolveFully(true);
+        options.setResolveResponses(true);
 
         OpenAPI openAPI = new OpenAPIV3Parser().readLocation(path, auths, options).getOpenAPI();
 
@@ -3298,4 +3305,51 @@ public class OpenAPIV3ParserTest {
         assertEquals(openAPI.getComponents().getSchemas().get("PetCreate").getRequired().size(), 1);
         assertEquals(openAPI.getComponents().getSchemas().get("PetCreate").getProperties().size(), 2);
     }
+
+    @Test(description = "responses should be inline")
+    public void testFullyResolveResponses() {
+        ParseOptions options = new ParseOptions();
+        options.setResolve(true);
+        options.setResolveResponses(true);
+        OpenAPIV3Parser openApiParser = new OpenAPIV3Parser();
+        SwaggerParseResult parseResult = openApiParser.readLocation("resolve-responses-test.yaml", null, options);
+        OpenAPI openAPI = parseResult.getOpenAPI();
+        assertNull(openAPI.getPaths().get("/users").getGet().getResponses().get("400").get$ref());
+        assertNull(openAPI.getPaths().get("/users").getPost().getResponses().get("400").get$ref());
+        assertNull(openAPI.getPaths().get("/users").getPost().getResponses().get("422").get$ref());
+        assertNull(openAPI.getPaths().get("/users/{userId}").getGet().getResponses().get("404").get$ref());
+        assertNull(openAPI.getPaths().get("/users/{userId}").getPut().getResponses().get("400").get$ref());
+        assertNull(openAPI.getPaths().get("/users/{userId}").getPut().getResponses().get("404").get$ref());
+        assertNull(openAPI.getPaths().get("/users/{userId}").getDelete().getResponses().get("400").get$ref());
+        assertNull(openAPI.getPaths().get("/users/{userId}").getDelete().getResponses().get("404").get$ref());
+    }
+
+    @Test(description = "responses should not be inline")
+    public void testResolveResponsesRef() {
+        ParseOptions options = new ParseOptions();
+        options.setResolve(true);
+        OpenAPIV3Parser openApiParser = new OpenAPIV3Parser();
+        SwaggerParseResult parseResult = openApiParser.readLocation("resolve-responses-test.yaml", null, options);
+        OpenAPI openAPI = parseResult.getOpenAPI();
+        assertEquals(openAPI.getPaths().get("/users").getGet().getResponses().get("400").get$ref(), "#/components/responses/BadRequest");
+        assertEquals(openAPI.getPaths().get("/users").getPost().getResponses().get("400").get$ref(), "#/components/responses/BadRequest");
+        assertEquals(openAPI.getPaths().get("/users").getPost().getResponses().get("422").get$ref(), "#/components/responses/UnprocessableEntity");
+        assertEquals(openAPI.getPaths().get("/users/{userId}").getGet().getResponses().get("404").get$ref(), "#/components/responses/NotFound");
+        assertEquals(openAPI.getPaths().get("/users/{userId}").getPut().getResponses().get("400").get$ref(), "#/components/responses/BadRequest");
+        assertEquals(openAPI.getPaths().get("/users/{userId}").getPut().getResponses().get("404").get$ref(), "#/components/responses/NotFound");
+        assertEquals(openAPI.getPaths().get("/users/{userId}").getDelete().getResponses().get("400").get$ref(), "#/components/responses/BadRequest");
+        assertEquals(openAPI.getPaths().get("/users/{userId}").getDelete().getResponses().get("404").get$ref(), "#/components/responses/NotFound");
+    }
+
+    @Test
+    public void testResolveOASWithFlatten(){
+        ParseOptions options = new ParseOptions();
+        options.setResolve(true);
+        options.setFlatten(true);
+        OpenAPIV3Parser openApiParser = new OpenAPIV3Parser();
+        SwaggerParseResult parseResult = openApiParser.readLocation("resolve-flatten-SH-configuration-test.yaml", null, options);
+        OpenAPI openAPI = parseResult.getOpenAPI();
+        assertNull(openAPI.getComponents().getSchemas().get("#/components/schemas/inline_response_404"));
+        assertNull(openAPI.getComponents().getSchemas().get("#/components/schemas/inline_response_200"));
+    }
 }
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/resolve-flatten-SH-configuration-test.yaml b/modules/swagger-parser-v3/src/test/resources/resolve-flatten-SH-configuration-test.yaml
new file mode 100644
index 00000000..151e91a8
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/resolve-flatten-SH-configuration-test.yaml
@@ -0,0 +1,43 @@
+openapi: 3.0.1
+info:
+  title: Simple API
+  version: 1.0.0
+
+paths:
+  /users:
+    get:
+      summary: Get list of users
+      operationId: getUsers
+      responses:
+        '200':
+          $ref: '#/components/responses/SuccessResponse'
+        '404':
+          $ref: '#/components/responses/NotFoundResponse'
+
+components:
+  responses:
+    SuccessResponse:
+      description: A successful response
+      content:
+        application/json:
+          schema:
+            type: array
+            items:
+              type: object
+              properties:
+                id:
+                  type: integer
+                name:
+                  type: string
+                email:
+                  type: string
+    NotFoundResponse:
+      description: Not Found response
+      content:
+        application/json:
+          schema:
+            type: object
+            properties:
+              message:
+                type: string
+                example: "Resource not found"
diff --git a/modules/swagger-parser-v3/src/test/resources/resolve-responses-test.yaml b/modules/swagger-parser-v3/src/test/resources/resolve-responses-test.yaml
new file mode 100644
index 00000000..43642b01
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/resolve-responses-test.yaml
@@ -0,0 +1,200 @@
+openapi: 3.0.1
+info:
+  title: Example API with Error Responses
+  description: An example API with 4xx responses as references.
+  version: "1.0.0"
+servers:
+  - url: https://api.example.com/v1
+    description: Production server
+  - url: https://staging-api.example.com/v1
+    description: Staging server
+paths:
+  /users:
+    get:
+      summary: Get list of users
+      description: Returns a list of users
+      responses:
+        '200':
+          description: A list of users.
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/User'
+        '500':
+          description: Server error
+        '400':
+          $ref: "#/components/responses/BadRequest"
+    post:
+      summary: Create a new user
+      description: Creates a new user in the system
+      requestBody:
+        $ref: "#/components/requestBodies/UserStructure"
+      responses:
+        '201':
+          description: User created successfully
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/User'
+        '400':
+          $ref: "#/components/responses/BadRequest"
+        '422':
+          $ref: '#/components/responses/UnprocessableEntity'
+  /users/{userId}:
+    get:
+      summary: Get a user by ID
+      description: Retrieves a specific user by their ID
+      parameters:
+        - name: userId
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        '200':
+          description: User data retrieved successfully
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/User'
+        '404':
+          $ref: "#/components/responses/NotFound"
+        '500':
+          description: Server error
+    put:
+      summary: Update a user by ID
+      description: Updates a user's data
+      parameters:
+        - name: userId
+          in: path
+          required: true
+          schema:
+            type: string
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/UserInput'
+      responses:
+        '200':
+          description: User updated successfully
+        '400':
+          $ref: "#/components/responses/BadRequest"
+        '404':
+          $ref: "#/components/responses/NotFound"
+        '500':
+          description: Server error
+    delete:
+      summary: Delete a user by ID
+      description: Deletes a specific user
+      parameters:
+        - name: userId
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        '204':
+          description: User deleted successfully
+        '404':
+          $ref: "#/components/responses/NotFound"
+        '400':
+          $ref: "#/components/responses/BadRequest"
+        '500':
+          description: Server error
+components:
+  schemas:
+    User:
+      type: object
+      properties:
+        id:
+          type: string
+          description: The user's unique ID
+        name:
+          type: string
+          description: The user's name
+        email:
+          type: string
+          description: The user's email address
+        age:
+          type: integer
+          description: The user's age
+      required:
+        - id
+        - name
+        - email
+    UserInput:
+      type: object
+      properties:
+        name:
+          type: string
+          description: The user's name
+        email:
+          type: string
+          description: The user's email address
+        age:
+          type: integer
+          description: The user's age
+      required:
+        - name
+        - email
+  responses:
+    BadRequest:
+      description: Bad request due to invalid input data
+      content:
+        application/json:
+          schema:
+            type: object
+            properties:
+              message:
+                type: string
+                description: Error message explaining why the request was invalid
+              code:
+                type: integer
+                description: Error code
+    NotFound:
+      description: The requested resource was not found
+      content:
+        application/json:
+          schema:
+            type: object
+            properties:
+              message:
+                type: string
+                description: Error message explaining why the resource was not found
+              code:
+                type: integer
+                description: Error code
+    UnprocessableEntity:
+      description: Request was well-formed but contains invalid data
+      content:
+        application/json:
+          schema:
+            type: object
+            properties:
+              message:
+                type: string
+                description: Explanation of why the request could not be processed
+              code:
+                type: integer
+                description: Error code
+  requestBodies:
+    UserStructure:
+      content:
+        application/json:
+          schema: 
+            type: object
+            properties:
+              name:
+                type: string
+                maxLength: 10
+                minLength: 1
+              surname:
+                type: string
+                maxLength: 10
+                minLength: 1
+          
+    
\ No newline at end of file