Esempio n. 1
0
def tokenize_json(content):
    assert isinstance(content, (str, bytes))

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

    if not content.strip():
        message = ErrorMessage(text='No content.',
                               code='parse_error',
                               position=Position(line_no=1,
                                                 column_no=1,
                                                 index=0))
        raise ParseError(messages=[message], summary='Invalid JSON.')

    try:
        decoder = _TokenizingDecoder(content=content)
        return decoder.decode(content)
    except json.decoder.JSONDecodeError as exc:
        message = ErrorMessage(
            text=_strip_endings(exc.msg, [" starting at", " at"]) + ".",
            code='parse_error',
            position=Position(line_no=exc.lineno,
                              column_no=exc.colno,
                              index=exc.pos))
        raise ParseError(messages=[message], summary='Invalid JSON.') from None
def test_invalid_properties():
    with pytest.raises(ValidationError) as exc:
        parse_json('{"a": "abc", "b": 123}', VALIDATOR)

    error_messages = exc.value.get_error_messages()
    expecting = [
        ErrorMessage('Must be a number.', Marker(6)),
        ErrorMessage('Invalid property name.', Marker(13))
    ]
    assert error_messages == expecting
def test_invalid_top_level_item():
    with pytest.raises(ValidationError) as exc:
        parse_json('123', VALIDATOR)

    error_messages = exc.value.get_error_messages()
    expecting = [ErrorMessage('Must be an object.', Marker(0))]
    assert error_messages == expecting
def test_object_unterminated_after_value():
    with pytest.raises(ParseError) as exc:
        parse_json('{"abc": "def"', VALIDATOR)

    error_messages = exc.value.get_error_messages()
    expecting = [ErrorMessage("Expecting ',' delimiter.", Marker(13))]
    assert error_messages == expecting
def test_object_unterminated_after_key():
    with pytest.raises(ParseError) as exc:
        parse_json('{"abc": ', VALIDATOR)

    error_messages = exc.value.get_error_messages()
    expecting = [ErrorMessage("Expecting value.", Marker(8))]
    assert error_messages == expecting
def test_invalid_token():
    with pytest.raises(ParseError) as exc:
        parse_json('-', VALIDATOR)

    error_messages = exc.value.get_error_messages()
    expecting = [ErrorMessage("Expecting value.", Marker(0))]
    assert error_messages == expecting
def test_object_missing_comma_delimiter():
    with pytest.raises(ParseError) as exc:
        parse_json('{"abc": "def" 1', VALIDATOR)

    error_messages = exc.value.get_error_messages()
    expecting = [ErrorMessage("Expecting ',' delimiter.", Marker(14))]
    assert error_messages == expecting
Esempio n. 8
0
def test_invalid_property():
    with pytest.raises(ValidationError) as exc:
        parse_json('{"a": "abc"}', VALIDATOR)

    error_messages = exc.value.get_error_messages()
    expecting = [ErrorMessage("Must be a number.", Marker(6))]
    assert error_messages == expecting
def test_empty_string():
    with pytest.raises(ParseError) as exc:
        parse_json(b'', VALIDATOR)

    error_messages = exc.value.get_error_messages()
    expecting = [ErrorMessage('No content.', Marker(0))]
    assert error_messages == expecting
Esempio n. 10
0
def test_invalid_properties():
    with pytest.raises(ValidationError) as exc:
        apistar.parse('{"a": "abc", "b": 123}', encoding="json", validator=VALIDATOR)

    assert exc.value.messages == [
        ErrorMessage(
            text='Must be a number.',
            code='type',
            index=['a'],
            position=Position(line_no=1, column_no=7, index=6)
        ),
        ErrorMessage(
            text='Invalid property name.',
            code='invalid_property',
            index=['b'],
            position=Position(line_no=1, column_no=14, index=13)
        )
    ]
Esempio n. 11
0
def test_object_invalid_property_name():
    with pytest.raises(ParseError) as exc:
        parse_json('{"abc": "def", 1', VALIDATOR)

    error_messages = exc.value.get_error_messages()
    expecting = [
        ErrorMessage("Expecting property name enclosed in double quotes.",
                     Marker(15))
    ]
    assert error_messages == expecting
Esempio n. 12
0
 def decode(self, bytestring, **options):
     try:
         data = json.loads(bytestring.decode('utf-8'),
                           object_pairs_hook=dict_type)
     except ValueError as exc:
         message = ErrorMessage(text='Malformed JSON. %s' % exc,
                                code='parse_failed')
         raise ParseError(messages=[message]) from None
     jsonschema = JSON_SCHEMA.validate(data)
     return decode(jsonschema)
Esempio n. 13
0
def test_object_missing_property_name():
    with pytest.raises(ParseError) as exc:
        parse_json('{', VALIDATOR)

    error_messages = exc.value.get_error_messages()
    expecting = [
        ErrorMessage('Expecting property name enclosed in double quotes.',
                     Marker(1))
    ]
    assert error_messages == expecting
Esempio n. 14
0
def test_unterminated_string():
    with pytest.raises(ParseError) as exc:
        apistar.parse('"ab', encoding='json')

    assert exc.value.messages == [
        ErrorMessage(
            text="Unterminated string.",
            code='parse_error',
            position=Position(line_no=1, column_no=1, index=0)
        )
    ]
Esempio n. 15
0
def test_object_invalid_property_name():
    with pytest.raises(ParseError) as exc:
        apistar.parse('{"abc": "def", 1', encoding='json')

    assert exc.value.messages == [
        ErrorMessage(
            text="Expecting property name enclosed in double quotes.",
            code='parse_error',
            position=Position(line_no=1, column_no=16, index=15)
        )
    ]
Esempio n. 16
0
def test_object_missing_comma_delimiter():
    with pytest.raises(ParseError) as exc:
        apistar.parse('{"abc": "def" 1', encoding='json')

    assert exc.value.messages == [
        ErrorMessage(
            text="Expecting ',' delimiter.",
            code='parse_error',
            position=Position(line_no=1, column_no=15, index=14)
        )
    ]
Esempio n. 17
0
def test_object_missing_property_name():
    with pytest.raises(ParseError) as exc:
        apistar.parse('{', encoding='json')

    assert exc.value.messages == [
        ErrorMessage(
            text='Expecting property name enclosed in double quotes.',
            code='parse_error',
            position=Position(line_no=1, column_no=2, index=1)
        )
    ]
Esempio n. 18
0
def test_empty_string():
    with pytest.raises(ParseError) as exc:
        apistar.parse(b'', encoding='json')

    assert exc.value.messages == [
        ErrorMessage(
            text='No content.',
            code='parse_error',
            position=Position(line_no=1, column_no=1, index=0)
        )
    ]
Esempio n. 19
0
def test_missing_required_property():
    with pytest.raises(ValidationError) as exc:
        apistar.parse('{}', encoding="json", validator=VALIDATOR)

    assert exc.value.messages == [
        ErrorMessage(
            text='The "a" field is required.',
            code='required',
            index=['a'],
            position=Position(line_no=1, column_no=1, index=0))
    ]
Esempio n. 20
0
def test_object_unterminated_after_value():
    with pytest.raises(ParseError) as exc:
        apistar.parse('{"abc": "def"', encoding='json')

    assert exc.value.messages == [
        ErrorMessage(
            text="Expecting ',' delimiter.",
            code='parse_error',
            position=Position(line_no=1, column_no=14, index=13)
        )
    ]
Esempio n. 21
0
def test_invalid_token():
    with pytest.raises(ParseError) as exc:
        apistar.parse('-', encoding='json')

    assert exc.value.messages == [
        ErrorMessage(
            text="Expecting value.",
            code='parse_error',
            position=Position(line_no=1, column_no=1, index=0)
        )
    ]
Esempio n. 22
0
def test_invalid_top_level_item():
    with pytest.raises(ValidationError) as exc:
        apistar.parse('123', encoding="json", validator=VALIDATOR)

    assert exc.value.messages == [
        ErrorMessage(
            text='Must be an object.',
            code='type',
            index=None,
            position=Position(line_no=1, column_no=1, index=0)
        )
    ]
Esempio n. 23
0
def parse(content, encoding=None, validator=None):
    if encoding not in (None, "json", "yaml"):
        raise ValueError('encoding must be either "json" or "yaml"')

    if encoding is None:
        if INFER_YAML.match(content):
            encoding = "yaml"
        elif INFER_JSON.match(content):
            encoding = "json"
        else:
            message = ErrorMessage(
                text=
                "Unable to guess if encoding is JSON or YAML. Use the 'encoding' argument.",
                code="unknown_encoding")
            raise ValidationError(messages=[message])

    if encoding == "json":
        token = tokenize_json(content)
    else:
        token = tokenize_yaml(content)

    value = token.get_value()

    if validator is not None:
        try:
            value = validator.validate(value)
        except ValidationError as exc:
            for message in exc.messages:
                if message.code == 'required':
                    message.position = token.lookup_position(
                        message.index[:-1])
                elif message.code in ['invalid_property', 'invalid_key']:
                    message.position = token.lookup_key_position(message.index)
                else:
                    message.position = token.lookup_position(message.index)
            exc.messages = sorted(exc.messages, key=lambda x: x.position.index)
            raise exc

    return (value, token)
Esempio n. 24
0
def tokenize_yaml(content):
    class CustomLoader(SafeLoader):
        pass

    def construct_mapping(loader, node):
        start = node.start_mark.index
        end = node.end_mark.index
        mapping = loader.construct_mapping(node)
        return DictToken(mapping, start, end - 1, content=content)

    def construct_sequence(loader, node):
        start = node.start_mark.index
        end = node.end_mark.index
        value = loader.construct_sequence(node)
        return ListToken(value, start, end - 1, content=content)

    def construct_scalar(loader, node):
        start = node.start_mark.index
        end = node.end_mark.index
        value = loader.construct_scalar(node)
        return ScalarToken(value, start, end - 1, content=content)

    def construct_int(loader, node):
        start = node.start_mark.index
        end = node.end_mark.index
        value = loader.construct_yaml_int(node)
        return ScalarToken(value, start, end - 1, content=content)

    def construct_float(loader, node):
        start = node.start_mark.index
        end = node.end_mark.index
        value = loader.construct_yaml_float(node)
        return ScalarToken(value, start, end - 1, content=content)

    def construct_bool(loader, node):
        start = node.start_mark.index
        end = node.end_mark.index
        value = loader.construct_yaml_bool(node)
        return ScalarToken(value, start, end - 1, content=content)

    def construct_null(loader, node):
        start = node.start_mark.index
        end = node.end_mark.index
        value = loader.construct_yaml_null(node)
        return ScalarToken(value, start, end - 1, content=content)

    CustomLoader.add_constructor(
        yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, construct_mapping)

    CustomLoader.add_constructor(
        yaml.resolver.BaseResolver.DEFAULT_SEQUENCE_TAG, construct_sequence)

    CustomLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_SCALAR_TAG,
                                 construct_scalar)

    CustomLoader.add_constructor('tag:yaml.org,2002:int', construct_int)

    CustomLoader.add_constructor('tag:yaml.org,2002:float', construct_float)

    CustomLoader.add_constructor('tag:yaml.org,2002:bool', construct_bool)

    CustomLoader.add_constructor('tag:yaml.org,2002:null', construct_null)

    assert isinstance(content, (str, bytes))

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

    if not content.strip():
        message = ErrorMessage(text='No content.',
                               code='parse_error',
                               position=Position(line_no=1,
                                                 column_no=1,
                                                 index=0))
        raise ParseError(errors=[message], summary='Invalid YAML.')

    try:
        return yaml.load(content, CustomLoader)
    except (yaml.scanner.ScannerError, yaml.parser.ParserError) as exc:
        index = getattr(exc, 'index', 0)
        message = ErrorMessage(text=exc.problem + ".",
                               code='parse_error',
                               position=_get_position(content, index=index))
        raise ParseError(messages=[message], summary='Invalid YAML.') from None
Esempio n. 25
0
def validate(schema, format=None, encoding=None):
    if format not in FORMAT_CHOICES:
        raise ValueError('format must be one of %s' % FORMAT_CHOICES)

    if isinstance(schema, (str, bytes)):
        value, token = parse(schema, encoding)
    elif isinstance(schema, dict):
        if encoding is not None:
            raise ValueError('encoding must be `None`.')
        value, token = schema, None
    else:
        raise ValueError(
            'schema must either be a dict, or a string/bytestring.')

    if format is None:
        if isinstance(value,
                      dict) and "openapi" in value and "swagger" not in value:
            format = "openapi"
        elif isinstance(
                value, dict) and "swagger" in value and "openapi" not in value:
            format = "swagger"
        else:
            message = ErrorMessage(
                text=
                "Unable to determine schema format. Use the 'format' argument.",
                code='unknown_format')
            raise ValidationError(messages=[message])

    validator = {
        'config': APISTAR_CONFIG,
        'jsonschema': JSON_SCHEMA,
        'openapi': OPEN_API,
        'swagger': SWAGGER
    }[format]

    if validator is not None:
        try:
            value = validator.validate(value)
        except ValidationError as exc:
            exc.summary = {
                'config': 'Invalid configuration file.',
                'jsonschema': 'Invalid JSONSchema document.',
                'openapi': 'Invalid OpenAPI schema.',
                'swagger': 'Invalid Swagger schema.',
            }[format]
            if token is not None:
                for message in exc.messages:
                    if message.code == 'required':
                        message.position = token.lookup_position(
                            message.index[:-1])
                    elif message.code in ['invalid_property', 'invalid_key']:
                        message.position = token.lookup_key_position(
                            message.index)
                    else:
                        message.position = token.lookup_position(message.index)
                exc.messages = sorted(exc.messages,
                                      key=lambda x: x.position.index)
            raise exc

    if format in ['openapi', 'swagger']:
        decoder = {'openapi': OpenAPI, 'swagger': Swagger}[format]
        value = decoder().load(value)

    return value
Esempio n. 26
0
    def validate(self, value, definitions=None, allow_coerce=False):
        if value is None and self.allow_null:
            return None
        elif value is None:
            self.error('null')
        elif not isinstance(value, list):
            self.error('type')

        definitions = self.get_definitions(definitions)
        validated = []

        if self.min_items is not None and self.min_items == self.max_items and len(value) != self.min_items:
            self.error('exact_items')
        if self.min_items is not None and len(value) < self.min_items:
            if self.min_items == 1:
                self.error('empty')
            self.error('min_items')
        elif self.max_items is not None and len(value) > self.max_items:
            self.error('max_items')
        elif isinstance(self.items, list) and (self.additional_items is False) and len(value) > len(self.items):
            self.error('additional_items')

        # Ensure all items are of the right type.
        errors = {}
        if self.unique_items:
            seen_items = Uniqueness()

        for pos, item in enumerate(value):
            try:
                if isinstance(self.items, list):
                    if pos < len(self.items):
                        item = self.items[pos].validate(
                            item,
                            definitions=definitions,
                            allow_coerce=allow_coerce
                        )
                    elif isinstance(self.additional_items, Validator):
                        item = self.additional_items.validate(
                            item,
                            definitions=definitions,
                            allow_coerce=allow_coerce
                        )
                elif self.items is not None:
                    item = self.items.validate(
                        item,
                        definitions=definitions,
                        allow_coerce=allow_coerce
                    )

                if self.unique_items:
                    if item in seen_items:
                        self.error('unique_items')
                    else:
                        seen_items.add(item)

                validated.append(item)
            except ValidationError as exc:
                errors[pos] = exc.messages

        if errors:
            error_messages = []
            for key, messages in errors.items():
                for message in messages:
                    index = [key] if message.index is None else [key] + message.index
                    error_message = ErrorMessage(message.text, message.code, index)
                    error_messages.append(error_message)
            raise ValidationError(error_messages)

        return validated
Esempio n. 27
0
    def validate(self, value, definitions=None, allow_coerce=False):
        if value is None and self.allow_null:
            return None
        elif value is None:
            self.error('null')
        elif not isinstance(value, (dict, typing.Mapping)):
            self.error('type')

        definitions = self.get_definitions(definitions)
        validated = dict_type()

        # Ensure all property keys are strings.
        errors = {}
        for key in value.keys():
            if not isinstance(key, str):
                errors[key] = [self.error_message('invalid_key')]

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

        # Required properties
        for key in self.required:
            if key not in value:
                errors[key] = [self.error_message('required', field_name=key)]

        # Properties
        for key, child_schema in self.properties.items():
            if key not in value:
                if child_schema.has_default():
                    validated[key] = child_schema.default
                continue
            item = value[key]
            try:
                validated[key] = child_schema.validate(
                    item,
                    definitions=definitions,
                    allow_coerce=allow_coerce
                )
            except ValidationError as exc:
                errors[key] = exc.messages

        # 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]
                        try:
                            validated[key] = child_schema.validate(
                                item, definitions=definitions,
                                allow_coerce=allow_coerce
                            )
                        except ValidationError as exc:
                            errors[key] = exc.messages

        # Additional properties
        remaining = [
            key for key in value.keys()
            if key not in set(validated.keys()) | set(errors.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:
                errors[key] = [self.error_message('invalid_property')]
        elif self.additional_properties is not None:
            child_schema = self.additional_properties
            for key in remaining:
                item = value[key]
                try:
                    validated[key] = child_schema.validate(
                        item,
                        definitions=definitions,
                        allow_coerce=allow_coerce
                    )
                except ValidationError as exc:
                    errors[key] = exc.messages

        if errors:
            error_messages = []
            for key, messages in errors.items():
                for message in messages:
                    index = [key] if message.index is None else [key] + message.index
                    error_message = ErrorMessage(message.text, message.code, index)
                    error_messages.append(error_message)
            raise ValidationError(error_messages)

        return validated
Esempio n. 28
0
 def error_message(self, code, **context):
     text = self.errors[code].format(**self.__dict__, **context)
     return ErrorMessage(text=text, code=code)