diff --git a/mitmproxy2swagger/swagger_util.py b/mitmproxy2swagger/swagger_util.py index 3afeb00..32a4ae4 100644 --- a/mitmproxy2swagger/swagger_util.py +++ b/mitmproxy2swagger/swagger_util.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import urllib +import uuid from typing import Any, List VERBS = [ @@ -127,10 +128,11 @@ def value_to_schema(value): return {"type": "array", "items": value_to_schema(value[0])} # check if value is a dict elif isinstance(value, dict): - all_keys_are_generic = all( - isinstance(key, str) and key.isnumeric() for key in value - ) - if all_keys_are_generic and len(value) > 0: + all_keys_are_numeric = all(is_numeric_string(key) for key in value) + all_keys_are_uuid = all(is_uuid(key) for key in value) + keys_are_generic = all_keys_are_numeric or all_keys_are_uuid + + if keys_are_generic and len(value) > 0: return { "type": "object", "additionalProperties": value_to_schema(list(value.values())[0]), @@ -144,6 +146,22 @@ def value_to_schema(value): return {"type": "object"} +def is_uuid(key): + return isinstance(key, str) and is_valid_uuid(key) + + +def is_numeric_string(key): + return isinstance(key, str) and key.isnumeric() + + +def is_valid_uuid(val): + try: + uuid.UUID(str(val)) + return True + except ValueError: + return False + + MAX_EXAMPLE_ARRAY_ELEMENTS = 10 MAX_EXAMPLE_OBJECT_PROPERTIES = 150 diff --git a/mitmproxy2swagger/test_mitmproxy2swagger.py b/mitmproxy2swagger/test_mitmproxy2swagger.py index 38b8c66..67e7030 100644 --- a/mitmproxy2swagger/test_mitmproxy2swagger.py +++ b/mitmproxy2swagger/test_mitmproxy2swagger.py @@ -67,17 +67,46 @@ def test_mitmproxy2swagger_generates_swagger_from_mitmproxy_flow_file_with_gener assert ( get_nested_key( data, - "paths./.post.responses.200.content.application/json.schema.properties", + "paths./.post.responses.200.content.application/json.schema.properties.numeric.properties", ) is None ) assert ( get_nested_key( data, - "paths./.post.responses.200.content.application/json.schema.additionalProperties", + "paths./.post.responses.200.content.application/json.schema.properties.uuid.properties", + ) + is None + ) + assert ( + get_nested_key( + data, + "paths./.post.responses.200.content.application/json.schema.properties.numeric.additionalProperties", ) is not None ) + assert ( + get_nested_key( + data, + "paths./.post.responses.200.content.application/json.schema.properties.numeric.additionalProperties", + ) + is not None + ) + + assert ( + get_nested_key( + data, + "paths./.post.responses.200.content.application/json.schema.properties.mixed.properties", + ) + is not None + ) + assert ( + get_nested_key( + data, + "paths./.post.responses.200.content.application/json.schema.properties.mixed.additionalProperties", + ) + is None + ) def test_mitmproxy2swagger_generates_headers_for_flow_files(): diff --git a/testdata/generic_keys_flows b/testdata/generic_keys_flows index 479f9cc..ba25984 100644 --- a/testdata/generic_keys_flows +++ b/testdata/generic_keys_flows @@ -1 +1 @@ -2333:9:websocket;0:~8:response;539:6:reason;2:OK,11:status_code;3:200#13:timestamp_end;17:1709845739.967148^15:timestamp_start;17:1709845739.966887^8:trailers;0:~7:content;200:{"1234": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}, "5678": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}, "789": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}},7:headers;155:39:6:Server,26:BaseHTTP/0.6 Python/3.11.7,]40:4:Date,29:Thu, 07 Mar 2024 21:08:59 GMT,]36:12:Content-type,16:application/json,]24:14:Content-length,3:200,]]12:http_version;8:HTTP/1.0,}7:request;607:4:path;1:/,9:authority;0:,6:scheme;4:http,6:method;4:POST,4:port;4:8082#4:host;9:localhost;13:timestamp_end;18:1709845739.9647229^15:timestamp_start;17:1709845739.964001^8:trailers;0:~7:content;134:{"1234": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}, "5678": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}},7:headers;232:25:4:Host,14:localhost:8082,]40:10:User-Agent,22:python-requests/2.31.0,]36:15:Accept-Encoding,13:gzip, deflate,]15:6:Accept,3:*/*,]28:10:Connection,10:keep-alive,]36:12:Content-Type,16:application/json,]24:14:Content-Length,3:134,]]12:http_version;8:HTTP/1.1,}6:backup;0:~17:timestamp_created;17:1709845739.964161^7:comment;0:;8:metadata;0:}6:marked;0:;9:is_replay;0:~11:intercepted;5:false!11:server_conn;464:3:via;0:~19:timestamp_tcp_setup;17:1709845739.966096^7:address;19:9:localhost;4:8082#]19:timestamp_tls_setup;0:~13:timestamp_end;17:1709845739.967722^15:timestamp_start;16:1709845739.96509^3:sni;0:~11:tls_version;0:~11:cipher_list;0:]6:cipher;0:~11:alpn_offers;0:]4:alpn;0:~16:certificate_list;0:]3:tls;5:false!5:error;0:~18:transport_protocol;3:tcp;2:id;36:f0fd10d5-8f3b-4687-81c0-c1dd5b765a1c;8:sockname;20:9:127.0.0.1;5:59865#]8:peername;19:9:127.0.0.1;4:8082#]}11:client_conn;421:10:proxy_mode;7:regular;8:mitmcert;0:~19:timestamp_tls_setup;0:~13:timestamp_end;17:1709845739.967637^15:timestamp_start;17:1709845739.962744^3:sni;0:~11:tls_version;0:~11:cipher_list;0:]6:cipher;0:~11:alpn_offers;0:]4:alpn;0:~16:certificate_list;0:]3:tls;5:false!5:error;0:~18:transport_protocol;3:tcp;2:id;36:91886e5c-85ef-4509-b320-7c9345ce3c45;8:sockname;21:3:::1;4:8080#1:0#1:0#]8:peername;22:3:::1;5:59863#1:0#1:0#]}5:error;0:~2:id;36:960bd3ff-4193-46f6-914f-6ea1d66336d1;4:type;4:http;7:version;2:20#} \ No newline at end of file +3299:9:websocket;0:~8:response;1105:6:reason;2:OK,11:status_code;3:200#13:timestamp_end;18:1709848351.4360309^15:timestamp_start;17:1709848351.435767^8:trailers;0:~7:content;765:{"numeric": {"1234": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}, "5678": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}, "0000": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}}, "uuid": {"123e4567-e89b-12d3-a456-426614174000": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}, "123e4567-e89b-12d3-a456-426614174001": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}, "123e4567-e89b-12d3-a456-426614174002": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}}, "mixed": {"1234": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}, "123e4567-e89b-12d3-a456-426614174000": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}, "0000": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}}},7:headers;155:39:6:Server,26:BaseHTTP/0.6 Python/3.11.7,]40:4:Date,29:Thu, 07 Mar 2024 21:52:31 GMT,]36:12:Content-type,16:application/json,]24:14:Content-length,3:765,]]12:http_version;8:HTTP/1.0,}7:request;1003:4:path;1:/,9:authority;0:,6:scheme;4:http,6:method;4:POST,4:port;4:8082#4:host;9:localhost;13:timestamp_end;17:1709848351.433835^15:timestamp_start;16:1709848351.43342^8:trailers;0:~7:content;532:{"numeric": {"1234": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}, "5678": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}}, "uuid": {"123e4567-e89b-12d3-a456-426614174000": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}, "123e4567-e89b-12d3-a456-426614174001": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}}, "mixed": {"1234": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}, "123e4567-e89b-12d3-a456-426614174000": {"lorem": "ipsum", "dolor": "sit", "amet": "consectetur"}}},7:headers;232:25:4:Host,14:localhost:8082,]40:10:User-Agent,22:python-requests/2.31.0,]36:15:Accept-Encoding,13:gzip, deflate,]15:6:Accept,3:*/*,]28:10:Connection,10:keep-alive,]36:12:Content-Type,16:application/json,]24:14:Content-Length,3:532,]]12:http_version;8:HTTP/1.1,}6:backup;0:~17:timestamp_created;17:1709848351.433495^7:comment;0:;8:metadata;0:}6:marked;0:;9:is_replay;0:~11:intercepted;5:false!11:server_conn;466:3:via;0:~19:timestamp_tcp_setup;18:1709848351.4350889^7:address;19:9:localhost;4:8082#]19:timestamp_tls_setup;0:~13:timestamp_end;17:1709848351.436529^15:timestamp_start;17:1709848351.434129^3:sni;0:~11:tls_version;0:~11:cipher_list;0:]6:cipher;0:~11:alpn_offers;0:]4:alpn;0:~16:certificate_list;0:]3:tls;5:false!5:error;0:~18:transport_protocol;3:tcp;2:id;36:48b3aee0-862d-4ed5-b160-594334eaaa19;8:sockname;20:9:127.0.0.1;5:61640#]8:peername;19:9:127.0.0.1;4:8082#]}11:client_conn;421:10:proxy_mode;7:regular;8:mitmcert;0:~19:timestamp_tls_setup;0:~13:timestamp_end;17:1709848351.436451^15:timestamp_start;17:1709848351.432699^3:sni;0:~11:tls_version;0:~11:cipher_list;0:]6:cipher;0:~11:alpn_offers;0:]4:alpn;0:~16:certificate_list;0:]3:tls;5:false!5:error;0:~18:transport_protocol;3:tcp;2:id;36:50acece3-e5e3-460f-b761-be89aa458880;8:sockname;21:3:::1;4:8080#1:0#1:0#]8:peername;22:3:::1;5:61638#1:0#1:0#]}5:error;0:~2:id;36:14ae0961-3df8-41b0-8ddb-510082083178;4:type;4:http;7:version;2:20#} \ No newline at end of file diff --git a/testdata/generic_keys_testclient.py b/testdata/generic_keys_testclient.py index eda79bf..8b178c7 100644 --- a/testdata/generic_keys_testclient.py +++ b/testdata/generic_keys_testclient.py @@ -6,15 +6,41 @@ import requests # type: ignore # Sample data data = { - "1234": { - "lorem": "ipsum", - "dolor": "sit", - "amet": "consectetur", + "numeric": { + "1234": { + "lorem": "ipsum", + "dolor": "sit", + "amet": "consectetur", + }, + "5678": { + "lorem": "ipsum", + "dolor": "sit", + "amet": "consectetur", + }, }, - "5678": { - "lorem": "ipsum", - "dolor": "sit", - "amet": "consectetur", + "uuid": { + "123e4567-e89b-12d3-a456-426614174000": { + "lorem": "ipsum", + "dolor": "sit", + "amet": "consectetur", + }, + "123e4567-e89b-12d3-a456-426614174001": { + "lorem": "ipsum", + "dolor": "sit", + "amet": "consectetur", + }, + }, + "mixed": { + "1234": { + "lorem": "ipsum", + "dolor": "sit", + "amet": "consectetur", + }, + "123e4567-e89b-12d3-a456-426614174000": { + "lorem": "ipsum", + "dolor": "sit", + "amet": "consectetur", + }, }, } diff --git a/testdata/generic_keys_testserver.py b/testdata/generic_keys_testserver.py index 969311a..a83a4d1 100644 --- a/testdata/generic_keys_testserver.py +++ b/testdata/generic_keys_testserver.py @@ -15,7 +15,17 @@ class GenericKeysHandler(http.server.BaseHTTPRequestHandler): print(raw_data) data = json.loads(raw_data) - data["789"] = { + data["numeric"]["0000"] = { + "lorem": "ipsum", + "dolor": "sit", + "amet": "consectetur", + } + data["uuid"]["123e4567-e89b-12d3-a456-426614174002"] = { + "lorem": "ipsum", + "dolor": "sit", + "amet": "consectetur", + } + data["mixed"]["0000"] = { "lorem": "ipsum", "dolor": "sit", "amet": "consectetur",