mirror of
https://github.com/pi-hole/FTL.git
synced 2024-10-26 16:52:18 +02:00
Add example verification when (possibly multiple) global examples are provided below the schema level
Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
@@ -38,8 +38,10 @@ class ResponseVerifyer():
|
||||
self.errors.append("Method " + method + " not found in OpenAPI specs (" + endpoint + ")")
|
||||
return self.errors
|
||||
|
||||
# Get YAML response schema
|
||||
YAMLresponseSchema = self.openapi.paths[endpoint][method]['responses'][str(rcode)]['content']['application/json']['schema']
|
||||
# Get YAML response schema and examples (if applicable)
|
||||
jsonData = self.openapi.paths[endpoint][method]['responses'][str(rcode)]['content']['application/json']
|
||||
YAMLresponseSchema = jsonData['schema']
|
||||
YAMLresponseExamples = jsonData['examples'] if 'examples' in jsonData else None
|
||||
# Prepare required parameters (if any)
|
||||
FTLparameters = []
|
||||
if 'parameters' in self.openapi.paths[endpoint][method]:
|
||||
@@ -53,26 +55,32 @@ class ResponseVerifyer():
|
||||
continue
|
||||
FTLparameters.append(param['name'] + "=" + urllib.parse.quote_plus(str(param['example'])))
|
||||
|
||||
self.openapi.paths[endpoint][method]['responses'][str(rcode)]['content']['application/json']['schema']
|
||||
FTLresponse = self.ftl.getFTLresponse("/api" + endpoint, FTLparameters)
|
||||
if FTLresponse is None:
|
||||
return self.ftl.errors
|
||||
|
||||
if 'type' in YAMLresponseSchema and YAMLresponseSchema['type'] == 'object':
|
||||
for prop in YAMLresponseSchema['properties']:
|
||||
self.verify_property(YAMLresponseSchema['properties'], FTLresponse, [prop])
|
||||
self.verify_property(YAMLresponseSchema['properties'], YAMLresponseExamples, FTLresponse, [prop])
|
||||
|
||||
elif 'allOf' in YAMLresponseSchema and len(YAMLresponseSchema['allOf']) > 0:
|
||||
for i in range(len(YAMLresponseSchema['allOf'])):
|
||||
for prop in YAMLresponseSchema['allOf'][i]['properties']:
|
||||
self.verify_property(YAMLresponseSchema['allOf'][i]['properties'], FTLresponse, [prop])
|
||||
self.verify_property(YAMLresponseSchema['allOf'][i]['properties'], YAMLresponseExamples, FTLresponse, [prop])
|
||||
else:
|
||||
self.errors.append("Top-level response should be either an object or a non-empty allOf/anyOf/oneOf")
|
||||
|
||||
return self.errors
|
||||
|
||||
|
||||
def verify_property(self, YAMLprops: dict, FTLprops: dict, props: list):
|
||||
def verify_type(self, prop_type: any, yaml_type: str, yaml_nullable: bool):
|
||||
# None is an acceptable reply when this is specified in the API specs
|
||||
if prop_type == NoneType and yaml_nullable:
|
||||
return True
|
||||
return prop_type in self.YAML_TYPES[yaml_type]
|
||||
|
||||
|
||||
def verify_property(self, YAMLprops: dict, YAMLexamples: dict, FTLprops: dict, props: list):
|
||||
all_okay = True
|
||||
|
||||
if props[-1] not in YAMLprops:
|
||||
@@ -80,34 +88,40 @@ class ResponseVerifyer():
|
||||
return False
|
||||
YAMLprop = YAMLprops[props[-1]]
|
||||
if props[-1] not in FTLprops:
|
||||
self.errors.append("Property " + props[-1] + " missing in FTL' response")
|
||||
self.errors.append("Property " + props[-1] + " missing in FTL's response")
|
||||
return False
|
||||
FTLprop = FTLprops[props[-1]]
|
||||
|
||||
# If this is another object, we have to dive deeper
|
||||
if YAMLprop['type'] == 'object':
|
||||
for prop in YAMLprop['properties']:
|
||||
if not self.verify_property(YAMLprop['properties'], FTLprop, props + [prop]):
|
||||
if not self.verify_property(YAMLprop['properties'], YAMLexamples, FTLprop, props + [prop]):
|
||||
all_okay = False
|
||||
else:
|
||||
# Check this property
|
||||
full_path = " => ".join(props)
|
||||
yaml_type = YAMLprop['type']
|
||||
yaml_nullable = 'nullable' in YAMLprop and YAMLprop['nullable'] == True
|
||||
|
||||
# Check type of YAML example (if defined)
|
||||
if 'example' in YAMLprop:
|
||||
example_type = type(YAMLprop['example'])
|
||||
if example_type not in self.YAML_TYPES[yaml_type]:
|
||||
self.errors.append(f"API example ({str(yaml_type)}) does not match defined type ({yaml_type}) in {full_path}")
|
||||
if not self.verify_type(example_type, yaml_type, yaml_nullable):
|
||||
self.errors.append(f"API example ({str(example_type)}) does not match defined type ({yaml_type}) in {full_path}")
|
||||
return False
|
||||
elif YAMLexamples is not None:
|
||||
for t in YAMLexamples:
|
||||
example = YAMLexamples[t]['value']
|
||||
for p in props:
|
||||
example = example[p]
|
||||
example_type = type(example)
|
||||
if not self.verify_type(example_type, yaml_type, yaml_nullable):
|
||||
self.errors.append(f"API example ({str(example_type)}) does not match defined type ({yaml_type}) in {full_path}")
|
||||
return False
|
||||
|
||||
ftl_type = type(FTLprop)
|
||||
|
||||
# None is an acceptable reply when this is specified in the API specs
|
||||
if ftl_type == NoneType and 'nullable' in YAMLprop and YAMLprop['nullable'] == True:
|
||||
pass
|
||||
# Compare type of FTL's reply against what we defined in the API specs
|
||||
elif ftl_type not in self.YAML_TYPES[yaml_type]:
|
||||
ftl_type = type(FTLprop)
|
||||
if not self.verify_type(ftl_type, yaml_type, yaml_nullable):
|
||||
self.errors.append(f"FTL's reply ({str(ftl_type)}) does not match defined type ({yaml_type}) in {full_path}")
|
||||
return False
|
||||
return all_okay
|
||||
|
||||
Reference in New Issue
Block a user