jpmckinney / validictory

🎓 deprecated general purpose python data validator
Other
240 stars 57 forks source link

"oneOf" #74

Closed dmr closed 7 years ago

dmr commented 10 years ago

Hi,

did anyone here ever need "oneOf" from the JSON Schema specification (http://json-schema.org/example2.html)?

validictory does not support that yet, am I right?

Thanks, Daniel

jamesturk commented 10 years ago

looks like this is just a special case of $ref - which is not supported as it introduces the idea of loading from loading remote files that I don't think make sense for a validator of python objects.

dmr commented 10 years ago

I think that $ref and the remote resource problems that come with that can be preprocessed before validation and then a little wrapper could solve the problem.

Why is it a special case of ref?

dmr commented 10 years ago

I implemented a solution for "anyOf", it looks like this:

def check_xOf_schema(fieldname, xOf):
    if isinstance(xOf, (list, tuple)):
        for possibleSchemaMatch in xOf:
            if not isinstance(possibleSchemaMatch, dict):
                raise SchemaError("Error in {}: Expected {} to only contain objects".format(fieldname, xOf))
    else:
        raise SchemaError("Properties definition of field '{}' is "
                          "not a list".format(fieldname))

def validate_anyOf(self, x, fieldname, schema, path, anyOf=None):
    '''
    Validates that the value of the given field matches any of the defined
    schemas
    '''
    value = x.get(fieldname, None)
    if value is not None:
        check_xOf_schema(fieldname=fieldname, xOf=anyOf)

        matches = []
        for index, possibleSchemaMatch in enumerate(anyOf):
            try:
                self.__validate("_data", {"_data": value}, possibleSchemaMatch,
                                '{}[{}]'.format(path, index))
                matches.append(index)
            except FieldValidationError:
                pass

        if not matches:
            raise FieldValidationError(
                fieldname=fieldname,
                value=value,
                message='Must match anyOf {}'.format(anyOf)
            )

check_xOf_schema is a global method.

oneOf is quite similar:

def validate_oneOf(self, x, fieldname, schema, path, oneOf=None):
    '''
    Validates that the value of the given field matches exactly one of the
    defined schemas
    '''
    value = x.get(fieldname, None)
    if value is not None:
        check_xOf_schema(fieldname=fieldname, xOf=oneOf)

        matches = []
        for index, possibleSchemaMatch in enumerate(oneOf):
            try:
                self.__validate("_data", {"_data": value}, possibleSchemaMatch,
                                '{}[{}]'.format(path, index))
                matches.append(index)
            except FieldValidationError:
                pass

        if len(matches) != 1:
            raise FieldValidationError(
                fieldname=fieldname,
                value=value,
                message='Must match exactly oneOf {}'.format(oneOf)
            )

And allOf also:

def validate_allOf(self, x, fieldname, schema, path, allOf=None):
    '''
    Validates that the value of the given field matches all of the defined
    schemas
    '''
    value = x.get(fieldname, None)
    if value is not None:
        check_xOf_schema(fieldname=fieldname, xOf=allOf)

        matches = []
        for index, possibleSchemaMatch in enumerate(allOf):
            try:
                self.__validate("_data", {"_data": value}, possibleSchemaMatch,
                                '{}[{}]'.format(path, index))
                matches.append(index)
            except FieldValidationError:
                pass

        print('matches', matches)

        if len(matches) != len(allOf):
            raise FieldValidationError(
                fieldname=fieldname,
                value=value,
                message='Must match allOf {}'.format(allOf)
            )

For my personal taste of clean code this is too much duplicate code but at least anyOf and oneOf passes all the test cases defined in JSON-Schema-Test-Suite.

allOf does not pass all the tests but first things first: I only have use for "anyOf" in my projects, that's what I realized during this. -->

  1. How do you like the implementation?
  2. Which features of the specification should become part of validictory and where do we write down why we ignore the other features?

Side note: "multipleOf" has nothing to do with the other "xOf" cases which is also confusing to me.

jamesturk commented 10 years ago

if this is passing the inclusion is fine with me, what is failing in allOf?

NickFranceschina commented 10 years ago

+1 --- $ref can refer to other "sub-schemas" inside the overall schema document (not external loading) and use those with any/all/oneOf to build more complex extensions. Sayt I have three different types that all look the same except for a few differences based on one property value... I could define the "base schema" in a $ref and then build on it to create other schemas: http://spacetelescope.github.io/understanding-json-schema/structuring.html#extending

apologies if this is already possible in Validictory and I just missed it...

dmr commented 10 years ago

@NickFranceschina Thanks for the link.

validictory today ignores "$ref" because you can actually preprocess your schema before you validate it. In https://github.com/sunlightlabs/validictory/issues/20 I linked to a small function I wrote to solve the refs before validating: https://github.com/dmr/validictory_preprocess_ref. This is how I solve the "$ref" problem.

This issue here is more about "oneOf" and other special things or where did I miss your question?

NickFranceschina commented 10 years ago

Thanks @dmr ... agreed with your comment in issue #20 "there are lots of dumb things in jsonschema" ... and it is probably easier to just do it with a preprocessor anyways.

Thanks again!

jamesturk commented 7 years ago

closed in light of #114