bubble-analysis 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. bubble/__init__.py +3 -0
  2. bubble/cache.py +207 -0
  3. bubble/cli.py +470 -0
  4. bubble/config.py +52 -0
  5. bubble/detectors.py +90 -0
  6. bubble/enums.py +65 -0
  7. bubble/extractor.py +829 -0
  8. bubble/formatters.py +887 -0
  9. bubble/integrations/__init__.py +92 -0
  10. bubble/integrations/base.py +98 -0
  11. bubble/integrations/cli_scripts/__init__.py +49 -0
  12. bubble/integrations/cli_scripts/cli.py +108 -0
  13. bubble/integrations/cli_scripts/detector.py +149 -0
  14. bubble/integrations/django/__init__.py +63 -0
  15. bubble/integrations/django/cli.py +111 -0
  16. bubble/integrations/django/detector.py +331 -0
  17. bubble/integrations/django/semantics.py +40 -0
  18. bubble/integrations/fastapi/__init__.py +57 -0
  19. bubble/integrations/fastapi/cli.py +110 -0
  20. bubble/integrations/fastapi/detector.py +176 -0
  21. bubble/integrations/fastapi/semantics.py +14 -0
  22. bubble/integrations/flask/__init__.py +57 -0
  23. bubble/integrations/flask/cli.py +110 -0
  24. bubble/integrations/flask/detector.py +191 -0
  25. bubble/integrations/flask/semantics.py +19 -0
  26. bubble/integrations/formatters.py +268 -0
  27. bubble/integrations/generic/__init__.py +13 -0
  28. bubble/integrations/generic/config.py +106 -0
  29. bubble/integrations/generic/detector.py +346 -0
  30. bubble/integrations/generic/frameworks.py +145 -0
  31. bubble/integrations/models.py +68 -0
  32. bubble/integrations/queries.py +481 -0
  33. bubble/loader.py +118 -0
  34. bubble/models.py +397 -0
  35. bubble/propagation.py +737 -0
  36. bubble/protocols.py +104 -0
  37. bubble/queries.py +627 -0
  38. bubble/results.py +211 -0
  39. bubble/stubs.py +89 -0
  40. bubble/timing.py +144 -0
  41. bubble_analysis-0.2.0.dist-info/METADATA +264 -0
  42. bubble_analysis-0.2.0.dist-info/RECORD +46 -0
  43. bubble_analysis-0.2.0.dist-info/WHEEL +5 -0
  44. bubble_analysis-0.2.0.dist-info/entry_points.txt +2 -0
  45. bubble_analysis-0.2.0.dist-info/licenses/LICENSE +21 -0
  46. bubble_analysis-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,176 @@
1
+ """FastAPI route and exception handler detection."""
2
+
3
+ import libcst as cst
4
+ from libcst.metadata import MetadataWrapper, PositionProvider
5
+
6
+ from bubble.enums import EntrypointKind, Framework
7
+ from bubble.integrations.base import Entrypoint, GlobalHandler
8
+
9
+
10
+ class FastAPIRouteVisitor(cst.CSTVisitor):
11
+ """Detects FastAPI route decorators (@router.get, @router.post, etc.)."""
12
+
13
+ METADATA_DEPENDENCIES = (PositionProvider,)
14
+
15
+ HTTP_METHODS = {"get", "post", "put", "delete", "patch", "options", "head"}
16
+
17
+ def __init__(self, file_path: str) -> None:
18
+ self.file_path = file_path
19
+ self.entrypoints: list[Entrypoint] = []
20
+
21
+ def visit_FunctionDef(self, node: cst.FunctionDef) -> bool:
22
+ for decorator in node.decorators:
23
+ route_info = self._parse_route_decorator(decorator)
24
+ if route_info:
25
+ pos = self.get_metadata(PositionProvider, node)
26
+ self.entrypoints.append(
27
+ Entrypoint(
28
+ file=self.file_path,
29
+ function=node.name.value,
30
+ line=pos.start.line,
31
+ kind=EntrypointKind.HTTP_ROUTE,
32
+ metadata={
33
+ "http_method": route_info["method"],
34
+ "http_path": route_info["path"],
35
+ "framework": Framework.FASTAPI,
36
+ },
37
+ )
38
+ )
39
+ return True
40
+
41
+ def _parse_route_decorator(self, decorator: cst.Decorator) -> dict[str, str] | None:
42
+ if not isinstance(decorator.decorator, cst.Call):
43
+ return None
44
+
45
+ call = decorator.decorator
46
+
47
+ if not isinstance(call.func, cst.Attribute):
48
+ return None
49
+
50
+ method_name = call.func.attr.value.lower()
51
+ if method_name not in self.HTTP_METHODS:
52
+ return None
53
+
54
+ path = None
55
+ if call.args:
56
+ first_arg = call.args[0]
57
+ if isinstance(first_arg.value, cst.SimpleString):
58
+ path = first_arg.value.evaluated_value
59
+
60
+ if path:
61
+ return {"path": path, "method": method_name.upper()}
62
+ return None
63
+
64
+
65
+ class FastAPIExceptionHandlerVisitor(cst.CSTVisitor):
66
+ """Detects FastAPI exception handlers.
67
+
68
+ Detects both patterns:
69
+ - app.add_exception_handler(ExceptionType, handler_func)
70
+ - @app.exception_handler(ExceptionType) decorator
71
+ """
72
+
73
+ METADATA_DEPENDENCIES = (PositionProvider,)
74
+
75
+ def __init__(self, file_path: str) -> None:
76
+ self.file_path = file_path
77
+ self.handlers: list[GlobalHandler] = []
78
+
79
+ def visit_FunctionDef(self, node: cst.FunctionDef) -> bool:
80
+ for decorator in node.decorators:
81
+ handler_info = self._parse_exception_handler_decorator(decorator)
82
+ if handler_info:
83
+ pos = self.get_metadata(PositionProvider, node)
84
+ self.handlers.append(
85
+ GlobalHandler(
86
+ file=self.file_path,
87
+ line=pos.start.line,
88
+ function=node.name.value,
89
+ handled_type=handler_info,
90
+ )
91
+ )
92
+ return True
93
+
94
+ def _parse_exception_handler_decorator(self, decorator: cst.Decorator) -> str | None:
95
+ if not isinstance(decorator.decorator, cst.Call):
96
+ return None
97
+
98
+ call = decorator.decorator
99
+ if not isinstance(call.func, cst.Attribute):
100
+ return None
101
+
102
+ if call.func.attr.value != "exception_handler":
103
+ return None
104
+
105
+ if not call.args:
106
+ return None
107
+
108
+ return self._get_name_from_expr(call.args[0].value)
109
+
110
+ def visit_Call(self, node: cst.Call) -> bool:
111
+ if not isinstance(node.func, cst.Attribute):
112
+ return True
113
+ if node.func.attr.value != "add_exception_handler":
114
+ return True
115
+ if len(node.args) < 2:
116
+ return True
117
+
118
+ exception_type = self._get_name_from_expr(node.args[0].value)
119
+ handler_name = self._get_name_from_expr(node.args[1].value)
120
+
121
+ if exception_type and handler_name:
122
+ pos = self.get_metadata(PositionProvider, node)
123
+ self.handlers.append(
124
+ GlobalHandler(
125
+ file=self.file_path,
126
+ line=pos.start.line,
127
+ function=handler_name,
128
+ handled_type=exception_type,
129
+ )
130
+ )
131
+
132
+ return True
133
+
134
+ def _get_name_from_expr(self, expr: cst.BaseExpression) -> str:
135
+ if isinstance(expr, cst.Name):
136
+ return expr.value
137
+ elif isinstance(expr, cst.Attribute):
138
+ base = self._get_name_from_expr(expr.value)
139
+ if base:
140
+ return f"{base}.{expr.attr.value}"
141
+ return expr.attr.value
142
+ return ""
143
+
144
+
145
+ def detect_fastapi_entrypoints(source: str, file_path: str) -> list[Entrypoint]:
146
+ """Detect FastAPI route entrypoints in a Python source file."""
147
+ try:
148
+ module = cst.parse_module(source)
149
+ except Exception:
150
+ return []
151
+
152
+ wrapper = MetadataWrapper(module)
153
+ visitor = FastAPIRouteVisitor(file_path)
154
+
155
+ try:
156
+ wrapper.visit(visitor)
157
+ return visitor.entrypoints
158
+ except Exception:
159
+ return []
160
+
161
+
162
+ def detect_fastapi_global_handlers(source: str, file_path: str) -> list[GlobalHandler]:
163
+ """Detect FastAPI exception handlers in a Python source file."""
164
+ try:
165
+ module = cst.parse_module(source)
166
+ except Exception:
167
+ return []
168
+
169
+ wrapper = MetadataWrapper(module)
170
+ visitor = FastAPIExceptionHandlerVisitor(file_path)
171
+
172
+ try:
173
+ wrapper.visit(visitor)
174
+ return visitor.handlers
175
+ except Exception:
176
+ return []
@@ -0,0 +1,14 @@
1
+ """FastAPI exception-to-HTTP-response mappings.
2
+
3
+ Defines which exceptions FastAPI converts to HTTP responses automatically.
4
+ """
5
+
6
+ EXCEPTION_RESPONSES: dict[str, str] = {
7
+ "fastapi.HTTPException": "HTTP {status_code}",
8
+ "HTTPException": "HTTP {status_code}",
9
+ "starlette.exceptions.HTTPException": "HTTP {status_code}",
10
+ "pydantic.ValidationError": "HTTP 422",
11
+ "pydantic_core.ValidationError": "HTTP 422",
12
+ "ValidationError": "HTTP 422",
13
+ "RequestValidationError": "HTTP 422",
14
+ }
@@ -0,0 +1,57 @@
1
+ """Flask framework integration for flow analysis."""
2
+
3
+ import typer
4
+
5
+ from bubble.integrations.base import Entrypoint, GlobalHandler
6
+ from bubble.integrations.flask.detector import (
7
+ FlaskErrorHandlerVisitor,
8
+ FlaskRouteVisitor,
9
+ detect_flask_entrypoints,
10
+ detect_flask_global_handlers,
11
+ )
12
+ from bubble.integrations.flask.semantics import EXCEPTION_RESPONSES
13
+ from bubble.integrations.models import IntegrationData
14
+
15
+
16
+ class FlaskIntegration:
17
+ """Flask framework integration."""
18
+
19
+ @property
20
+ def name(self) -> str:
21
+ return "flask"
22
+
23
+ @property
24
+ def cli_app(self) -> typer.Typer:
25
+ from bubble.integrations.flask.cli import app
26
+
27
+ return app
28
+
29
+ def detect_entrypoints(self, source: str, file_path: str) -> list[Entrypoint]:
30
+ return detect_flask_entrypoints(source, file_path)
31
+
32
+ def detect_global_handlers(self, source: str, file_path: str) -> list[GlobalHandler]:
33
+ return detect_flask_global_handlers(source, file_path)
34
+
35
+ def get_exception_response(self, exception_type: str) -> str | None:
36
+ exc_simple = exception_type.split(".")[-1]
37
+ for handled_type, response in EXCEPTION_RESPONSES.items():
38
+ handled_simple = handled_type.split(".")[-1]
39
+ if exc_simple == handled_simple or exception_type == handled_type:
40
+ return response
41
+ return None
42
+
43
+ def extract_integration_data(self, source: str, file_path: str) -> IntegrationData:
44
+ return IntegrationData(
45
+ entrypoints=self.detect_entrypoints(source, file_path),
46
+ global_handlers=self.detect_global_handlers(source, file_path),
47
+ )
48
+
49
+
50
+ __all__ = [
51
+ "FlaskIntegration",
52
+ "FlaskRouteVisitor",
53
+ "FlaskErrorHandlerVisitor",
54
+ "EXCEPTION_RESPONSES",
55
+ "detect_flask_entrypoints",
56
+ "detect_flask_global_handlers",
57
+ ]
@@ -0,0 +1,110 @@
1
+ """CLI commands for Flask integration (flow flask ...)."""
2
+
3
+ from pathlib import Path
4
+ from typing import Annotated
5
+
6
+ import typer
7
+ from rich.console import Console
8
+
9
+ from bubble.enums import Framework, OutputFormat
10
+ from bubble.extractor import extract_from_directory
11
+ from bubble.integrations import formatters
12
+ from bubble.integrations.flask import FlaskIntegration
13
+ from bubble.integrations.queries import (
14
+ audit_integration,
15
+ list_integration_entrypoints,
16
+ trace_routes_to_exception,
17
+ )
18
+ from bubble.models import ProgramModel
19
+
20
+ app = typer.Typer(
21
+ name="flask",
22
+ help="Flask framework-specific commands.",
23
+ no_args_is_help=True,
24
+ )
25
+
26
+ console = Console()
27
+ integration = FlaskIntegration()
28
+
29
+
30
+ def _build_model(directory: Path, use_cache: bool = True) -> ProgramModel:
31
+ """Build the program model from a directory."""
32
+ with console.status(f"[bold blue]Analyzing[/bold blue] {directory.name}/..."):
33
+ return extract_from_directory(directory, use_cache=use_cache)
34
+
35
+
36
+ def _get_flask_entrypoints_and_handlers(model: ProgramModel) -> tuple[list, list]:
37
+ """Get Flask entrypoints and global handlers from the model."""
38
+ entrypoints = [e for e in model.entrypoints if e.metadata.get("framework") == Framework.FLASK]
39
+ handlers = model.global_handlers
40
+ return entrypoints, handlers
41
+
42
+
43
+ @app.command()
44
+ def audit(
45
+ directory: Annotated[
46
+ Path, typer.Option("--directory", "-d", help="Directory to analyze")
47
+ ] = Path("."),
48
+ output_format: Annotated[str, typer.Option("--format", "-f", help="Output format")] = "text",
49
+ no_cache: Annotated[bool, typer.Option("--no-cache", help="Disable caching")] = False,
50
+ ) -> None:
51
+ """Check Flask routes for escaping exceptions.
52
+
53
+ Scans every Flask HTTP route, reports which have uncaught exceptions.
54
+
55
+ Example:
56
+ flow flask audit
57
+ flow flask audit -d /path/to/project
58
+ """
59
+ directory = directory.resolve()
60
+ model = _build_model(directory, use_cache=not no_cache)
61
+ entrypoints, handlers = _get_flask_entrypoints_and_handlers(model)
62
+ result = audit_integration(model, integration, entrypoints, handlers)
63
+ formatters.audit(result, OutputFormat(output_format), directory, console)
64
+
65
+
66
+ @app.command(name="entrypoints")
67
+ def list_routes(
68
+ directory: Annotated[
69
+ Path, typer.Option("--directory", "-d", help="Directory to analyze")
70
+ ] = Path("."),
71
+ output_format: Annotated[str, typer.Option("--format", "-f", help="Output format")] = "text",
72
+ no_cache: Annotated[bool, typer.Option("--no-cache", help="Disable caching")] = False,
73
+ ) -> None:
74
+ """List Flask HTTP routes.
75
+
76
+ Example:
77
+ flow flask entrypoints
78
+ """
79
+ directory = directory.resolve()
80
+ model = _build_model(directory, use_cache=not no_cache)
81
+ entrypoints, _ = _get_flask_entrypoints_and_handlers(model)
82
+ result = list_integration_entrypoints(integration, entrypoints)
83
+ formatters.entrypoints(result, OutputFormat(output_format), directory, console)
84
+
85
+
86
+ @app.command(name="routes-to")
87
+ def routes_to(
88
+ exception_type: Annotated[str, typer.Argument(help="Exception type to trace")],
89
+ directory: Annotated[
90
+ Path, typer.Option("--directory", "-d", help="Directory to analyze")
91
+ ] = Path("."),
92
+ include_subclasses: Annotated[
93
+ bool, typer.Option("--include-subclasses", "-s", help="Include subclasses")
94
+ ] = False,
95
+ output_format: Annotated[str, typer.Option("--format", "-f", help="Output format")] = "text",
96
+ no_cache: Annotated[bool, typer.Option("--no-cache", help="Disable caching")] = False,
97
+ ) -> None:
98
+ """Trace which Flask routes can trigger a given exception.
99
+
100
+ Example:
101
+ flow flask routes-to ValueError
102
+ flow flask routes-to DatabaseError -s
103
+ """
104
+ directory = directory.resolve()
105
+ model = _build_model(directory, use_cache=not no_cache)
106
+ entrypoints, _ = _get_flask_entrypoints_and_handlers(model)
107
+ result = trace_routes_to_exception(
108
+ model, integration, entrypoints, exception_type, include_subclasses
109
+ )
110
+ formatters.routes_to(result, OutputFormat(output_format), directory, console)
@@ -0,0 +1,191 @@
1
+ """Flask route and error handler detection."""
2
+
3
+ import libcst as cst
4
+ from libcst.metadata import MetadataWrapper, PositionProvider
5
+
6
+ from bubble.enums import EntrypointKind, Framework
7
+ from bubble.integrations.base import Entrypoint, GlobalHandler
8
+
9
+
10
+ class FlaskRouteVisitor(cst.CSTVisitor):
11
+ """
12
+ Detects Flask route decorators.
13
+
14
+ Supports:
15
+ - @app.route, @blueprint.route (standard Flask)
16
+ - @expose (Flask-AppBuilder)
17
+ """
18
+
19
+ METADATA_DEPENDENCIES = (PositionProvider,)
20
+
21
+ ROUTE_DECORATOR_NAMES = {"route", "expose"}
22
+
23
+ def __init__(self, file_path: str) -> None:
24
+ self.file_path = file_path
25
+ self.entrypoints: list[Entrypoint] = []
26
+
27
+ def visit_FunctionDef(self, node: cst.FunctionDef) -> bool:
28
+ for decorator in node.decorators:
29
+ route_info = self._parse_route_decorator(decorator)
30
+ if route_info:
31
+ pos = self.get_metadata(PositionProvider, node)
32
+ self.entrypoints.append(
33
+ Entrypoint(
34
+ file=self.file_path,
35
+ function=node.name.value,
36
+ line=pos.start.line,
37
+ kind=EntrypointKind.HTTP_ROUTE,
38
+ metadata={
39
+ "http_method": route_info["method"],
40
+ "http_path": route_info["path"],
41
+ "framework": Framework.FLASK,
42
+ },
43
+ )
44
+ )
45
+ return True
46
+
47
+ def _parse_route_decorator(self, decorator: cst.Decorator) -> dict[str, str] | None:
48
+ if not isinstance(decorator.decorator, cst.Call):
49
+ return None
50
+
51
+ call = decorator.decorator
52
+
53
+ if isinstance(call.func, cst.Attribute):
54
+ if call.func.attr.value not in self.ROUTE_DECORATOR_NAMES:
55
+ return None
56
+ elif isinstance(call.func, cst.Name):
57
+ if call.func.value not in self.ROUTE_DECORATOR_NAMES:
58
+ return None
59
+ else:
60
+ return None
61
+
62
+ path = None
63
+ if call.args:
64
+ first_arg = call.args[0]
65
+ if isinstance(first_arg.value, cst.SimpleString):
66
+ path = first_arg.value.evaluated_value
67
+ elif isinstance(first_arg.value, cst.ConcatenatedString):
68
+ parts = []
69
+ for part in first_arg.value.left, first_arg.value.right:
70
+ if isinstance(part, cst.SimpleString):
71
+ parts.append(part.evaluated_value)
72
+ path = "".join(parts) if parts else None
73
+
74
+ methods = ["GET"]
75
+ for arg in call.args:
76
+ if arg.keyword and arg.keyword.value == "methods":
77
+ methods = self._extract_methods(arg.value)
78
+
79
+ if path:
80
+ return {"path": path, "method": methods[0] if methods else "GET"}
81
+ return None
82
+
83
+ def _extract_methods(self, value: cst.BaseExpression) -> list[str]:
84
+ """
85
+ Extract HTTP methods from a list or tuple.
86
+
87
+ Handles both Flask-style lists and Flask-AppBuilder-style tuples:
88
+ - methods=["GET", "POST"]
89
+ - methods=("GET", "POST")
90
+ """
91
+ methods: list[str] = []
92
+ if isinstance(value, cst.List | cst.Tuple):
93
+ for el in value.elements:
94
+ if isinstance(el, cst.Element) and isinstance(el.value, cst.SimpleString):
95
+ extracted = el.value.evaluated_value
96
+ if extracted:
97
+ methods.append(extracted)
98
+ return methods if methods else ["GET"]
99
+
100
+
101
+ class FlaskErrorHandlerVisitor(cst.CSTVisitor):
102
+ """Detects Flask error handlers (@app.errorhandler, @blueprint.errorhandler)."""
103
+
104
+ METADATA_DEPENDENCIES = (PositionProvider,)
105
+
106
+ def __init__(self, file_path: str) -> None:
107
+ self.file_path = file_path
108
+ self.handlers: list[GlobalHandler] = []
109
+
110
+ def visit_FunctionDef(self, node: cst.FunctionDef) -> bool:
111
+ for decorator in node.decorators:
112
+ handler_info = self._parse_errorhandler_decorator(decorator)
113
+ if handler_info:
114
+ pos = self.get_metadata(PositionProvider, node)
115
+ self.handlers.append(
116
+ GlobalHandler(
117
+ file=self.file_path,
118
+ line=pos.start.line,
119
+ function=node.name.value,
120
+ handled_type=handler_info["exception_type"],
121
+ )
122
+ )
123
+ return True
124
+
125
+ def _parse_errorhandler_decorator(self, decorator: cst.Decorator) -> dict[str, str] | None:
126
+ if not isinstance(decorator.decorator, cst.Call):
127
+ return None
128
+
129
+ call = decorator.decorator
130
+
131
+ if isinstance(call.func, cst.Attribute):
132
+ if call.func.attr.value != "errorhandler":
133
+ return None
134
+ elif isinstance(call.func, cst.Name):
135
+ if call.func.value != "errorhandler":
136
+ return None
137
+ else:
138
+ return None
139
+
140
+ if not call.args:
141
+ return None
142
+
143
+ first_arg = call.args[0].value
144
+ exception_type = self._get_name_from_expr(first_arg)
145
+ if exception_type:
146
+ return {"exception_type": exception_type}
147
+ return None
148
+
149
+ def _get_name_from_expr(self, expr: cst.BaseExpression) -> str:
150
+ if isinstance(expr, cst.Name):
151
+ return expr.value
152
+ elif isinstance(expr, cst.Attribute):
153
+ base = self._get_name_from_expr(expr.value)
154
+ if base:
155
+ return f"{base}.{expr.attr.value}"
156
+ return expr.attr.value
157
+ return ""
158
+
159
+
160
+ def detect_flask_entrypoints(source: str, file_path: str) -> list[Entrypoint]:
161
+ """Detect Flask route entrypoints in a Python source file."""
162
+ try:
163
+ module = cst.parse_module(source)
164
+ except Exception:
165
+ return []
166
+
167
+ wrapper = MetadataWrapper(module)
168
+ visitor = FlaskRouteVisitor(file_path)
169
+
170
+ try:
171
+ wrapper.visit(visitor)
172
+ return visitor.entrypoints
173
+ except Exception:
174
+ return []
175
+
176
+
177
+ def detect_flask_global_handlers(source: str, file_path: str) -> list[GlobalHandler]:
178
+ """Detect Flask error handlers in a Python source file."""
179
+ try:
180
+ module = cst.parse_module(source)
181
+ except Exception:
182
+ return []
183
+
184
+ wrapper = MetadataWrapper(module)
185
+ visitor = FlaskErrorHandlerVisitor(file_path)
186
+
187
+ try:
188
+ wrapper.visit(visitor)
189
+ return visitor.handlers
190
+ except Exception:
191
+ return []
@@ -0,0 +1,19 @@
1
+ """Flask exception-to-HTTP-response mappings.
2
+
3
+ Defines which exceptions Flask converts to HTTP responses automatically.
4
+ """
5
+
6
+ EXCEPTION_RESPONSES: dict[str, str] = {
7
+ "werkzeug.exceptions.HTTPException": "HTTP {code}",
8
+ "HTTPException": "HTTP {code}",
9
+ "werkzeug.exceptions.NotFound": "HTTP 404",
10
+ "NotFound": "HTTP 404",
11
+ "werkzeug.exceptions.BadRequest": "HTTP 400",
12
+ "BadRequest": "HTTP 400",
13
+ "werkzeug.exceptions.Unauthorized": "HTTP 401",
14
+ "Unauthorized": "HTTP 401",
15
+ "werkzeug.exceptions.Forbidden": "HTTP 403",
16
+ "Forbidden": "HTTP 403",
17
+ "werkzeug.exceptions.InternalServerError": "HTTP 500",
18
+ "InternalServerError": "HTTP 500",
19
+ }