Last active
February 17, 2022 11:01
-
-
Save alexrashed/54e07fc1a06d96b921ca13251c489cbf to your computer and use it in GitHub Desktop.
Script to test botocore service spec service and shape assumptions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import cast, Callable | |
from botocore.model import StructureShape, ListShape, Shape, MapShape, ServiceModel | |
from localstack.aws.spec import list_services | |
def traverse_members(service: ServiceModel, shape: Shape, fun: Callable, traversed_shapes=None, hierarchy=0): | |
if traversed_shapes is None: | |
traversed_shapes = [] | |
if shape.name in traversed_shapes: | |
return | |
else: | |
traversed_shapes.append(shape.name) | |
fun(service, shape, hierarchy) | |
if shape.type_name in ["list", "structure", "map"]: | |
hierarchy += 1 | |
if shape.type_name == "list": | |
shape = cast(ListShape, shape) | |
traverse_members(service, shape.member, fun, traversed_shapes, hierarchy) | |
elif shape.type_name == "structure": | |
shape = cast(StructureShape, shape) | |
for _, member_shape in shape.members.items(): | |
traverse_members(service, member_shape, fun, traversed_shapes, hierarchy) | |
elif shape.type_name == "map": | |
shape = cast(MapShape, shape) | |
traverse_members(service, shape.key, fun, traversed_shapes, hierarchy) | |
traverse_members(service, shape.value, fun, traversed_shapes, hierarchy) | |
def main(): | |
def test_shape_assumptions(current_service: ServiceModel, current_shape: Shape, hierarchy: int): | |
if current_shape.serialization.get("location"): | |
# test the assumptions: | |
# - all location traits are defined in the root structure | |
if hierarchy > 1: | |
raise Exception(f"Non root location trait: {current_service}/{current_shape}") | |
# - all location traits are defined in services which have a rest protocol | |
if current_service.protocol not in ["rest-xml", "rest-json"]: | |
raise Exception(f"Non-REST service with location trait: {current_service}") | |
# - maps within the header (location trait is "headers") only contain string values | |
if current_shape.serialization.get("location") == "headers": | |
if current_shape.type_name == "map": | |
current_shape = cast(MapShape, current_shape) | |
if not (current_shape.key.type_name == current_shape.value.type_name == "string"): | |
raise Exception(f"Non-string headers location trait shape: {current_service}/{current_shape}") | |
elif current_shape.type_name == "structure": | |
current_shape = cast(StructureShape, current_shape) | |
for _, member_shape in current_shape.members.items(): | |
if member_shape.type_name != "string": | |
raise Exception(f"Non-string headers location trait shape: {current_service}/{current_shape}") | |
elif current_shape.type_name == "list": | |
current_shape = cast(ListShape, current_shape) | |
if current_shape.member.type_name != "string": | |
raise Exception(f"Non-string headers location trait shape: {current_service}/{current_shape}") | |
if current_shape.serialization.get("payload") and current_service.protocol not in ["rest-xml", "rest-json"]: | |
raise Exception(f"Payload in non-rest service: {current_service}") | |
services = list_services() | |
for service in services: | |
for shape_name in service.shape_names: | |
shape = service.shape_for(shape_name) | |
traverse_members(service, shape, test_shape_assumptions) | |
print("All good...") | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment