diff --git a/.github/workflows/maven-pulls.yml b/.github/workflows/maven-pulls.yml index 4328767c1d..c4e1edbb64 100644 --- a/.github/workflows/maven-pulls.yml +++ b/.github/workflows/maven-pulls.yml @@ -20,7 +20,7 @@ jobs: java-version: ${{ matrix.java }} distribution: 'zulu' - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/maven-v1-pulls.yml b/.github/workflows/maven-v1-pulls.yml index 62bb2bbf38..1c077fd617 100644 --- a/.github/workflows/maven-v1-pulls.yml +++ b/.github/workflows/maven-v1-pulls.yml @@ -20,7 +20,7 @@ jobs: java-version: ${{ matrix.java }} distribution: 'zulu' - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/maven-v1.yml b/.github/workflows/maven-v1.yml index d0be1f29c9..4646419134 100644 --- a/.github/workflows/maven-v1.yml +++ b/.github/workflows/maven-v1.yml @@ -23,7 +23,7 @@ jobs: server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 7f95213eda..c259ff7833 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -23,7 +23,7 @@ jobs: server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/next-snapshot-v1.yml b/.github/workflows/next-snapshot-v1.yml index 7aaf92770e..fa8ee06613 100644 --- a/.github/workflows/next-snapshot-v1.yml +++ b/.github/workflows/next-snapshot-v1.yml @@ -29,7 +29,7 @@ jobs: server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/next-snapshot.yml b/.github/workflows/next-snapshot.yml index adf990b5cc..4db72bcbf8 100644 --- a/.github/workflows/next-snapshot.yml +++ b/.github/workflows/next-snapshot.yml @@ -29,7 +29,7 @@ jobs: server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/prepare-release-v1.yml b/.github/workflows/prepare-release-v1.yml index 06283aca17..398f728a3e 100644 --- a/.github/workflows/prepare-release-v1.yml +++ b/.github/workflows/prepare-release-v1.yml @@ -29,7 +29,7 @@ jobs: server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index f0d8315e47..f09592211c 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -29,7 +29,7 @@ jobs: server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/release-v1.yml b/.github/workflows/release-v1.yml index 48a515fa1b..6731b061e6 100644 --- a/.github/workflows/release-v1.yml +++ b/.github/workflows/release-v1.yml @@ -29,7 +29,7 @@ jobs: server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4fbc14b6da..4208d0e3f9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 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 60a587af3a..777d4877b3 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 @@ -21,6 +21,7 @@ public class ParseOptions { private boolean safelyResolveURL; private List remoteRefAllowList; private List remoteRefBlockList; + private boolean explicitStyleAndExplode = true; public boolean isResolve() { @@ -169,4 +170,13 @@ public boolean isResolveResponses() { public void setResolveResponses(boolean resolveResponses) { this.resolveResponses = resolveResponses; } + + public boolean isExplicitStyleAndExplode() { + return explicitStyleAndExplode; + } + + public void setExplicitStyleAndExplode(boolean explicitStyleAndExplode) { + this.explicitStyleAndExplode = explicitStyleAndExplode; + } + } diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java index bf10fcd792..5a3aa12875 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java @@ -305,6 +305,7 @@ public SwaggerParseResult deserialize(JsonNode rootNode, String path, ParseOptio rootParse.setInferSchemaType(options.isInferSchemaType()); rootParse.setAllowEmptyStrings(options.isAllowEmptyString()); rootParse.setValidateInternalRefs(options.isValidateInternalRefs()); + rootParse.setExplicitStyleAndExplode(options.isExplicitStyleAndExplode()); OpenAPI api = parseRoot(rootNode, rootParse, path); result.openapi31(rootParse.isOpenapi31()); result.setOpenAPI(api); @@ -1520,7 +1521,9 @@ public Encoding getEncoding(ObjectNode node, String location, ParseResult result value = getString("style", node, false, location, result); if (StringUtils.isBlank(value)) { - encoding.setStyle(Encoding.StyleEnum.FORM); + if (result.isExplicitStyleAndExplode()) { + encoding.setStyle(Encoding.StyleEnum.FORM); + } } else { if (value.equals(Encoding.StyleEnum.FORM.toString())) { encoding.setStyle(Encoding.StyleEnum.FORM); @@ -2131,9 +2134,9 @@ else if(parameter.getSchema() == null) { Boolean explode = getBoolean("explode", obj, false, location, result); if (explode != null) { parameter.setExplode(explode); - } else if (StyleEnum.FORM.equals(parameter.getStyle())) { + } else if (StyleEnum.FORM.equals(parameter.getStyle()) && result.isExplicitStyleAndExplode()) { parameter.setExplode(Boolean.TRUE); - } else { + } else if (result.isExplicitStyleAndExplode()){ parameter.setExplode(Boolean.FALSE); } } @@ -2237,15 +2240,26 @@ public Header getHeader(ObjectNode headerNode, String location, ParseResult resu header.setDeprecated(deprecated); } + String style = getString("style", headerNode, false, location, result); + if (StringUtils.isBlank(style)) { + if (result.isExplicitStyleAndExplode()) { + header.setStyle(Header.StyleEnum.SIMPLE); + } + } else { + if (value.equals(Header.StyleEnum.SIMPLE.toString())) { + header.setStyle(Header.StyleEnum.SIMPLE); + } else { + result.invalidType(location, "style", "simple", headerNode); + } + } + Boolean explode = getBoolean("explode", headerNode, false, location, result); if (explode != null) { header.setExplode(explode); - } else { + } else if (result.isExplicitStyleAndExplode()){ header.setExplode(Boolean.FALSE); } - header.setStyle(Header.StyleEnum.SIMPLE); - ObjectNode headerObject = getObject("schema", headerNode, false, location, result); if (headerObject != null) { header.setSchema(getSchema(headerObject, location, result)); @@ -3369,11 +3383,13 @@ public Example getExample(ObjectNode node, String location, ParseResult result) public void setStyle(String value, Parameter parameter, String location, ObjectNode obj, ParseResult result) { if (StringUtils.isBlank(value)) { - if (QUERY_PARAMETER.equals(parameter.getIn()) || COOKIE_PARAMETER.equals(parameter.getIn())) { - parameter.setStyle(StyleEnum.FORM); - } else if (PATH_PARAMETER.equals(parameter.getIn()) || HEADER_PARAMETER.equals(parameter.getIn())) { - parameter.setStyle(StyleEnum.SIMPLE); - } + if (result.isExplicitStyleAndExplode()) { + if (QUERY_PARAMETER.equals(parameter.getIn()) || COOKIE_PARAMETER.equals(parameter.getIn())) { + parameter.setStyle(StyleEnum.FORM); + } else if (PATH_PARAMETER.equals(parameter.getIn()) || HEADER_PARAMETER.equals(parameter.getIn())) { + parameter.setStyle(StyleEnum.SIMPLE); + } + } } else { if (value.equals(StyleEnum.FORM.toString())) { parameter.setStyle(StyleEnum.FORM); @@ -4275,6 +4291,8 @@ public static class ParseResult { private boolean openapi31 = false; private boolean oaiAuthor = false; + private boolean explicitStyleAndExplode = true; + public boolean isInferSchemaType() { return inferSchemaType; } @@ -4370,6 +4388,19 @@ public ParseResult oaiAuthor(boolean oaiAuthor) { return this; } + public boolean isExplicitStyleAndExplode() { + return explicitStyleAndExplode; + } + + public void setExplicitStyleAndExplode(boolean explicitStyleAndExplode) { + this.explicitStyleAndExplode = explicitStyleAndExplode; + } + + public ParseResult explicitStyleAndExplode(boolean explicitStyleAndExplode) { + this.explicitStyleAndExplode = explicitStyleAndExplode; + return this; + } + public List getMessages() { List messages = new ArrayList(); for (Location l : extra.keySet()) { 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 f67ba3acb0..1308c6c024 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 @@ -3362,6 +3362,63 @@ public void testResolveFullyResponses(){ 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()); + } + @Test(description = "style and explode should not be set with explicitStyleAndExplode = false") + public void testStyleAndExplodeNotExplicit(){ + ParseOptions options = new ParseOptions(); + options.setExplicitStyleAndExplode(false); + OpenAPIV3Parser openApiParser = new OpenAPIV3Parser(); + SwaggerParseResult parseResult = openApiParser.readLocation("style-explode.yaml", null, options); + OpenAPI openAPI = parseResult.getOpenAPI(); + assertEquals(openAPI.getPaths().get("/test").getGet().getParameters().get(0).getStyle().toString(), "form"); + assertEquals(openAPI.getPaths().get("/test").getGet().getParameters().get(0).getExplode(), true); + assertEquals(openAPI.getPaths().get("/test").getGet().getParameters().get(1).getStyle().toString(), "spaceDelimited"); + assertNull(openAPI.getPaths().get("/test").getGet().getParameters().get(1).getExplode()); + assertEquals(openAPI.getPaths().get("/test").getGet().getParameters().get(2).getExplode(), true); + assertNull(openAPI.getPaths().get("/test").getGet().getParameters().get(2).getStyle()); + assertNull(openAPI.getPaths().get("/test").getGet().getParameters().get(6).getStyle()); + assertNull(openAPI.getPaths().get("/test").getGet().getParameters().get(6).getExplode()); + assertNull(openAPI.getPaths().get("/test").getGet().getResponses().get("200").getHeaders().get("bar").getExplode()); + assertNull(openAPI.getPaths().get("/test").getGet().getResponses().get("200").getHeaders().get("bar").getStyle()); + assertEquals(openAPI.getPaths().get("/test").getGet().getResponses().get("200").getHeaders().get("foo").getExplode(), false); + assertNull(openAPI.getPaths().get("/test").getGet().getResponses().get("200").getHeaders().get("foo").getStyle()); + assertEquals(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("file").getStyle().toString(), "form"); + assertEquals(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("file").getExplode(), true); + assertEquals(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("fileWithOnlyStyle").getStyle().toString(), "form"); + assertNull(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("fileWithOnlyStyle").getExplode()); + assertEquals(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("fileWithOnlyExplode").getExplode(), true); + assertNull(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("fileWithOnlyExplode").getStyle()); + assertNull(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("fileWithout").getStyle()); + assertNull(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("fileWithout").getExplode()); + } + + @Test(description = "style and explode should be set with explicitStyleAndExplode = true") + public void testStyleAndExplodeExplicit(){ + ParseOptions options = new ParseOptions(); + options.setExplicitStyleAndExplode(true); + OpenAPIV3Parser openApiParser = new OpenAPIV3Parser(); + SwaggerParseResult parseResult = openApiParser.readLocation("style-explode.yaml", null, options); + OpenAPI openAPI = parseResult.getOpenAPI(); + assertEquals(openAPI.getPaths().get("/test").getGet().getParameters().get(0).getStyle().toString(), "form"); + assertEquals(openAPI.getPaths().get("/test").getGet().getParameters().get(0).getExplode(), true); + assertEquals(openAPI.getPaths().get("/test").getGet().getParameters().get(1).getStyle().toString(), "spaceDelimited"); + assertEquals(openAPI.getPaths().get("/test").getGet().getParameters().get(1).getExplode(), false); + assertEquals(openAPI.getPaths().get("/test").getGet().getParameters().get(2).getStyle().toString(), "form"); + assertEquals(openAPI.getPaths().get("/test").getGet().getParameters().get(2).getExplode(), true); + assertEquals(openAPI.getPaths().get("/test").getGet().getParameters().get(6).getStyle().toString(), "simple"); + assertEquals(openAPI.getPaths().get("/test").getGet().getParameters().get(6).getExplode(), false); + assertEquals(openAPI.getPaths().get("/test").getGet().getResponses().get("200").getHeaders().get("bar").getExplode(), false); + assertEquals(openAPI.getPaths().get("/test").getGet().getResponses().get("200").getHeaders().get("bar").getStyle().toString(), "simple"); + assertEquals(openAPI.getPaths().get("/test").getGet().getResponses().get("200").getHeaders().get("foo").getExplode(), false); + assertNull(openAPI.getPaths().get("/test").getGet().getResponses().get("200").getHeaders().get("foo").getStyle()); + assertEquals(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("file").getStyle().toString(), "form"); + assertEquals(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("file").getExplode(), true); + assertEquals(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("fileWithOnlyStyle").getStyle().toString(), "form"); + assertNull(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("fileWithOnlyStyle").getExplode()); + assertEquals(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("fileWithOnlyExplode").getExplode(), true); + assertEquals(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("fileWithOnlyExplode").getStyle().toString(), "form"); + assertEquals(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("fileWithout").getStyle().toString(), "form"); + assertNull(openAPI.getPaths().get("/test").getPost().getRequestBody().getContent().get("multipart/form-data").getEncoding().get("fileWithout").getExplode()); } } \ No newline at end of file diff --git a/modules/swagger-parser-v3/src/test/resources/style-explode.yaml b/modules/swagger-parser-v3/src/test/resources/style-explode.yaml new file mode 100644 index 0000000000..b5ce6b599d --- /dev/null +++ b/modules/swagger-parser-v3/src/test/resources/style-explode.yaml @@ -0,0 +1,127 @@ +openapi: 3.0.3 +info: + title: Example API with Style and Explode + version: "1.0.0" +paths: + /test: + get: + summary: Demonstrate query and header parameters with style/explode variations + parameters: + # Query parameter with both style and explode + - in: query + name: foo + style: form + explode: true + schema: + type: array + items: + type: string + + # Query parameter with only style + - in: query + name: bar + style: spaceDelimited + schema: + type: array + items: + type: string + + # Query parameter with only explode + - in: query + name: baz + explode: true + schema: + type: array + items: + type: string + + # Header parameter with both style and explode + - in: header + name: X-Foo + style: simple + explode: true + schema: + type: array + items: + type: string + + # Header parameter with only style + - in: header + name: X-Bar + style: simple + schema: + type: array + items: + type: string + + # Header parameter with only explode + - in: header + name: X-Baz + explode: true + schema: + type: array + items: + type: string + + # Header parameter without + - in: header + name: X-FooBar + schema: + type: array + items: + type: string + responses: + '200': + description: Success + headers: + foo: + description: test + style: form + explode: false + schema: + type: array + items: + type: string + bar: + description: test + schema: + type: array + items: + type: string + + post: + summary: Demonstrate encoding with style/explode variations + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + fileWithOnlyStyle: + type: string + format: binary + fileWithOnlyExplode: + type: string + format: binary + fileWithout: + type: string + format: binary + encoding: + # Encoding with both style and explode + file: + style: form + explode: true + # Encoding with only style + fileWithOnlyStyle: + style: form + # Encoding with only explode + fileWithOnlyExplode: + explode: true + fileWithout: + contentType: image/png, image/jpeg + responses: + '200': + description: Success diff --git a/pom.xml b/pom.xml index 18241699bb..d7d16f0c7e 100644 --- a/pom.xml +++ b/pom.xml @@ -412,20 +412,20 @@ 8 - 2.3 + 2.4 1.0.73 2.18.0 2.0.9 - 2.2.28 + 2.2.29 1.6.15 4.13.2 - 7.10.2 + 7.11.0 1.49 2.35.2 3.2.2 3.17.0 - 2.18.2 - 2.18.2 + 2.18.3 + 2.18.3 UTF-8 https://oss.sonatype.org/content/repositories/snapshots/