1+ import copy
2+ import json
13import os
24import re
35from collections import defaultdict
46from datetime import datetime
7+ from typing import Any , Dict , Optional , Tuple
58import yaml
69from hackingBuddyGPT .capabilities .yamlFile import YAMLFile
7- from hackingBuddyGPT .usecases . web_api_testing . documentation .pattern_matcher import PatternMatcher
10+ from hackingBuddyGPT .utils . web_api .pattern_matcher import PatternMatcher
811from hackingBuddyGPT .utils .prompt_generation .information import PromptStrategy
9- from hackingBuddyGPT .usecases .web_api_testing .response_processing import ResponseHandler
10- from hackingBuddyGPT .usecases .web_api_testing .utils import LLMHandler
12+ from hackingBuddyGPT .utils .web_api .llm_handler import LLMHandler
1113
1214
1315class OpenAPISpecificationHandler (object ):
1416 """
1517 Handles the generation and updating of an OpenAPI specification document based on dynamic API responses.
1618
1719 Attributes:
18- response_handler (object): An instance of the response handler for processing API responses.
1920 schemas (dict): A dictionary to store API schemas.
2021 filename (str): The filename for the OpenAPI specification file.
2122 openapi_spec (dict): The OpenAPI specification document structure.
@@ -26,18 +27,16 @@ class OpenAPISpecificationHandler(object):
2627 _capabilities (dict): A dictionary to store capabilities related to YAML file handling.
2728 """
2829
29- def __init__ (self , llm_handler : LLMHandler , response_handler : ResponseHandler , strategy : PromptStrategy , url : str ,
30+ def __init__ (self , llm_handler : LLMHandler , strategy : PromptStrategy , url : str ,
3031 description : str , name : str ) -> None :
3132 """
3233 Initializes the handler with a template OpenAPI specification.
3334
3435 Args:
3536 llm_handler (object): An instance of the LLM handler for interacting with the LLM.
36- response_handler (object): An instance of the response handler for processing API responses.
3737 strategy (PromptStrategy): An instance of the PromptStrategy class.
3838 """
3939 self .unsuccessful_methods = {}
40- self .response_handler = response_handler
4140 self .schemas = {}
4241 self .query_params = {}
4342 self .endpoint_methods = {}
@@ -103,6 +102,143 @@ def is_partial_match(self, element, string_list):
103102
104103 return False
105104
105+ def parse_http_response_to_openapi_example (
106+ self , openapi_spec : Dict [str , Any ], http_response : str , path : str , method : str
107+ ) -> Tuple [Optional [Dict [str , Any ]], Optional [str ], Dict [str , Any ]]:
108+ """
109+ Parses an HTTP response to generate an OpenAPI example.
110+
111+ Args:
112+ openapi_spec (Dict[str, Any]): The OpenAPI specification to update.
113+ http_response (str): The HTTP response to parse.
114+ path (str): The API path.
115+ method (str): The HTTP method.
116+
117+ Returns:
118+ Tuple[Optional[Dict[str, Any]], Optional[str], Dict[str, Any]]: A tuple containing the entry dictionary, reference, and updated OpenAPI specification.
119+ """
120+
121+ headers , body = http_response .split ("\r \n \r \n " , 1 )
122+ try :
123+ body_dict = json .loads (body )
124+ except json .decoder .JSONDecodeError :
125+ return None , None , openapi_spec
126+
127+ reference , object_name , openapi_spec = self .parse_http_response_to_schema (openapi_spec , body_dict , path )
128+ entry_dict = {}
129+ old_body_dict = copy .deepcopy (body_dict )
130+
131+ if len (body_dict ) == 1 and "data" not in body_dict :
132+ entry_dict ["id" ] = body_dict
133+ self .llm_handler ._add_created_object (entry_dict , object_name )
134+ else :
135+ if "data" in body_dict :
136+ body_dict = body_dict ["data" ]
137+ if isinstance (body_dict , list ) and len (body_dict ) > 0 :
138+ body_dict = body_dict [0 ]
139+ if isinstance (body_dict , list ):
140+ for entry in body_dict :
141+ key = entry .get ("title" ) or entry .get ("name" ) or entry .get ("id" )
142+ entry_dict [key ] = {"value" : entry }
143+ self .llm_handler ._add_created_object (entry_dict [key ], object_name )
144+ if len (entry_dict ) > 3 :
145+ break
146+
147+
148+ if isinstance (body_dict , list ) and len (body_dict ) > 0 :
149+ body_dict = body_dict [0 ]
150+ if isinstance (body_dict , list ):
151+
152+ for entry in body_dict :
153+ key = entry .get ("title" ) or entry .get ("name" ) or entry .get ("id" )
154+ entry_dict [key ] = entry
155+ self .llm_handler ._add_created_object (entry_dict [key ], object_name )
156+ if len (entry_dict ) > 3 :
157+ break
158+ else :
159+ if isinstance (body_dict , list ) and len (body_dict ) == 0 :
160+ entry_dict = ""
161+ elif isinstance (body_dict , dict ) and "data" in body_dict .keys ():
162+ entry_dict = body_dict ["data" ]
163+ if isinstance (entry_dict , list ) and len (entry_dict ) > 0 :
164+ entry_dict = entry_dict [0 ]
165+ else :
166+ entry_dict = body_dict
167+ self .llm_handler ._add_created_object (entry_dict , object_name )
168+ if isinstance (old_body_dict , dict ) and len (old_body_dict .keys ()) > 0 and "data" in old_body_dict .keys () and isinstance (old_body_dict , dict ) \
169+ and isinstance (entry_dict , dict ):
170+ old_body_dict .pop ("data" )
171+ entry_dict = {** entry_dict , ** old_body_dict }
172+
173+
174+ return entry_dict , reference , openapi_spec
175+
176+ def parse_http_response_to_schema (
177+ self , openapi_spec : Dict [str , Any ], body_dict : Dict [str , Any ], path : str
178+ ) -> Tuple [str , str , Dict [str , Any ]]:
179+ """
180+ Parses an HTTP response body to generate an OpenAPI schema.
181+
182+ Args:
183+ openapi_spec (Dict[str, Any]): The OpenAPI specification to update.
184+ body_dict (Dict[str, Any]): The HTTP response body as a dictionary or list.
185+ path (str): The API path.
186+
187+ Returns:
188+ Tuple[str, str, Dict[str, Any]]: A tuple containing the reference, object name, and updated OpenAPI specification.
189+ """
190+ if "/" not in path :
191+ return None , None , openapi_spec
192+
193+ object_name = path .split ("/" )[1 ].capitalize ().rstrip ("s" )
194+ properties_dict = {}
195+
196+ # Handle different structures of `body_dict`
197+ if isinstance (body_dict , dict ):
198+ for key , value in body_dict .items ():
199+ # If it's a nested dictionary, extract keys recursively
200+ properties_dict = self .extract_keys (key , value , properties_dict )
201+
202+ elif isinstance (body_dict , list ) and len (body_dict ) > 0 :
203+ first_item = body_dict [0 ]
204+ if isinstance (first_item , dict ):
205+ for key , value in first_item .items ():
206+ properties_dict = self .extract_keys (key , value , properties_dict )
207+
208+ # Create the schema object for this response
209+ object_dict = {"type" : "object" , "properties" : properties_dict }
210+
211+ # Add the schema to OpenAPI spec if not already present
212+ if object_name not in openapi_spec ["components" ]["schemas" ]:
213+ openapi_spec ["components" ]["schemas" ][object_name ] = object_dict
214+
215+ reference = f"#/components/schemas/{ object_name } "
216+ return reference , object_name , openapi_spec
217+
218+ def extract_keys (self , key : str , value : Any , properties_dict : Dict [str , Any ]) -> Dict [str , Any ]:
219+ """
220+ Extracts and formats the keys and values from a dictionary to generate OpenAPI properties.
221+
222+ Args:
223+ key (str): The key in the dictionary.
224+ value (Any): The value associated with the key.
225+ properties_dict (Dict[str, Any]): The dictionary to store the extracted properties.
226+
227+ Returns:
228+ Dict[str, Any]: The updated properties dictionary.
229+ """
230+ if key == "id" :
231+ properties_dict [key ] = {
232+ "type" : str (type (value ).__name__ ),
233+ "format" : "uuid" ,
234+ "example" : str (value ),
235+ }
236+ else :
237+ properties_dict [key ] = {"type" : str (type (value ).__name__ ), "example" : str (value )}
238+
239+ return properties_dict
240+
241+
106242 def update_openapi_spec (self , resp , result , prompt_engineer ):
107243 """
108244 Updates the OpenAPI specification based on the API response provided.
@@ -156,7 +292,7 @@ def update_openapi_spec(self, resp, result, prompt_engineer):
156292 return list (self .openapi_spec ["endpoints" ].keys ())
157293
158294 # Parse the response into OpenAPI example and reference
159- example , reference , self .openapi_spec = self .response_handler . parse_http_response_to_openapi_example (
295+ example , reference , self .openapi_spec = self .parse_http_response_to_openapi_example (
160296 self .openapi_spec , result , path , method
161297 )
162298
0 commit comments