import itertools import json import re from collections import defaultdict import yaml def parse_swagger_and_generate_dependencies(swagger_json_path, lifecycle_yaml_path): # Load Swagger JSON with open(swagger_json_path, "r") as f: swagger = json.load(f) # Load lifecycle YAML with open(lifecycle_yaml_path, "r") as f: lifecycle = yaml.safe_load(f) # Parse paths and operations services = defaultdict(list) for path, path_items in swagger["paths"].items(): for method, operation in path_items.items(): operation_id = operation["operationId"] service_name = operation["tags"][0] services[service_name].append( {"id": operation_id, "path": path, "method": method, "dependencies": []} ) # Resolve dependencies for service_name, operations in services.items(): for operation in operations: path_parts = operation["path"].split("/") for i in range(len(path_parts)): if "{" in path_parts[i]: potential_dependency_path = "/".join(path_parts[:i]) dependency = find_dependency(services, potential_dependency_path) if dependency: operation["dependencies"].append(dependency) # Generate output YAML output = {"services": []} for service_name, operations in services.items(): sorted_ops = sort_operations(operations, lifecycle) cleaned_ops = [] for op in sorted_ops: cleaned_op = {"id": op["id"]} if op.get("dependencies"): cleaned_op["dependencies"] = op["dependencies"] cleaned_ops.append(cleaned_op) service = {"name": service_name, "operations": cleaned_ops} output["services"].append(service) # Custom YAML dumper to handle the formatting class CustomDumper(yaml.SafeDumper): def increase_indent(self, flow=False, indentless=False): return super(CustomDumper, self).increase_indent(flow, False) return yaml.dump( output, Dumper=CustomDumper, default_flow_style=False, sort_keys=False ) def normalize_path(path): """ Normalize the path by replacing path parameters with a placeholder. For example, '/clusters/{cluster_id}' becomes '/clusters/{}' """ return re.sub(r"{[^}]*}", "{}", path) def find_dependency(services, path): post_operation = None get_operation = None for service_operations in services.values(): for operation in service_operations: if normalize_path(operation["path"]) == normalize_path(path): if operation["method"] == "post": post_operation = operation["id"] elif operation["method"] == "get" and not post_operation: get_operation = operation["id"] return post_operation or get_operation def get_lifecycle_index(operation_id, lifecycle): operation_name = operation_id.split("_")[-1].lower() for i, lifecycle_step in enumerate(lifecycle): if lifecycle_step.lower() in operation_name: return i return len(lifecycle) # If not found, put at the end def sort_operations(operations, lifecycle): # Group operations by number of dependencies grouped_by_deps = itertools.groupby( sorted(operations, key=lambda op: len(op.get("dependencies", []))), key=lambda op: len(op.get("dependencies", [])), ) result = [] for _, group in grouped_by_deps: # Sort operations within this dependency group sorted_group = sorted( group, key=lambda op: (get_lifecycle_index(op["id"], lifecycle), op["id"]) ) result.extend(sorted_group) return result # Usage if __name__ == "__main__": swagger_json_path = "/Users/sunyishen/PingCAP/repos/SwaggerTestGen/config/services.swagger_5d2ef05.json" lifecycle_yaml_path = ( "/Users/sunyishen/PingCAP/repos/SwaggerTestGen/config/biz_lifecycle.yaml" ) output_yaml = parse_swagger_and_generate_dependencies( swagger_json_path, lifecycle_yaml_path ) print(output_yaml)