Skip to content

NullPointerException when comparing composed schemas #317

Closed
@julienrf

Description

@julienrf

I get a NullPointerException when I try to compare two schemas defined as a oneOf, if one of them is referred to via a allOf and the other one is referred to via a $ref. Let me show you a test case:

Old OpenAPI:

{ "openapi":"3.0.0", "info":{ "title":"API", "version":"0.1.0" }, "paths":{ "/resource":{ "post":{ "responses":{ "200":{ "description":"Created resource", "content":{ "application/json":{ "schema":{ "type":"string" } } } } }, "summary":"Create resource", "requestBody":{ "content":{ "application/json":{ "schema":{ "$ref":"#/components/schemas/Resource" } } }, "description":"Definition of the resource" } } } }, "components":{ "schemas":{ "Resource":{ "type":"object", "properties":{ "assignment":{ "$ref":"#/components/schemas/Foo" } } }, "Foo":{ "oneOf":[ { "$ref":"#/components/schemas/Foo.Bar" }, { "$ref":"#/components/schemas/Foo.Baz" } ], "discriminator":{ "propertyName":"type", "mapping":{ "Bar":"#/components/schemas/Foo.Bar", "Baz":"#/components/schemas/Foo.Baz" } } }, "Foo.Bar":{ "type":"object", "properties":{ "type":{ "type":"string" } } }, "Foo.Baz":{ "type":"object", "properties":{ "type":{ "type":"string" } } } }, "securitySchemes":{
}

}
}

New OpenAPI:

{ "openapi":"3.0.0", "info":{ "title":"API", "version":"0.1.0" }, "paths":{ "/resource":{ "post":{ "responses":{ "200":{ "description":"Created resource", "content":{ "application/json":{ "schema":{ "type":"string" } } } } }, "summary":"Create resource", "requestBody":{ "content":{ "application/json":{ "schema":{ "$ref":"#/components/schemas/Resource" } } }, "description":"Definition of the resource" } } } }, "components":{ "schemas":{ "Resource":{ "type":"object", "properties":{ "assignment":{ "default":{ "type":"Bar" }, "allOf":[ { "$ref":"#/components/schemas/Foo" } ] } } }, "Foo":{ "oneOf":[ { "$ref":"#/components/schemas/Foo.Bar" }, { "$ref":"#/components/schemas/Foo.Baz" } ], "discriminator":{ "propertyName":"type", "mapping":{ "Bar":"#/components/schemas/Foo.Bar", "Baz":"#/components/schemas/Foo.Baz" } } }, "Foo.Bar":{ "type":"object", "properties":{ "type":{ "type":"string" } } }, "Foo.Baz":{ "type":"object", "properties":{ "type":{ "type":"string" } } } }, "securitySchemes":{
}

}
}

The difference between both is just that:

--- old-open-api.json	2022-01-09 23:22:47.307460584 +0100
+++ new-open-api.json	2022-01-09 23:24:02.127524148 +0100
@@ -39,7 +39,14 @@
         "type":"object",
         "properties":{
           "assignment":{
-            "$ref":"#/components/schemas/Foo"
+            "default":{
+              "type":"Bar"
+            },
+            "allOf":[
+              {
+                "$ref":"#/components/schemas/Foo"
+              }
+            ]
           }
         }
       },

So, in the old version we refer to the schema Foo via a direct "$ref": "#/components/schemas/Foo", whereas in the new version we use an "allOf": [{ "$ref": "#/components/schemas/Foo" }]. The allOf trick is useful because it allows us to add additional information about the referenced schemas (see the default value in my example).

If I try to diff both documents, I get the following stack trace:

$ docker run --rm -t \
  -v $(pwd):/specs:ro \
  openapitools/openapi-diff:latest /specs/old-open-api.json /specs/new-open-api.json
Unexpected exception. Reason: null
java.lang.NullPointerException
	at org.openapitools.openapidiff.core.compare.schemadiffresult.ComposedSchemaDiffResult.getMapping(ComposedSchemaDiffResult.java:100)
	at org.openapitools.openapidiff.core.compare.schemadiffresult.ComposedSchemaDiffResult.diff(ComposedSchemaDiffResult.java:62)
	at org.openapitools.openapidiff.core.compare.SchemaDiff.computeDiff(SchemaDiff.java:316)
	at org.openapitools.openapidiff.core.compare.SchemaDiff.computeDiff(SchemaDiff.java:27)
	at org.openapitools.openapidiff.core.compare.ReferenceDiffCache.cachedDiff(ReferenceDiffCache.java:51)
	at org.openapitools.openapidiff.core.compare.SchemaDiff.diff(SchemaDiff.java:282)
	at org.openapitools.openapidiff.core.compare.schemadiffresult.SchemaDiffResult.diff(SchemaDiffResult.java:70)
	at org.openapitools.openapidiff.core.compare.SchemaDiff.computeDiff(SchemaDiff.java:316)
	at org.openapitools.openapidiff.core.compare.SchemaDiff.computeDiff(SchemaDiff.java:27)
	at org.openapitools.openapidiff.core.compare.ReferenceDiffCache.cachedDiff(ReferenceDiffCache.java:44)
	at org.openapitools.openapidiff.core.compare.SchemaDiff.diff(SchemaDiff.java:282)
	at org.openapitools.openapidiff.core.compare.ContentDiff.diff(ContentDiff.java:33)
	at org.openapitools.openapidiff.core.compare.RequestBodyDiff.computeDiff(RequestBodyDiff.java:77)
	at org.openapitools.openapidiff.core.compare.RequestBodyDiff.computeDiff(RequestBodyDiff.java:17)
	at org.openapitools.openapidiff.core.compare.ReferenceDiffCache.cachedDiff(ReferenceDiffCache.java:51)
	at org.openapitools.openapidiff.core.compare.RequestBodyDiff.diff(RequestBodyDiff.java:34)
	at org.openapitools.openapidiff.core.compare.OperationDiff.diff(OperationDiff.java:42)
	at org.openapitools.openapidiff.core.compare.PathDiff.diff(PathDiff.java:35)
	at org.openapitools.openapidiff.core.compare.PathsDiff.lambda$diff$2(PathsDiff.java:67)
	at java.util.LinkedHashMap$LinkedKeySet.forEach(LinkedHashMap.java:559)
	at org.openapitools.openapidiff.core.compare.PathsDiff.diff(PathsDiff.java:40)
	at org.openapitools.openapidiff.core.compare.OpenApiDiff.compare(OpenApiDiff.java:95)
	at org.openapitools.openapidiff.core.compare.OpenApiDiff.compare(OpenApiDiff.java:66)
	at org.openapitools.openapidiff.core.OpenApiCompare.fromSpecifications(OpenApiCompare.java:101)
	at org.openapitools.openapidiff.core.OpenApiCompare.fromLocations(OpenApiCompare.java:90)
	at org.openapitools.openapidiff.core.OpenApiCompare.fromLocations(OpenApiCompare.java:77)
	at org.openapitools.openapidiff.cli.Main.main(Main.java:156)

Which points to this line.

It seems openapi-diff expects the “right” schema to always be a oneOf with a mapping, but this is not the case here (only the “left” one is a oneOf).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions