Fix 'format' checking for json schema validation

In json schema, "format" keyword is defined to allow inter-operable
semantic validation for a fixed subset of values.
For example - ip address which comes as 'string' can be further checked
with ipv4 or ipv6 format.

ip_address = {
    'type': 'string',
    'oneOf': [
        {'format': 'ipv4'},
        {'format': 'ipv6'}
    ]
}

Json schema does not validate 'format' correctly if FormatChecker is not
passed during validation. Without FormatChecker, it validate all format as true.

To validate 'format' correctly FormatChecker has to be passed while doing schema
validation.

This commit fix this issue and adds unit tests for that.

Closes-Bug:1460975

Change-Id: Iad6ec9644eb1ec976b4a47f7dec127bfaecfd1fa
This commit is contained in:
ghanshyam
2015-06-09 17:20:34 +09:00
parent ad2d4e9a3a
commit f46b6c99e0
2 changed files with 173 additions and 2 deletions

View File

@@ -34,6 +34,9 @@ MAX_RECURSION_DEPTH = 2
# All the successful HTTP status codes from RFC 7231 & 4918
HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206, 207)
# JSON Schema format checker used for JSON Schema validation
FORMAT_CHECKER = jsonschema.draft4_format_checker
class RestClient(object):
"""Unified OpenStack RestClient class
@@ -805,7 +808,8 @@ class RestClient(object):
body_schema = schema.get('response_body')
if body_schema:
try:
jsonschema.validate(body, body_schema)
jsonschema.validate(body, body_schema,
format_checker=FORMAT_CHECKER)
except jsonschema.ValidationError as ex:
msg = ("HTTP response body is invalid (%s)") % ex
raise exceptions.InvalidHTTPResponseBody(msg)
@@ -818,7 +822,8 @@ class RestClient(object):
header_schema = schema.get('response_header')
if header_schema:
try:
jsonschema.validate(resp, header_schema)
jsonschema.validate(resp, header_schema,
format_checker=FORMAT_CHECKER)
except jsonschema.ValidationError as ex:
msg = ("HTTP response header is invalid (%s)") % ex
raise exceptions.InvalidHTTPResponseHeader(msg)

View File

@@ -861,3 +861,169 @@ class TestRestClientJSONHeaderSchemaValidation(TestJSONSchemaValidationBase):
self.rest_client.validate_response,
self.schema, resp, resp_body)
self.assertIn("HTTP response header is invalid", ex._error_string)
class TestRestClientJSONSchemaFormatValidation(TestJSONSchemaValidationBase):
schema = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'foo': {
'type': 'string',
'format': 'email'
}
},
'required': ['foo']
}
}
def test_validate_format_pass(self):
body = {'foo': 'example@example.com'}
self._test_validate_pass(self.schema, body)
def test_validate_format_fail(self):
body = {'foo': 'wrong_email'}
self._test_validate_fail(self.schema, body)
def test_validate_formats_in_oneOf_pass(self):
schema = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'foo': {
'type': 'string',
'oneOf': [
{'format': 'ipv4'},
{'format': 'ipv6'}
]
}
},
'required': ['foo']
}
}
body = {'foo': '10.0.0.0'}
self._test_validate_pass(schema, body)
body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'}
self._test_validate_pass(schema, body)
def test_validate_formats_in_oneOf_fail_both_match(self):
schema = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'foo': {
'type': 'string',
'oneOf': [
{'format': 'ipv4'},
{'format': 'ipv4'}
]
}
},
'required': ['foo']
}
}
body = {'foo': '10.0.0.0'}
self._test_validate_fail(schema, body)
def test_validate_formats_in_oneOf_fail_no_match(self):
schema = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'foo': {
'type': 'string',
'oneOf': [
{'format': 'ipv4'},
{'format': 'ipv6'}
]
}
},
'required': ['foo']
}
}
body = {'foo': 'wrong_ip_format'}
self._test_validate_fail(schema, body)
def test_validate_formats_in_anyOf_pass(self):
schema = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'foo': {
'type': 'string',
'anyOf': [
{'format': 'ipv4'},
{'format': 'ipv6'}
]
}
},
'required': ['foo']
}
}
body = {'foo': '10.0.0.0'}
self._test_validate_pass(schema, body)
body = {'foo': 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329'}
self._test_validate_pass(schema, body)
def test_validate_formats_in_anyOf_pass_both_match(self):
schema = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'foo': {
'type': 'string',
'anyOf': [
{'format': 'ipv4'},
{'format': 'ipv4'}
]
}
},
'required': ['foo']
}
}
body = {'foo': '10.0.0.0'}
self._test_validate_pass(schema, body)
def test_validate_formats_in_anyOf_fail_no_match(self):
schema = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'foo': {
'type': 'string',
'anyOf': [
{'format': 'ipv4'},
{'format': 'ipv6'}
]
}
},
'required': ['foo']
}
}
body = {'foo': 'wrong_ip_format'}
self._test_validate_fail(schema, body)
def test_validate_formats_pass_for_unknow_format(self):
schema = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'foo': {
'type': 'string',
'format': 'UNKNOWN'
}
},
'required': ['foo']
}
}
body = {'foo': 'example@example.com'}
self._test_validate_pass(schema, body)