Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug when a schema has a nullable AllOf ref #20

Closed
knivets opened this issue Sep 22, 2021 · 8 comments
Closed

Bug when a schema has a nullable AllOf ref #20

knivets opened this issue Sep 22, 2021 · 8 comments

Comments

@knivets
Copy link

knivets commented Sep 22, 2021

Hey! I have a weird issue that happens with refs that are nullable. I have the following schema:

"GroupWithRecentMessage": {
                "type": "object",
                "properties": {
                    "uid": {
                        "type": "string",
                        "format": "uuid",
                        "readOnly": true
                    },
                    "recent_message": {
                        "allOf": [
                            {
                                "$ref": "#/components/schemas/Message"
                            }
                        ],
                        "readOnly": true,
                        "nullable": true
                    }
                }
            }

Essentially what I try to express is that recent_message can be either null or an object of type Message.

When I run a validator it raises an exception:

openapi_core.unmarshalling.schemas.exceptions.InvalidSchemaValue: Value {
'results': [{'uid': '018a8f79-5098-478b-a77e-2adb685a1ea5', 'recent_message': None}]
} not valid for schema of type object: (<ValidationError: 'None for not nullable'>

After debugging the issue I have been able to find the root cause. I think the main reason why it happens is that openapi-schema-validator adds a "nullable": false to each ref. So the actual schema that ends being used is the following:

{'nullable': False,
 'properties': {'recent_message': {'allOf': [{'$ref': '#/components/schemas/Message',
                                              'nullable': False, # <---- this is what triggers the wrong error
                                              'x-scope': ['',
                                                          '#/components/schemas/GroupWithRecentMessage']}],
                                   'nullable': True,
                                   'readOnly': True},
                'uid': {'format': 'uuid',
                        'nullable': False,
                        'readOnly': True,
                        'type': 'string'}},
 'required': ['recent_message',
              'uid'],
 'type': 'object'}

How can I fix this issue?

Thanks!

@ngnpope
Copy link

ngnpope commented Oct 11, 2021

I have also bumped into this issue. I believe that these lines are incorrect and should be removed:

https://github.com/p1c2u/openapi-schema-validator/blob/ab7222af0597ebc672f93cc7f7e089897aba1bec/openapi_schema_validator/validators.py#L68-L72

This is for a number of reasons, all of which have affected me:

  • OpenAPI 3.0.x should ignore siblings of $ref (1)
  • OpenAPI 3.0.x needs special hacks for nullable with enum (1, 2, 3)
  • This breaks nullable set to True alongside allOf/anyOf/oneOf. (This is your case @knivets.)

The comment # append defaults to trigger validator (i.e. nullable) doesn't seem to make sense as validation seems to work perfectly fine without it. Perhaps @p1c2u can shed some light on why this is/was necessary?

This shouldn't be an issue with OpenAPI 3.1.x when it is supported as null is allowed as a type.

I have used the following workaround which may help:

from openapi_core.unmarshalling.schemas.enums import UnmarshalContext
from openapi_core.unmarshalling.schemas.factories import SchemaUnmarshallersFactory
from openapi_core.validation.response.validators import ResponseValidator
from openapi_schema_validator import OAS30Validator

class FixedOAS30Validator(OAS30Validator):
    def iter_errors(self, instance, _schema=None):
        # Use the implementation of .iter_errors() from the parent.
        return super(OAS30Validator, self).iter_errors(instance, _schema)

class FixedSchemaUnmarshallersFactory(SchemaUnmarshallersFactory):
    def get_validator(self, schema):
        kwargs = {"resolver": self.resolver, "format_checker": self.format_checker}
        if self.context is not None:
            kwargs[self.CONTEXT_VALIDATION[self.context]] = True
        with schema.open() as schema_dict:
            return FixedOAS30Validator(schema_dict, **kwargs)

class FixedResponseValidator(ResponseValidator):
    @property
    def schema_unmarshallers_factory(self):
        return FixedSchemaUnmarshallersFactory(
            self.spec.accessor.dereferencer.resolver_manager.resolver,
            self.format_checker,
            self.custom_formatters,
            context=UnmarshalContext.RESPONSE,
        )

@claeyswo
Copy link

claeyswo commented Feb 8, 2022

We are experiencing the same issue. As the previous comment stated: Perhaps @p1c2u can shed some light on why this is/was necessary?

@p1c2u
Copy link
Collaborator

p1c2u commented Feb 10, 2022

That's exactly what comment says to trigger nullable validator.

The comment # append defaults to trigger validator (i.e. nullable) doesn't seem to make sense as validation seems to work perfectly fine without it.

It can work fine if you define nullable param explicitly.
For schema without nullable defined (default is False) if you pass None value it won't raise validation error - which is incorrect. You can try to remove these lines and run tests.

@ngnpope
Copy link

ngnpope commented Feb 10, 2022

Maybe the issue I was bumping into was related to #24 (solved by #26), but I haven't been able to check.

@p1c2u
Copy link
Collaborator

p1c2u commented Feb 10, 2022

Same here, can't reproduce the issue. @claeyswo which jsonschema version you use and do you have any example of the issue?

@claeyswo
Copy link

3.0, but by adding nullable to every item the problem was solved. By switching the nullable om some_id in the example below we get the desired effect.

schema = {
    "$ref": "#/components/schemas/TestSchema",
    "components": {
        "schemas": {
            "IntegerString": {
                "anyOf": [
                    {"type": "integer", "nullable": True},
                    {"type": "string", "nullable": True},
                ],
                "nullable": True,
            },
            "TestSchema": {
                "type": "object",
                "properties": {
                    "some_id": {
                        "$ref":  "#/components/schemas/IntegerString",
                        "nullable": True,
                    }
                },
            },
        }
    },
}

@ghandic
Copy link

ghandic commented Jun 23, 2022

attribute:
    nullable: true
    $ref: '#/components/Example'
attribute:
    oneOf:
        - type: "null"
        - $ref: '#/components/Example'

Neither work, any update on a fix for this?

stephenfin added a commit to stephenfin/openapi-schema-validator that referenced this issue Oct 14, 2022
This is a known bug. Add a test for it so we can work on fixing it.

Signed-off-by: Stephen Finucane <stephen@that.guru>
Related-bug: python-openapi#20
pyckle pushed a commit to pyckle/openapi-schema-validator that referenced this issue Jan 5, 2023
pyckle pushed a commit to pyckle/openapi-schema-validator that referenced this issue Jan 5, 2023
@p1c2u
Copy link
Collaborator

p1c2u commented Jan 7, 2023

There were ambiguities in the semantics of nullable in OAS 3.0.0-2 More about the issue can be found in the following proposal. OAS 3.0.3 clarified the definition so it can be implemented properly. There shouldn't be any major backward compatibility issues.

@p1c2u p1c2u closed this as completed in e213079 Jan 7, 2023
p1c2u added a commit that referenced this issue Jan 7, 2023
Fix #20 - nullable semantics with $ref, oneOf, anyOf, and allOf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants