From a027b2ecacffe17ee8b79500406785667eb508be Mon Sep 17 00:00:00 2001 From: MicRyc Date: Tue, 15 Oct 2024 10:31:48 +0200 Subject: [PATCH 1/2] Add resolveResponses flag to ParseOptions and OperationProcessor --- .../io/swagger/v3/parser/core/models/ParseOptions.java | 10 +++++++++- .../v3/parser/processors/OperationProcessor.java | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) 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 adc2ddb308..60a587af3a 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 List getRemoteRefBlockList() { public void setRemoteRefBlockList(List 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 2662e3ca30..3a73be51db 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 @@ -71,7 +71,7 @@ public void processOperation(Operation operation) { 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) { + if (response.get$ref() != null && cache != null && cache.getParseOptions() != null && cache.getParseOptions().isResolveResponses()) { responseProcessor.processResponse(response); From 6bed0f604557002d6acec6401f0f46e0bb5be14f Mon Sep 17 00:00:00 2001 From: MicRyc Date: Tue, 15 Oct 2024 15:05:22 +0200 Subject: [PATCH 2/2] Add tests for setResolveResponses flag/ Fix existing ones --- .../parser/processors/OperationProcessor.java | 2 +- .../v3/parser/test/NetworkReferenceTest.java | 1 + .../v3/parser/test/OpenAPIResolverTest.java | 9 +- .../v3/parser/test/OpenAPIV3ParserTest.java | 56 ++++- ...resolve-flatten-SH-configuration-test.yaml | 43 ++++ .../resources/resolve-responses-test.yaml | 200 ++++++++++++++++++ 6 files changed, 306 insertions(+), 5 deletions(-) create mode 100644 modules/swagger-parser-v3/src/test/resources/resolve-flatten-SH-configuration-test.yaml create mode 100644 modules/swagger-parser-v3/src/test/resources/resolve-responses-test.yaml 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 3a73be51db..c79237557e 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,7 +70,7 @@ public void processOperation(Operation operation) { 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 + //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); 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 081dc264f1..96ed91bad6 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 void testIssue411() throws Exception { 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 5820e23775..36117dd216 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 void testIssue1706() { 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 void testIssue1706() { 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 void testSharedSwaggerParametersTest() { @Test(description = "resolve top-level responses") public void testSharedResponses() { final OpenAPI swagger = new OpenAPI(); + ParseOptions parseOptions = new ParseOptions(); + parseOptions.setResolveResponses(true); List parameters = new ArrayList<>(); parameters.add(0,new Parameter().$ref("username")); swagger.path("/fun", new PathItem() @@ -1294,7 +1297,7 @@ public void testSharedResponses() { 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 997f1c23bd..e7e4a55566 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 void testIssue1780() { 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 void testIssue1131() { 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 void testIssue811_RefSchema_ToRefSchema() { 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 void testRelativePath() { 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 void testRelativePath2() { 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 @@ private OpenAPI doRelativeFileTest(String location) { 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 void testIssue1177() { 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 void testIssue2081() { 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 0000000000..151e91a871 --- /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 0000000000..43642b01f1 --- /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