Repository: swagger-api/swagger-parser
Pull request #1890 (2023-03-07 09:02:32)

url and identifier in the license field are handled incorrectly
Currently, swagger-parser will error with the following message `attribute info.license.identifier is missing` when parsing an OpenApi 3.1 document where `license.url` and `license.name` is set (see the example document below).
According to the official OpenApi 3.1 schema it must have either the `license.url` or the `license.identifier` set but not both.
This PR will fix swagger-parser to work according to the schema.

Example OpenApi 3.1 which currently fails:
```yaml
openapi: 3.1.0
servers:
  - url: 'http://abc:5555/mypath'
info:
  version: '1.0'
  title: dd
  license:
    name: test
    url: http://example.com
paths:
  /resource1/Id:
    post:
      description: ''
      operationId: postOp
      responses:
        '200':
          description: Successful
        '401':
          description: Access Denied
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/mydefinition'
        required: true
components:
  schemas:
    mydefinition: {}
```

Link to the OpenApi 3.1 schema:
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#licenseObject


Patch:
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 fbcd77b0..61265f39 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
@@ -1305,9 +1305,17 @@ public class OpenAPIDeserializer {
 		}
 
 		if (result.isOpenapi31()) {
-			value = getString("identifier", node, true, location, result);
+            // either the url must be set or the identifier but not both
+            boolean needsIdentifier = license.getUrl() == null;
+			value = getString("identifier", node, needsIdentifier, location, result);
+
 			if (StringUtils.isNotBlank(value)) {
-				license.setIdentifier(value);
+                if (!needsIdentifier) {
+                    result.extra(location, "identifier", node);
+                    result.invalid();
+                } else {
+                    license.setIdentifier(value);
+                }
 			}
 		}
 


Test patch:
diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/OpenAPIDeserializerTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/OpenAPIDeserializerTest.java
index 2cadef7b..945396eb 100644
--- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/OpenAPIDeserializerTest.java
+++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/OpenAPIDeserializerTest.java
@@ -162,6 +162,127 @@ public class OpenAPIDeserializerTest {
 
     }
 
+    @Test
+    public void testIdentifierAndUrlInvalid() throws Exception {
+        String yaml = "openapi: 3.1.0\n" +
+                      "servers:\n" +
+                      "  - url: 'http://abc:5555/mypath'\n" +
+                      "info:\n" +
+                      "  version: '1.0'\n" +
+                      "  title: dd\n" +
+                      "  license:\n" +
+                      "    name: test\n" +
+                      "    url: http://example.com\n" +
+                      "    identifier: test\n" +
+                      "paths:\n" +
+                      "  /resource1/Id:\n" +
+                      "    post:\n" +
+                      "      description: ''\n" +
+                      "      operationId: postOp\n" +
+                      "      responses:\n" +
+                      "        '200':\n" +
+                      "          description: Successful\n" +
+                      "        '401':\n" +
+                      "          description: Access Denied\n" +
+                      "      requestBody:\n" +
+                      "        content:\n" +
+                      "          application/json:\n" +
+                      "            schema:\n" +
+                      "              $ref: '#/components/schemas/mydefinition'\n" +
+                      "        required: true\n" +
+                      "components:\n" +
+                      "  schemas:\n" +
+                      "    mydefinition: {}";
+
+        OpenAPIV3Parser parser = new OpenAPIV3Parser();
+
+        SwaggerParseResult result = parser.readContents(yaml,null,null);
+        OpenAPI openAPI = result.getOpenAPI();
+        assertNotNull(openAPI);
+
+        assertEquals(result.getMessages().size(), 1);
+        assertTrue(result.getMessages().get(0).contains("identifier"));
+    }
+
+    @Test
+    public void testUrlValid() {
+        String yaml = "openapi: 3.1.0\n" +
+                      "servers:\n" +
+                      "  - url: 'http://abc:5555/mypath'\n" +
+                      "info:\n" +
+                      "  version: '1.0'\n" +
+                      "  title: dd\n" +
+                      "  license:\n" +
+                      "    name: test\n" +
+                      "    url: http://example.com\n" +
+                      "paths:\n" +
+                      "  /resource1/Id:\n" +
+                      "    post:\n" +
+                      "      description: ''\n" +
+                      "      operationId: postOp\n" +
+                      "      responses:\n" +
+                      "        '200':\n" +
+                      "          description: Successful\n" +
+                      "        '401':\n" +
+                      "          description: Access Denied\n" +
+                      "      requestBody:\n" +
+                      "        content:\n" +
+                      "          application/json:\n" +
+                      "            schema:\n" +
+                      "              $ref: '#/components/schemas/mydefinition'\n" +
+                      "        required: true\n" +
+                      "components:\n" +
+                      "  schemas:\n" +
+                      "    mydefinition: {}";
+
+        OpenAPIV3Parser parser = new OpenAPIV3Parser();
+
+        SwaggerParseResult result = parser.readContents(yaml,null,null);
+        OpenAPI openAPI = result.getOpenAPI();
+        assertNotNull(openAPI);
+
+        assertEquals(result.getMessages().size(), 0);
+    }
+
+    @Test
+    public void testIdentifierValid() {
+        String yaml = "openapi: 3.1.0\n" +
+                      "servers:\n" +
+                      "  - url: 'http://abc:5555/mypath'\n" +
+                      "info:\n" +
+                      "  version: '1.0'\n" +
+                      "  title: dd\n" +
+                      "  license:\n" +
+                      "    name: test\n" +
+                      "    identifier: abc\n" +
+                      "paths:\n" +
+                      "  /resource1/Id:\n" +
+                      "    post:\n" +
+                      "      description: ''\n" +
+                      "      operationId: postOp\n" +
+                      "      responses:\n" +
+                      "        '200':\n" +
+                      "          description: Successful\n" +
+                      "        '401':\n" +
+                      "          description: Access Denied\n" +
+                      "      requestBody:\n" +
+                      "        content:\n" +
+                      "          application/json:\n" +
+                      "            schema:\n" +
+                      "              $ref: '#/components/schemas/mydefinition'\n" +
+                      "        required: true\n" +
+                      "components:\n" +
+                      "  schemas:\n" +
+                      "    mydefinition: {}";
+
+        OpenAPIV3Parser parser = new OpenAPIV3Parser();
+
+        SwaggerParseResult result = parser.readContents(yaml,null,null);
+        OpenAPI openAPI = result.getOpenAPI();
+        assertNotNull(openAPI);
+
+        assertEquals(result.getMessages().size(), 0);
+    }
 
     @Test
     public void testSecurityDeserialization() throws Exception {