def test_validate_json():
    validator = Object(properties=Integer())
    text = '{\n    "a": "123",\n    "b": "456"}'
    value = validate_json(text, validator=validator)
    assert value == {"a": 123, "b": 456}

    validator = Object(properties=Integer())
    text = '{\n    "a": "123",\n    "b": "abc"}'

    with pytest.raises(ValidationError) as exc_info:
        validate_json(text, validator=validator)
    exc = exc_info.value

    assert exc.messages() == [
        Message(
            text="Must be a number.",
            code="type",
            index=["b"],
            start_position=Position(line_no=3, column_no=10, char_index=27),
            end_position=Position(line_no=3, column_no=14, char_index=31),
        )
    ]
    assert (
        repr(exc.messages()[0]) ==
        "Message(text='Must be a number.', code='type', index=['b'], start_position=Position(line_no=3, column_no=10, char_index=27), end_position=Position(line_no=3, column_no=14, char_index=31))"
    )

    class Validator(Schema):
        a = Integer()
        b = Integer()

    text = '{\n    "a": "123",\n    "b": "abc"}'
    with pytest.raises(ValidationError) as exc_info:
        validate_json(text, validator=Validator)
    exc = exc_info.value
    assert exc.messages() == [
        Message(
            text="Must be a number.",
            code="type",
            index=["b"],
            start_position=Position(line_no=3, column_no=10, char_index=27),
            end_position=Position(line_no=3, column_no=14, char_index=31),
        )
    ]

    text = '{"a": "123"}'
    with pytest.raises(ValidationError) as exc_info:
        validate_json(text, validator=Validator)
    exc = exc_info.value
    assert exc.messages() == [
        Message(
            text="The field 'b' is required.",
            code="required",
            index=["b"],
            start_position=Position(line_no=1, column_no=1, char_index=0),
            end_position=Position(line_no=1, column_no=12, char_index=11),
        )
    ]
Beispiel #2
0
def test_validate_yaml():
    validator = Object(properties=Integer())
    text = "a: 123\nb: 456\n"
    value = validate_yaml(text, validator=validator)
    assert value == {"a": 123, "b": 456}

    validator = Object(properties=Integer())
    text = "a: 123\nb: abc\n"

    with pytest.raises(ValidationError) as exc_info:
        validate_yaml(text, validator=validator)

    exc = exc_info.value
    assert exc.messages() == [
        Message(
            text="Must be a number.",
            code="type",
            index=["b"],
            start_position=Position(line_no=2, column_no=4, char_index=10),
            end_position=Position(line_no=2, column_no=6, char_index=12),
        )
    ]

    class Validator(Schema):
        a = Integer()
        b = Integer()

    text = "a: 123\nb: abc\n"

    with pytest.raises(ValidationError) as exc_info:
        validate_yaml(text, validator=Validator)

    exc = exc_info.value
    assert exc.messages() == [
        Message(
            text="Must be a number.",
            code="type",
            index=["b"],
            start_position=Position(line_no=2, column_no=4, char_index=10),
            end_position=Position(line_no=2, column_no=6, char_index=12),
        )
    ]

    text = "a: 123"
    with pytest.raises(ValidationError) as exc_info:
        validate_yaml(text, validator=Validator)
    exc = exc_info.value
    assert exc.messages() == [
        Message(
            text="The field 'b' is required.",
            code="required",
            index=["b"],
            start_position=Position(line_no=1, column_no=1, char_index=0),
            end_position=Position(line_no=1, column_no=6, char_index=5),
        )
    ]
Beispiel #3
0
def test_error_messages_interface():
    """
    `errors.messages()` should return a list of Message instances.
    """
    validator = Integer()
    value, error = validator.validate_or_error("abc")
    assert error.messages() == [Message(text="Must be a number.", code="type")]
Beispiel #4
0
    def validate(self,
                 value: typing.Any,
                 *,
                 strict: bool = False) -> typing.Any:
        if value is None and self.allow_null:
            return None
        elif value is None:
            raise self.validation_error("null")
        elif not isinstance(value, list):
            raise self.validation_error("type")

        if (self.min_items is not None and self.min_items == self.max_items
                and len(value) != self.min_items):
            raise self.validation_error("exact_items")
        if self.min_items is not None and len(value) < self.min_items:
            if self.min_items == 1:
                raise self.validation_error("empty")
            raise self.validation_error("min_items")
        elif self.max_items is not None and len(value) > self.max_items:
            raise self.validation_error("max_items")

        # Ensure all items are of the right type.
        validated = []
        error_messages = []  # type: typing.List[Message]
        if self.unique_items:
            seen_items = set()  # type: typing.Set[typing.Any]

        for pos, item in enumerate(value):
            validator = None
            if isinstance(self.items, list):
                if pos < len(self.items):
                    validator = self.items[pos]
                elif isinstance(self.additional_items, Field):
                    validator = self.additional_items
            elif self.items is not None:
                validator = self.items

            if validator is None:
                validated.append(item)
            else:
                item, error = validator.validate_or_error(item, strict=strict)
                if error:
                    error_messages += error.messages(add_prefix=pos)
                else:
                    validated.append(item)

                if self.unique_items:
                    if item in seen_items:
                        text = self.get_error_text("unique_items")
                        message = Message(text=text,
                                          code="unique_items",
                                          index=[pos])
                        error_messages.append(message)
                    else:
                        seen_items.add(item)

        if error_messages:
            raise ValidationError(error_messages)

        return validated
def validate_with_positions(
        *, token: Token,
        validator: typing.Union[Field, typing.Type[Schema]]) -> typing.Any:
    try:
        return validator.validate(token.value)
    except ValidationError as error:
        messages = []
        for message in error.messages():
            if message.code == "required":
                field = message.index[-1]
                token = token.lookup(message.index[:-1])
                text = f"The field {field!r} is required."
            else:
                token = token.lookup(message.index)
                text = message.text

            positional_message = Message(
                text=text,
                code=message.code,
                index=message.index,
                start_position=token.start,
                end_position=token.end,
            )
            messages.append(positional_message)
        messages = sorted(
            messages,
            key=lambda m: m.start_position.char_index  # type: ignore
        )
        raise ValidationError(messages=messages)
Beispiel #6
0
    def validate(self,
                 value: typing.Any,
                 *,
                 strict: bool = False) -> typing.Any:
        if value is None and self.allow_null:
            return None
        elif value is None:
            raise self.validation_error("null")
        elif not isinstance(value, (dict, typing.Mapping)):
            raise self.validation_error("type")

        validated = {}
        error_messages = []

        # Ensure all property keys are strings.
        for key in value.keys():
            if not isinstance(key, str):
                text = self.get_error_text("invalid_key")
                message = Message(text=text, code="invalid_key", index=[key])
                error_messages.append(message)
            elif self.property_names is not None:
                _, error = self.property_names.validate_or_error(key)
                if error is not None:
                    text = self.get_error_text("invalid_property")
                    message = Message(text=text,
                                      code="invalid_property",
                                      index=[key])
                    error_messages.append(message)

        # Min/Max properties
        if self.min_properties is not None:
            if len(value) < self.min_properties:
                if self.min_properties == 1:
                    raise self.validation_error("empty")
                else:
                    raise self.validation_error("min_properties")
        if self.max_properties is not None:
            if len(value) > self.max_properties:
                raise self.validation_error("max_properties")

        # Required properties
        for key in self.required:
            if key not in value:
                text = self.get_error_text("required")
                message = Message(text=text, code="required", index=[key])
                error_messages.append(message)

        # Properties
        for key, child_schema in self.properties.items():
            if key not in value:
                if child_schema.has_default():
                    validated[key] = child_schema.get_default_value()
                continue
            item = value[key]
            child_value, error = child_schema.validate_or_error(item,
                                                                strict=strict)
            if not error:
                validated[key] = child_value
            else:
                error_messages += error.messages(add_prefix=key)

        # Pattern properties
        if self.pattern_properties:
            for key in list(value.keys()):
                for pattern, child_schema in self.pattern_properties.items():
                    if isinstance(key, str) and re.search(pattern, key):
                        item = value[key]
                        child_value, error = child_schema.validate_or_error(
                            item, strict=strict)
                        if not error:
                            validated[key] = child_value
                        else:
                            error_messages += error.messages(add_prefix=key)

        # Additional properties
        validated_keys = set(validated.keys())
        error_keys = set(
            [message.index[0] for message in error_messages if message.index])

        remaining = [
            key for key in value.keys()
            if key not in validated_keys | error_keys
        ]

        if self.additional_properties is True:
            for key in remaining:
                validated[key] = value[key]
        elif self.additional_properties is False:
            for key in remaining:
                text = self.get_error_text("invalid_property")
                message = Message(text=text, code="invalid_property", key=key)
                error_messages.append(message)
        elif self.additional_properties is not None:
            assert isinstance(self.additional_properties, Field)
            child_schema = self.additional_properties
            for key in remaining:
                item = value[key]
                child_value, error = child_schema.validate_or_error(
                    item, strict=strict)
                if not error:
                    validated[key] = child_value
                else:
                    error_messages += error.messages(add_prefix=key)

        if error_messages:
            raise ValidationError(messages=error_messages)

        return validated
Beispiel #7
0
def test_validate_json():
    value, messages = validate_json("")
    assert value is None
    assert messages == [
        Message(
            text="No content.",
            code="no_content",
            position=Position(line_no=1, column_no=1, char_index=0),
        )
    ]
    assert (
        repr(messages[0])
        == "Message(text='No content.', code='no_content', position=Position(line_no=1, column_no=1, char_index=0))"
    )

    value, messages = validate_json('{"a": 123}')
    assert value == {"a": 123}
    assert messages == []

    value, messages = validate_json(b'{"a": 123}')
    assert value == {"a": 123}
    assert messages == []

    value, messages = validate_json('{"a" 123}')
    assert value is None
    assert messages == [
        Message(
            text="Expecting ':' delimiter.",
            code="parse_error",
            position=Position(line_no=1, column_no=6, char_index=5),
        )
    ]

    validator = Object(properties=Integer())
    text = '{\n    "a": "123",\n    "b": "abc"}'
    value, messages = validate_json(text, validator=validator)
    assert value is None
    assert messages == [
        Message(
            text="Must be a number.",
            code="type",
            index=["b"],
            start_position=Position(line_no=3, column_no=10, char_index=27),
            end_position=Position(line_no=3, column_no=14, char_index=31),
        )
    ]
    assert (
        repr(messages[0])
        == "Message(text='Must be a number.', code='type', index=['b'], start_position=Position(line_no=3, column_no=10, char_index=27), end_position=Position(line_no=3, column_no=14, char_index=31))"
    )

    validator = Object(properties=Integer())
    text = '{\n    "a": "123",\n    "b": "456"}'
    value, messages = validate_json(text, validator=validator)
    assert value == {"a": 123, "b": 456}
    assert messages == []

    class Validator(Schema):
        a = Integer()
        b = Integer()

    text = '{\n    "a": "123",\n    "b": "abc"}'
    value, messages = validate_json(text, validator=Validator)
    assert value is None
    assert messages == [
        Message(
            text="Must be a number.",
            code="type",
            index=["b"],
            start_position=Position(line_no=3, column_no=10, char_index=27),
            end_position=Position(line_no=3, column_no=14, char_index=31),
        )
    ]

    text = '{"a": "123"}'
    value, messages = validate_json(text, validator=Validator)
    assert value is None
    assert messages == [
        Message(
            text="The field 'b' is required.",
            code="required",
            index=["b"],
            start_position=Position(line_no=1, column_no=1, char_index=0),
            end_position=Position(line_no=1, column_no=12, char_index=11),
        )
    ]
Beispiel #8
0
def test_validate_yaml():
    value, messages = validate_yaml("")
    assert value is None
    assert messages == [
        Message(
            text="No content.",
            code="no_content",
            position=Position(line_no=1, column_no=1, char_index=0),
        )
    ]

    value, messages = validate_yaml("a: 123")
    assert value == {"a": 123}
    assert messages == []

    value, messages = validate_yaml(b"a: 123")
    assert value == {"a": 123}
    assert messages == []

    value, messages = validate_yaml('{"a" 1}')
    assert value is None
    assert messages == [
        Message(
            text="expected ',' or '}', but got '<scalar>'.",
            code="parse_error",
            position=Position(line_no=1, column_no=6, char_index=5),
        )
    ]

    validator = Object(properties=Integer())
    text = "a: 123\nb: abc\n"
    value, messages = validate_yaml(text, validator=validator)
    assert value is None
    assert messages == [
        Message(
            text="Must be a number.",
            code="type",
            index=["b"],
            start_position=Position(line_no=2, column_no=4, char_index=10),
            end_position=Position(line_no=2, column_no=6, char_index=12),
        )
    ]

    validator = Object(properties=Integer())
    text = "a: 123\nb: 456\n"
    value, messages = validate_yaml(text, validator=validator)
    assert value == {"a": 123, "b": 456}
    assert messages == []

    class Validator(Schema):
        a = Integer()
        b = Integer()

    text = "a: 123\nb: abc\n"
    value, messages = validate_yaml(text, validator=Validator)
    assert value is None
    assert messages == [
        Message(
            text="Must be a number.",
            code="type",
            index=["b"],
            start_position=Position(line_no=2, column_no=4, char_index=10),
            end_position=Position(line_no=2, column_no=6, char_index=12),
        )
    ]

    text = "a: 123"
    value, messages = validate_yaml(text, validator=Validator)
    assert value is None
    assert messages == [
        Message(
            text="The field 'b' is required.",
            code="required",
            index=["b"],
            start_position=Position(line_no=1, column_no=1, char_index=0),
            end_position=Position(line_no=1, column_no=6, char_index=5),
        )
    ]
Beispiel #9
0
def validate_yaml(
    content: typing.Union[str, bytes],
    validator: typing.Union[Field, typing.Type[Schema]] = None,
) -> typing.Tuple[typing.Any, typing.List[Message]]:
    """
    Parse and validate a YAML string, returning positionally marked error
    messages on parse or validation failures.

    content - A YAML string or bytestring.
    validator - A Field instance or Schema class to validate against.

    Returns a two-tuple of (value, error_messages)
    """
    assert yaml is not None, "'pyyaml' must be installed."

    if isinstance(content, bytes):
        content = content.decode("utf-8", "ignore")

    if not content.strip():
        # Handle the empty string case explicitly for clear error messaging.
        position = Position(column_no=1, line_no=1, char_index=0)
        message = Message(
            text="No content.", code="no_content", index=None, position=position
        )
        return (None, [message])

    try:
        root_token = tokenize_yaml(content)
    except (yaml.scanner.ScannerError, yaml.parser.ParserError) as exc:  # type: ignore
        # Handle cases that result in a YAML parse error.
        position = _get_position(content, index=exc.problem_mark.index)
        message = Message(
            text=exc.problem + ".", code="parse_error", index=None, position=position
        )
        return (None, [message])

    if validator is None:
        return (root_token.value, [])

    value, error = validator.validate_or_error(root_token.value)
    if error:
        messages = []
        for message in error.messages():
            if message.code == "required":
                token = root_token.lookup(message.index[:-1])
                text = "The field '%s' is required." % message.index[-1]
            else:
                text = message.text
                token = root_token.lookup(message.index)

            positional_message = Message(
                text=text,
                code=message.code,
                index=message.index,
                start_position=token.start,
                end_position=token.end,
            )
            messages.append(positional_message)
        messages = sorted(
            messages, key=lambda m: m.start_position.char_index  # type: ignore
        )
        return (None, messages)
    return (value, [])