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:
@@ -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)
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user