././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1742825688.1078057 wapiti_swagger-0.1.9/0000755000175000001440000000000014770264330014040 5ustar00siriususers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735394366.0 wapiti_swagger-0.1.9/LICENSE0000644000175000001440000000206114734002076015041 0ustar00siriususersMIT License Copyright (c) 2025 Nicolas Surribas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739130852.0 wapiti_swagger-0.1.9/MANIFEST.in0000644000175000001440000000002714752203744015577 0ustar00siriususersglobal-exclude tests/* ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1742825688.107775 wapiti_swagger-0.1.9/PKG-INFO0000644000175000001440000001526714770264330015150 0ustar00siriususersMetadata-Version: 2.4 Name: wapiti-swagger Version: 0.1.9 Summary: A library for parsing and generating request bodies from Swagger/OpenAPI specifications. Author-email: Nicolas Surribas License: MIT Keywords: swagger,openapi,wapiti,parser Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Internet :: WWW/HTTP Requires-Python: >=3.9 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: PyYAML>=6.0 Dynamic: license-file # **Wapiti Swagger Parser** ![Version](https://img.shields.io/pypi/v/wapiti-swagger?label=version&logo=PyPI&logoColor=white&color=blue) ![License](https://img.shields.io/github/license/wapiti-scanner/wapiti_swagger) ![Python Versions](https://img.shields.io/badge/python-3.8%20|%203.9%20|%203.10%20|%203.11%20|%203.12-blue) ## **Project Description** The **Wapiti Swagger Parser** is a Python library designed to parse Swagger (OpenAPI) specifications and extract the necessary information to generate valid HTTP requests. It focuses on providing a clean, programmatic interface for analyzing Swagger files and creating request templates without relying on external tools for OpenAPI processing. This library is particularly useful for scenarios where only the request generation requirements are of interest, such as: - Automated testing and validation of APIs. - Dynamic request generation for web vulnerability scanners (like Wapiti). - Custom API tooling. The library is lightweight, relying only on Python's built-in `json` library and `PyYAML` for file parsing, and it avoids heavy dependencies on larger OpenAPI frameworks. --- ## **Installation** `pip install wapiti-swagger` --- ## **Key Features** - **Request Extraction**: - Parses all HTTP requests (methods, paths, parameters) defined in the Swagger file. - **Schema Handling**: - Resolves `$ref` references in schemas, including handling circular references gracefully. - **Custom Types**: - Identifies and retains custom types (e.g., enumerated values, objects) for enhanced request understanding. - **Request Body Generation**: - Automatically generates example request bodies based on schema definitions. - **Metadata Extraction**: - Captures root-level metadata like `host`, `basePath`, `servers`, and `schemes`. - **Supports Swagger 2.0 and OpenAPI 3.x**: - Works with both specification versions seamlessly. --- ## **Usage Example** ```python from wapiti_swagger.parser import parse, generate_request_body_from_schema # Load and parse a Swagger file parsed = parse("swagger.json") # List all available requests for request in parsed.requests: print(request) # Generate an example request body for a specific request (here one expecting JSON input) request_body = generate_request_body_from_schema( schema=request.parameters[0].schema, # Use the schema of the first parameter resolved_components=parsed.components ) print("Example request body:", request_body) ``` --- ## **Why Use This Library?** Unlike general-purpose OpenAPI parsers, this library is optimized for specific use cases like generating valid requests for API testing, scanning, or mocking. It is lightweight, customizable, and avoids unnecessary processing of response definitions or additional metadata unrelated to request generation. --- ## **Methods in `parser` Module** ### 1. `parse(file_path: str) -> ParsedSwagger` Parses a Swagger/OpenAPI specification file and returns a `ParsedSwagger` object containing the following: - **Requests:** List of `SwaggerRequest` objects extracted from paths. - **Components:** Preprocessed and resolved components (e.g., schemas, parameters). - **Metadata:** High-level metadata like `host`, `basePath`, and `servers`. **Parameters:** - `file_path` (str): Path to the Swagger/OpenAPI file (JSON or YAML). **Returns:** - `ParsedSwagger`: Object containing parsed requests, components, and metadata. --- ### 2. `extract_requests(data: dict) -> List[SwaggerRequest]` Extracts all HTTP requests from the `paths` section of the Swagger specification. **Parameters:** - `data` (dict): The full Swagger/OpenAPI specification as a dictionary. **Returns:** - `List[SwaggerRequest]`: A list of requests with details like method, path, parameters, and request bodies. --- ### 3. `extract_request_body(request_body: dict) -> List[Parameter]` Extracts parameters from the `requestBody` section of a Swagger path operation. Handles multiple media types (e.g., `application/json`, `text/json`). **Parameters:** - `request_body` (dict): The `requestBody` definition for a path operation. **Returns:** - `List[Parameter]`: A list of parameters with details like media type, schema, and custom type. --- ### 4. `extract_parameter(param: dict) -> Parameter` Parses a single parameter from the `parameters` section of a Swagger path operation. **Parameters:** - `param` (dict): The parameter definition. **Returns:** - `Parameter`: Object representing the parameter with details like name, location, type, and schema. --- ### 5. `parse_components(components: dict) -> Dict[str, Dict]` Resolves and preprocesses all components (e.g., schemas, parameters) from the `components` section of the Swagger specification. **Parameters:** - `components` (dict): The `components` section of the Swagger specification. **Returns:** - `Dict[str, Dict]`: Resolved and preprocessed components organized by type (e.g., schemas, parameters). --- ### 6. `resolve_schema(schema: dict, resolved_components: dict, visited_refs: set) -> dict` Recursively resolves `$ref` references in schemas while avoiding circular references. **Parameters:** - `schema` (dict): The schema to resolve. - `resolved_components` (dict): Preprocessed components for reference resolution. - `visited_refs` (set): Tracks references to avoid circular references. **Returns:** - `dict`: Fully resolved schema. --- ### 7. `extract_metadata(data: dict) -> Dict[str, Any]` Extracts high-level metadata from the root of the Swagger specification, such as `host`, `basePath`, and `servers`. **Parameters:** - `data` (dict): The full Swagger/OpenAPI specification. **Returns:** - `Dict[str, Any]`: Metadata like `host`, `basePath`, `schemes`, and `servers`. --- ### 8. `generate_request_body_from_schema(schema: dict, resolved_components: dict) -> Optional[Union[dict, list, str, int, bool]]` Generates an example request body based on a schema definition. **Parameters:** - `schema` (dict): The schema definition. - `resolved_components` (dict): Resolved components for reference resolution. **Returns:** - `Optional[Union[dict, list, str, int, bool]]`: An example request body. --- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735485208.0 wapiti_swagger-0.1.9/README.md0000644000175000001440000001406014734263430015320 0ustar00siriususers# **Wapiti Swagger Parser** ![Version](https://img.shields.io/pypi/v/wapiti-swagger?label=version&logo=PyPI&logoColor=white&color=blue) ![License](https://img.shields.io/github/license/wapiti-scanner/wapiti_swagger) ![Python Versions](https://img.shields.io/badge/python-3.8%20|%203.9%20|%203.10%20|%203.11%20|%203.12-blue) ## **Project Description** The **Wapiti Swagger Parser** is a Python library designed to parse Swagger (OpenAPI) specifications and extract the necessary information to generate valid HTTP requests. It focuses on providing a clean, programmatic interface for analyzing Swagger files and creating request templates without relying on external tools for OpenAPI processing. This library is particularly useful for scenarios where only the request generation requirements are of interest, such as: - Automated testing and validation of APIs. - Dynamic request generation for web vulnerability scanners (like Wapiti). - Custom API tooling. The library is lightweight, relying only on Python's built-in `json` library and `PyYAML` for file parsing, and it avoids heavy dependencies on larger OpenAPI frameworks. --- ## **Installation** `pip install wapiti-swagger` --- ## **Key Features** - **Request Extraction**: - Parses all HTTP requests (methods, paths, parameters) defined in the Swagger file. - **Schema Handling**: - Resolves `$ref` references in schemas, including handling circular references gracefully. - **Custom Types**: - Identifies and retains custom types (e.g., enumerated values, objects) for enhanced request understanding. - **Request Body Generation**: - Automatically generates example request bodies based on schema definitions. - **Metadata Extraction**: - Captures root-level metadata like `host`, `basePath`, `servers`, and `schemes`. - **Supports Swagger 2.0 and OpenAPI 3.x**: - Works with both specification versions seamlessly. --- ## **Usage Example** ```python from wapiti_swagger.parser import parse, generate_request_body_from_schema # Load and parse a Swagger file parsed = parse("swagger.json") # List all available requests for request in parsed.requests: print(request) # Generate an example request body for a specific request (here one expecting JSON input) request_body = generate_request_body_from_schema( schema=request.parameters[0].schema, # Use the schema of the first parameter resolved_components=parsed.components ) print("Example request body:", request_body) ``` --- ## **Why Use This Library?** Unlike general-purpose OpenAPI parsers, this library is optimized for specific use cases like generating valid requests for API testing, scanning, or mocking. It is lightweight, customizable, and avoids unnecessary processing of response definitions or additional metadata unrelated to request generation. --- ## **Methods in `parser` Module** ### 1. `parse(file_path: str) -> ParsedSwagger` Parses a Swagger/OpenAPI specification file and returns a `ParsedSwagger` object containing the following: - **Requests:** List of `SwaggerRequest` objects extracted from paths. - **Components:** Preprocessed and resolved components (e.g., schemas, parameters). - **Metadata:** High-level metadata like `host`, `basePath`, and `servers`. **Parameters:** - `file_path` (str): Path to the Swagger/OpenAPI file (JSON or YAML). **Returns:** - `ParsedSwagger`: Object containing parsed requests, components, and metadata. --- ### 2. `extract_requests(data: dict) -> List[SwaggerRequest]` Extracts all HTTP requests from the `paths` section of the Swagger specification. **Parameters:** - `data` (dict): The full Swagger/OpenAPI specification as a dictionary. **Returns:** - `List[SwaggerRequest]`: A list of requests with details like method, path, parameters, and request bodies. --- ### 3. `extract_request_body(request_body: dict) -> List[Parameter]` Extracts parameters from the `requestBody` section of a Swagger path operation. Handles multiple media types (e.g., `application/json`, `text/json`). **Parameters:** - `request_body` (dict): The `requestBody` definition for a path operation. **Returns:** - `List[Parameter]`: A list of parameters with details like media type, schema, and custom type. --- ### 4. `extract_parameter(param: dict) -> Parameter` Parses a single parameter from the `parameters` section of a Swagger path operation. **Parameters:** - `param` (dict): The parameter definition. **Returns:** - `Parameter`: Object representing the parameter with details like name, location, type, and schema. --- ### 5. `parse_components(components: dict) -> Dict[str, Dict]` Resolves and preprocesses all components (e.g., schemas, parameters) from the `components` section of the Swagger specification. **Parameters:** - `components` (dict): The `components` section of the Swagger specification. **Returns:** - `Dict[str, Dict]`: Resolved and preprocessed components organized by type (e.g., schemas, parameters). --- ### 6. `resolve_schema(schema: dict, resolved_components: dict, visited_refs: set) -> dict` Recursively resolves `$ref` references in schemas while avoiding circular references. **Parameters:** - `schema` (dict): The schema to resolve. - `resolved_components` (dict): Preprocessed components for reference resolution. - `visited_refs` (set): Tracks references to avoid circular references. **Returns:** - `dict`: Fully resolved schema. --- ### 7. `extract_metadata(data: dict) -> Dict[str, Any]` Extracts high-level metadata from the root of the Swagger specification, such as `host`, `basePath`, and `servers`. **Parameters:** - `data` (dict): The full Swagger/OpenAPI specification. **Returns:** - `Dict[str, Any]`: Metadata like `host`, `basePath`, `schemes`, and `servers`. --- ### 8. `generate_request_body_from_schema(schema: dict, resolved_components: dict) -> Optional[Union[dict, list, str, int, bool]]` Generates an example request body based on a schema definition. **Parameters:** - `schema` (dict): The schema definition. - `resolved_components` (dict): Resolved components for reference resolution. **Returns:** - `Optional[Union[dict, list, str, int, bool]]`: An example request body. --- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1742825564.0 wapiti_swagger-0.1.9/pyproject.toml0000644000175000001440000000150614770264134016760 0ustar00siriususers[build-system] requires = ["setuptools>=42", "wheel"] build-backend = "setuptools.build_meta" [project] name = "wapiti-swagger" version = "0.1.9" description = "A library for parsing and generating request bodies from Swagger/OpenAPI specifications." readme = "README.md" license = {text = "MIT"} authors = [ {name="Nicolas Surribas", email="nicolas.surribas@gmail.com"} ] keywords = ["swagger", "openapi", "wapiti", "parser"] classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Topic :: Software Development :: Libraries", "Topic :: Internet :: WWW/HTTP", ] dependencies = ["PyYAML>=6.0"] # Add any other required dependencies here requires-python = ">=3.9" [tool.setuptools.packages.find] where = ["."] exclude = ["tests*"] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1742825688.1081274 wapiti_swagger-0.1.9/setup.cfg0000644000175000001440000000004614770264330015661 0ustar00siriususers[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1742825688.1064224 wapiti_swagger-0.1.9/wapiti_swagger/0000755000175000001440000000000014770264330017054 5ustar00siriususers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733489788.0 wapiti_swagger-0.1.9/wapiti_swagger/__init__.py0000644000175000001440000000000014724572174021162 0ustar00siriususers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1742825342.0 wapiti_swagger-0.1.9/wapiti_swagger/models.py0000644000175000001440000000473014770263576020730 0ustar00siriususers"""Module containing structs to handle Swagger (openAPI) elements""" from typing import Any, Dict, List, Optional from dataclasses import dataclass, field # pylint: disable=too-many-instance-attributes @dataclass class Parameter: """Represents a parameter expected by an API endpoint""" name: str description: str = "" location: str = "" required: bool = False param_type: str = "" param_format: str = "" nullable: bool = False default: Any = None media_type: Optional[str] = None custom_type: Optional[str] = None schema: dict = field(default_factory=dict) # Use `field` to avoid mutable default class SwaggerRequest: """Represents HTTP request information required to use an API endpoint""" # pylint: disable=too-many-instance-attributes,too-many-arguments,too-many-positional-arguments,too-few-public-methods def __init__( self, path: str, method: str, summary: str, parameters: list[Parameter], consumes: Optional[List[str]] ): self.path = path self.method = method self.summary = summary self.parameters = parameters self.consumes = consumes if consumes is not None else [] def __repr__(self): output = "" if self.summary: output = self.summary + "\n" output += f"{self.method} {self.path}\n" output += str(self.parameters) + "\n" return output @dataclass class ParsedSwagger: """Represents the various sections of a swagger (openAPI) file""" metadata: Dict[str, Any] requests: List[SwaggerRequest] components: Dict[str, Dict] def urls(self) -> List[str]: """ Returns the list of full URLs for the API, combining host, basePath, schemes, or servers. """ urls = [] # Swagger 2.0: Combine schemes, host, and basePath if self.metadata: host = self.metadata.get("host") base_path = self.metadata.get("basePath", "").rstrip("/") # Default to http if schemes not provided schemes = self.metadata.get("schemes", ["http"]) if host: for scheme in schemes: urls.append(f"{scheme}://{host}{base_path}") # OpenAPI 3.x: Use servers directly servers = self.metadata.get("servers") if servers: urls.extend(servers) return [url for url in urls if url.strip()] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1742825468.0 wapiti_swagger-0.1.9/wapiti_swagger/parser.py0000644000175000001440000003431714770263774020745 0ustar00siriususers"""This module contains functions to parse several sections of a swagger (openAPI) file""" import json import logging from typing import Any, Dict, List, Optional, Union from uuid import uuid4 import yaml from wapiti_swagger.models import Parameter, ParsedSwagger, SwaggerRequest # Define a custom type alias for request body generation RequestBody = Union[Dict[str, Any], List[Any], str, int, float, bool] def parse(file_path: str) -> ParsedSwagger: """ Parses a Swagger or OpenAPI file (JSON or YAML) and returns the extracted requests and components. :param file_path: Path to the Swagger/OpenAPI file. :return: A ParsedSwagger object with extracted requests and resolved components. """ with open(file_path, 'r', encoding='utf-8') as f: if file_path.endswith('.json'): data = json.load(f) elif file_path.endswith(('.yaml', '.yml')): data = yaml.safe_load(f) else: raise ValueError("Unsupported file format. Use JSON or YAML.") if "paths" not in data: raise ValueError("Invalid Swagger/OpenAPI file: Missing 'paths' key.") metadata = extract_metadata(data) requests = extract_requests(data) # Handle both OpenAPI 3.x and Swagger 2.0 components = {} if "components" in data: components = parse_components(data["components"]) elif "definitions" in data or "parameters" in data: components = parse_components( { "schemas": data.get("definitions", {}), "parameters": data.get("parameters", {}) } ) return ParsedSwagger(metadata=metadata, requests=requests, components=components) def extract_metadata(data: dict) -> Dict[str, Any]: """ Extracts metadata from the root of the Swagger file. :param data: The Swagger/OpenAPI specification as a dictionary. :return: A dictionary containing metadata such as host, basePath, schemes, servers, etc. """ metadata = { "host": data.get("host"), "basePath": data.get("basePath"), "schemes": data.get("schemes"), "servers": [], } # OpenAPI 3.x for server in data.get("servers", []): if isinstance(server, dict) and "url" in server: url = server["url"] for variable_name, properties in server.get("variables", {}).items(): if "default" in properties: url = url.replace( f"{{{variable_name}}}", properties["default"] ) metadata["servers"].append(url) # Remove None values return {key: value for key, value in metadata.items() if value is not None} def replace_with_global_parameters(parameters: List[Parameter], global_params: Dict[str, Parameter]) -> List[Parameter]: """Replace parameters in list with their real version from the global parameters list when possible.""" return [global_params[param.custom_type] if param.custom_type in global_params else param for param in parameters] def extract_requests(data: dict): """Extracts HTTP requests from the Swagger specification.""" if "paths" not in data: raise ValueError("Invalid Swagger file: 'paths' section is missing.") requests = [] global_parameters = {name: extract_parameter(value) for name, value in data.get("parameters", {}).items()} for path, methods in data["paths"].items(): # Some parameters in common for all methods may be put at the same level that generic methods path_level_parameters = [extract_parameter(param) for param in methods.get("parameters", [])] for method, details in methods.items(): if method.lower() in {"get", "post", "put", "delete", "patch", "options", "head"}: method_level_parameters = [extract_parameter(param) for param in details.get("parameters", [])] # Handle requestBody if "requestBody" in details: request_body_params = extract_request_body(details["requestBody"]) method_level_parameters.extend(request_body_params) # Extract consumes consumes = details.get("consumes", []) fixed_parameters = replace_with_global_parameters( path_level_parameters + method_level_parameters, global_parameters, ) requests.append(SwaggerRequest( path=path, method=method.upper(), summary=details.get("summary", ""), parameters=fixed_parameters, consumes=consumes, )) return requests # pylint: disable=too-many-return-statements def resolve_schema( schema: dict, resolved_components: dict, visited_refs: set = None, resolved_cache: dict = None ) -> Optional[dict]: """ Recursively resolves $ref entries within a schema, avoiding circular references in the object graph. :param schema: The schema to resolve. :param resolved_components: The resolved components dictionary. :param visited_refs: Tracks visited $refs to prevent infinite recursion. :param resolved_cache: Caches already resolved schemas to prevent redundant work. :return: The fully resolved schema, or None if a circular reference is detected. """ if visited_refs is None: visited_refs = set() if resolved_cache is None: resolved_cache = {} # If the schema is empty, return as-is if not schema: return schema # Handle $ref resolution if "$ref" in schema: ref = schema["$ref"] if ref in resolved_cache: # If already resolved, return a deep copy of the cached result return resolved_cache[ref].copy() if resolved_cache[ref] else None if ref in visited_refs: # Log and break the circular reference logging.debug("Breaking circular reference: %s", ref) resolved_cache[ref] = None # Mark as skipped return None # Mark as in-progress in the cache to prevent recursion resolved_cache[ref] = None # Add the current $ref to the visited set visited_refs.add(ref) # Resolve the referenced schema ref_name = ref.split("/")[-1] referenced_schema = resolved_components["schemas"].get(ref_name, {}) resolved = resolve_schema(referenced_schema, resolved_components, visited_refs, resolved_cache) # Remove the current $ref from the visited set after resolution visited_refs.remove(ref) # Update the cache with the resolved schema resolved_cache[ref] = resolved.copy() if resolved else None return resolved.copy() if resolved else None # Handle object schemas with properties if schema.get("type") == "object" and "properties" in schema: schema_copy = schema.copy() schema_copy["properties"] = {} for prop_name, prop_schema in schema["properties"].items(): resolved_property = resolve_schema(prop_schema, resolved_components, visited_refs, resolved_cache) if resolved_property is not None: # Ensure a deep copy is used to avoid circular references schema_copy["properties"][prop_name] = ( resolved_property.copy() if isinstance(resolved_property, dict) else resolved_property ) return schema_copy # Handle array schemas if schema.get("type") == "array" and "items" in schema: schema_copy = schema.copy() resolved_items = resolve_schema(schema["items"], resolved_components, visited_refs, resolved_cache) if resolved_items is not None: schema_copy["items"] = resolved_items.copy() if isinstance(resolved_items, dict) else resolved_items return schema_copy # If no further resolution is needed, return the schema as-is return schema.copy() if isinstance(schema, dict) else schema def parse_components(components: dict) -> dict: """ Parses and resolves all $ref entries in the components section of the Swagger file. Handles OpenAPI 3.0 `components` and Swagger 2.0 `definitions` + `parameters`. :param components: The raw components section from the Swagger file. :return: A dictionary with resolved components. """ resolved_components = {"schemas": {}, "parameters": {}} resolved_cache = {} # Shared cache for all schemas for key, value in components.items(): if isinstance(value, dict): resolved_components[key] = {} for sub_key, sub_value in value.items(): resolved_schema = resolve_schema(sub_value, components, resolved_cache=resolved_cache) if resolved_schema is not None: resolved_components[key][sub_key] = resolved_schema return resolved_components def extract_parameter(param: dict) -> Parameter: """ Extracts a Parameter object from a parameter dictionary using preprocessed components. :param param: Dictionary representing a parameter. :return: Parameter object with raw schema. """ name = param.get("name", "") location = param.get("in", "") description = param.get("description", "") required = param.get("required", False) schema = param.get("schema", param) # Default to param (for Swagger 2.0) custom_type = None # Extract custom type from $ref if "$ref" in schema: ref = schema["$ref"] custom_type = ref.split("/")[-1] default_value = schema.get("default", param.get("default", None)) param_type = schema.get("type", "") if param_type == "array" and "items" in schema: items = schema["items"] default_value = items.get("default", default_value) or items.get("enum", [None])[0] return Parameter( name=name, description=description, location=location, required=required, param_type=param_type, param_format=schema.get("format", ""), nullable=schema.get("nullable", False), default=default_value, custom_type=custom_type, schema=schema # Store raw schema ) def extract_request_body(request_body: dict): """ Extracts parameters from a requestBody definition using preprocessed components. :param request_body: Dictionary representing the requestBody. :return: List of Parameter objects extracted from the requestBody. """ parameters = [] content = request_body.get("content", {}) for media_type, media_details in content.items(): schema = media_details.get("schema", {}) # Raw schema custom_type = None # Extract custom type from $ref if "$ref" in schema: ref = schema["$ref"] custom_type = ref.split("/")[-1] parameters.append(Parameter( name="body", description=f"Request body for {media_type}", location="body", required=request_body.get("required", False), param_type=schema.get("type", ""), # Basic type (e.g., "object") param_format=schema.get("format", ""), # Basic format (e.g., "int32") nullable=schema.get("nullable", False), default=None, media_type=media_type, custom_type=custom_type, schema=schema # Store raw schema )) return parameters default_string_values = { "date": "2024-01-01", "date-time": "2023-03-03T20:35:34.32", "email": "wapiti2021@mailinator.com", "uuid": str(uuid4()), "hostname": "google.com", "ipv4": "8.8.8.8", "ipv6": "2a00:1450:4007:818::200e", "uri": "https://example.com/api", "url": "https://example.com", "byte": "d2FwaXRp", "binary": "hello there", "password": "Letm3in_" } # pylint: disable=too-many-return-statements,too-many-branches def generate_request_body_from_schema( schema: dict, resolved_components: dict, visited_refs: set = None ) -> Optional[RequestBody]: """ Recursively generates a request body template from a given schema. :param schema: The raw schema to generate a body from. :param resolved_components: The resolved components dictionary. :param visited_refs: Tracks visited $refs to prevent infinite recursion. :return: A dictionary, list, or scalar representing a valid request body, or None if schema is empty. """ if not schema: return None if visited_refs is None: visited_refs = set() # Handle $ref resolution if "$ref" in schema: ref = schema["$ref"] if ref in visited_refs: return None # Skip circular references visited_refs.add(ref) ref_name = ref.split("/")[-1] resolved_schema = resolved_components["schemas"].get(ref_name, {}) resolved_body = generate_request_body_from_schema(resolved_schema, resolved_components, visited_refs) visited_refs.remove(ref) return resolved_body schema_type = schema.get("type", "object") # Handle object schemas if schema_type == "object": result = {} properties = schema.get("properties", {}) required_fields = schema.get("required", []) for prop_name, prop_schema in properties.items(): result[prop_name] = generate_request_body_from_schema( prop_schema, resolved_components, visited_refs ) if prop_name in required_fields and result[prop_name] is None: result[prop_name] = "" return result # Handle array schemas if schema_type == "array": items = schema.get("items", {}) return [generate_request_body_from_schema(items, resolved_components, visited_refs)] # Handle enums if schema_type == "string" and "enum" in schema: return schema["enum"][0] # Return the first enum value as an example # Handle scalar types if schema_type == "integer": return schema.get("default", 1) if schema_type == "number": return schema.get("default", 1.0) if schema_type == "boolean": return schema.get("default", True) if schema_type == "string": string_format = schema.get("format") return schema.get("default", default_string_values.get(string_format, "default")) # Fallback for unsupported types return f"<{schema_type or 'unknown'}>" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1742825688.107351 wapiti_swagger-0.1.9/wapiti_swagger.egg-info/0000755000175000001440000000000014770264330020546 5ustar00siriususers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1742825688.0 wapiti_swagger-0.1.9/wapiti_swagger.egg-info/PKG-INFO0000644000175000001440000001526714770264330021656 0ustar00siriususersMetadata-Version: 2.4 Name: wapiti-swagger Version: 0.1.9 Summary: A library for parsing and generating request bodies from Swagger/OpenAPI specifications. Author-email: Nicolas Surribas License: MIT Keywords: swagger,openapi,wapiti,parser Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Internet :: WWW/HTTP Requires-Python: >=3.9 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: PyYAML>=6.0 Dynamic: license-file # **Wapiti Swagger Parser** ![Version](https://img.shields.io/pypi/v/wapiti-swagger?label=version&logo=PyPI&logoColor=white&color=blue) ![License](https://img.shields.io/github/license/wapiti-scanner/wapiti_swagger) ![Python Versions](https://img.shields.io/badge/python-3.8%20|%203.9%20|%203.10%20|%203.11%20|%203.12-blue) ## **Project Description** The **Wapiti Swagger Parser** is a Python library designed to parse Swagger (OpenAPI) specifications and extract the necessary information to generate valid HTTP requests. It focuses on providing a clean, programmatic interface for analyzing Swagger files and creating request templates without relying on external tools for OpenAPI processing. This library is particularly useful for scenarios where only the request generation requirements are of interest, such as: - Automated testing and validation of APIs. - Dynamic request generation for web vulnerability scanners (like Wapiti). - Custom API tooling. The library is lightweight, relying only on Python's built-in `json` library and `PyYAML` for file parsing, and it avoids heavy dependencies on larger OpenAPI frameworks. --- ## **Installation** `pip install wapiti-swagger` --- ## **Key Features** - **Request Extraction**: - Parses all HTTP requests (methods, paths, parameters) defined in the Swagger file. - **Schema Handling**: - Resolves `$ref` references in schemas, including handling circular references gracefully. - **Custom Types**: - Identifies and retains custom types (e.g., enumerated values, objects) for enhanced request understanding. - **Request Body Generation**: - Automatically generates example request bodies based on schema definitions. - **Metadata Extraction**: - Captures root-level metadata like `host`, `basePath`, `servers`, and `schemes`. - **Supports Swagger 2.0 and OpenAPI 3.x**: - Works with both specification versions seamlessly. --- ## **Usage Example** ```python from wapiti_swagger.parser import parse, generate_request_body_from_schema # Load and parse a Swagger file parsed = parse("swagger.json") # List all available requests for request in parsed.requests: print(request) # Generate an example request body for a specific request (here one expecting JSON input) request_body = generate_request_body_from_schema( schema=request.parameters[0].schema, # Use the schema of the first parameter resolved_components=parsed.components ) print("Example request body:", request_body) ``` --- ## **Why Use This Library?** Unlike general-purpose OpenAPI parsers, this library is optimized for specific use cases like generating valid requests for API testing, scanning, or mocking. It is lightweight, customizable, and avoids unnecessary processing of response definitions or additional metadata unrelated to request generation. --- ## **Methods in `parser` Module** ### 1. `parse(file_path: str) -> ParsedSwagger` Parses a Swagger/OpenAPI specification file and returns a `ParsedSwagger` object containing the following: - **Requests:** List of `SwaggerRequest` objects extracted from paths. - **Components:** Preprocessed and resolved components (e.g., schemas, parameters). - **Metadata:** High-level metadata like `host`, `basePath`, and `servers`. **Parameters:** - `file_path` (str): Path to the Swagger/OpenAPI file (JSON or YAML). **Returns:** - `ParsedSwagger`: Object containing parsed requests, components, and metadata. --- ### 2. `extract_requests(data: dict) -> List[SwaggerRequest]` Extracts all HTTP requests from the `paths` section of the Swagger specification. **Parameters:** - `data` (dict): The full Swagger/OpenAPI specification as a dictionary. **Returns:** - `List[SwaggerRequest]`: A list of requests with details like method, path, parameters, and request bodies. --- ### 3. `extract_request_body(request_body: dict) -> List[Parameter]` Extracts parameters from the `requestBody` section of a Swagger path operation. Handles multiple media types (e.g., `application/json`, `text/json`). **Parameters:** - `request_body` (dict): The `requestBody` definition for a path operation. **Returns:** - `List[Parameter]`: A list of parameters with details like media type, schema, and custom type. --- ### 4. `extract_parameter(param: dict) -> Parameter` Parses a single parameter from the `parameters` section of a Swagger path operation. **Parameters:** - `param` (dict): The parameter definition. **Returns:** - `Parameter`: Object representing the parameter with details like name, location, type, and schema. --- ### 5. `parse_components(components: dict) -> Dict[str, Dict]` Resolves and preprocesses all components (e.g., schemas, parameters) from the `components` section of the Swagger specification. **Parameters:** - `components` (dict): The `components` section of the Swagger specification. **Returns:** - `Dict[str, Dict]`: Resolved and preprocessed components organized by type (e.g., schemas, parameters). --- ### 6. `resolve_schema(schema: dict, resolved_components: dict, visited_refs: set) -> dict` Recursively resolves `$ref` references in schemas while avoiding circular references. **Parameters:** - `schema` (dict): The schema to resolve. - `resolved_components` (dict): Preprocessed components for reference resolution. - `visited_refs` (set): Tracks references to avoid circular references. **Returns:** - `dict`: Fully resolved schema. --- ### 7. `extract_metadata(data: dict) -> Dict[str, Any]` Extracts high-level metadata from the root of the Swagger specification, such as `host`, `basePath`, and `servers`. **Parameters:** - `data` (dict): The full Swagger/OpenAPI specification. **Returns:** - `Dict[str, Any]`: Metadata like `host`, `basePath`, `schemes`, and `servers`. --- ### 8. `generate_request_body_from_schema(schema: dict, resolved_components: dict) -> Optional[Union[dict, list, str, int, bool]]` Generates an example request body based on a schema definition. **Parameters:** - `schema` (dict): The schema definition. - `resolved_components` (dict): Resolved components for reference resolution. **Returns:** - `Optional[Union[dict, list, str, int, bool]]`: An example request body. --- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1742825688.0 wapiti_swagger-0.1.9/wapiti_swagger.egg-info/SOURCES.txt0000644000175000001440000000046614770264330022440 0ustar00siriususersLICENSE MANIFEST.in README.md pyproject.toml wapiti_swagger/__init__.py wapiti_swagger/models.py wapiti_swagger/parser.py wapiti_swagger.egg-info/PKG-INFO wapiti_swagger.egg-info/SOURCES.txt wapiti_swagger.egg-info/dependency_links.txt wapiti_swagger.egg-info/requires.txt wapiti_swagger.egg-info/top_level.txt././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1742825688.0 wapiti_swagger-0.1.9/wapiti_swagger.egg-info/dependency_links.txt0000644000175000001440000000000114770264330024614 0ustar00siriususers ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1742825688.0 wapiti_swagger-0.1.9/wapiti_swagger.egg-info/requires.txt0000644000175000001440000000001414770264330023141 0ustar00siriususersPyYAML>=6.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1742825688.0 wapiti_swagger-0.1.9/wapiti_swagger.egg-info/top_level.txt0000644000175000001440000000004414770264330023276 0ustar00siriususersdist myvenv swaggers wapiti_swagger