fastapi-docs-plus 1.0.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.
- fastapi_docs_plus/__init__.py +9 -0
- fastapi_docs_plus/ai_fill.py +239 -0
- fastapi_docs_plus/config.py +123 -0
- fastapi_docs_plus/docs_plus.py +171 -0
- fastapi_docs_plus/i18n.py +61 -0
- fastapi_docs_plus/pre_request.py +145 -0
- fastapi_docs_plus/schema_utils.py +174 -0
- fastapi_docs_plus/static/adapter.js +197 -0
- fastapi_docs_plus/static/docs-plus.css +122 -0
- fastapi_docs_plus/static/docs-plus.js +628 -0
- fastapi_docs_plus/static/docs.html +19 -0
- fastapi_docs_plus-1.0.0.dist-info/METADATA +271 -0
- fastapi_docs_plus-1.0.0.dist-info/RECORD +16 -0
- fastapi_docs_plus-1.0.0.dist-info/WHEEL +5 -0
- fastapi_docs_plus-1.0.0.dist-info/licenses/LICENSE +21 -0
- fastapi_docs_plus-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any, Awaitable, Callable
|
|
6
|
+
from urllib.parse import unquote_plus, urlsplit
|
|
7
|
+
|
|
8
|
+
from starlette.routing import Match
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class PreRequestContext:
|
|
13
|
+
"""Context for a single Try-it-out request that is about to be sent.
|
|
14
|
+
|
|
15
|
+
Modify ``headers`` or ``query`` directly to affect the outgoing
|
|
16
|
+
request. All other fields are read-only.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
method: Uppercase HTTP method (e.g. ``"GET"``, ``"POST"``).
|
|
20
|
+
url: The full request URL.
|
|
21
|
+
path: The path component of the URL.
|
|
22
|
+
headers: Mutable mapping of request headers. Modifications are
|
|
23
|
+
reflected in the actual request.
|
|
24
|
+
query: Mutable mapping of query-string parameters (URL-decoded).
|
|
25
|
+
Modifications are re-encoded for the actual request.
|
|
26
|
+
env: Environment variables passed from the frontend, including
|
|
27
|
+
``identity`` and any extra environment JSON.
|
|
28
|
+
route_path: The matched route template (e.g.
|
|
29
|
+
``"/shops/{shop_id}"``), or ``None`` if no route matched.
|
|
30
|
+
operation_id: The ``operation_id`` of the matched route, or the
|
|
31
|
+
route name, or ``None``.
|
|
32
|
+
path_params: Path parameters extracted from the URL (e.g.
|
|
33
|
+
``{"shop_id": "shop-0042"}``).
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
method: str
|
|
37
|
+
url: str
|
|
38
|
+
path: str
|
|
39
|
+
headers: dict[str, str]
|
|
40
|
+
query: dict[str, str]
|
|
41
|
+
env: dict[str, Any] = field(default_factory=dict)
|
|
42
|
+
route_path: str | None = None
|
|
43
|
+
operation_id: str | None = None
|
|
44
|
+
path_params: dict[str, Any] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def identity(self) -> str | None:
|
|
48
|
+
"""The user-selected identity from the top-bar dropdown, if any."""
|
|
49
|
+
value = self.env.get("identity")
|
|
50
|
+
return value if isinstance(value, str) and value else None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
PreRequestHook = Callable[[PreRequestContext], None | Awaitable[None]]
|
|
54
|
+
"""Signature for a pre-request hook function.
|
|
55
|
+
|
|
56
|
+
A hook receives a :class:`PreRequestContext` and may modify
|
|
57
|
+
``ctx.headers`` and ``ctx.query``. It can be synchronous or
|
|
58
|
+
asynchronous.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def match_route(app, method: str, path: str):
|
|
63
|
+
"""Resolve the route matching a given HTTP method and path.
|
|
64
|
+
|
|
65
|
+
Returns a ``(route, path_params)`` tuple by scanning
|
|
66
|
+
``app.routes``. Prefers a ``FULL`` match over ``PARTIAL``.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
app: The FastAPI application instance.
|
|
70
|
+
method: HTTP method (e.g. ``"GET"``).
|
|
71
|
+
path: The URL path to match.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
``(route, path_params)`` where *route* is the matched
|
|
75
|
+
``APIRoute`` (or ``None``) and *path_params* is a dict of
|
|
76
|
+
extracted path parameters.
|
|
77
|
+
"""
|
|
78
|
+
scope = {
|
|
79
|
+
"type": "http",
|
|
80
|
+
"method": method.upper(),
|
|
81
|
+
"path": path,
|
|
82
|
+
"root_path": "",
|
|
83
|
+
"headers": [],
|
|
84
|
+
"query_string": b"",
|
|
85
|
+
}
|
|
86
|
+
partial = None
|
|
87
|
+
for route in app.routes:
|
|
88
|
+
match, child_scope = route.matches(scope)
|
|
89
|
+
if match == Match.FULL:
|
|
90
|
+
return route, child_scope.get("path_params") or {}
|
|
91
|
+
if match == Match.PARTIAL and partial is None:
|
|
92
|
+
partial = (route, child_scope.get("path_params") or {})
|
|
93
|
+
return partial if partial else (None, {})
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def build_context(app, *, method: str, url: str, headers: dict[str, str], env: dict[str, Any]) -> PreRequestContext:
|
|
97
|
+
"""Build a :class:`PreRequestContext` from the frontend's pre-request payload.
|
|
98
|
+
|
|
99
|
+
Parses the URL, extracts query parameters (URL-decoded), and
|
|
100
|
+
resolves the matched route and path params.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
app: The FastAPI application instance.
|
|
104
|
+
method: HTTP method.
|
|
105
|
+
url: Full request URL.
|
|
106
|
+
headers: Request headers from the frontend.
|
|
107
|
+
env: Environment variables from the frontend (identity, extra
|
|
108
|
+
env JSON, etc.).
|
|
109
|
+
"""
|
|
110
|
+
parts = urlsplit(url)
|
|
111
|
+
query: dict[str, str] = {}
|
|
112
|
+
for pair in parts.query.split("&"):
|
|
113
|
+
if not pair:
|
|
114
|
+
continue
|
|
115
|
+
key, _, value = pair.partition("=")
|
|
116
|
+
query[unquote_plus(key)] = unquote_plus(value)
|
|
117
|
+
|
|
118
|
+
route, path_params = match_route(app, method, parts.path)
|
|
119
|
+
return PreRequestContext(
|
|
120
|
+
method=method.upper(),
|
|
121
|
+
url=url,
|
|
122
|
+
path=parts.path,
|
|
123
|
+
headers=dict(headers),
|
|
124
|
+
query=query,
|
|
125
|
+
env=env,
|
|
126
|
+
route_path=getattr(route, "path", None),
|
|
127
|
+
operation_id=getattr(route, "operation_id", None) or getattr(route, "name", None),
|
|
128
|
+
path_params=path_params,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def run_hooks(hooks: list[PreRequestHook], ctx: PreRequestContext) -> None:
|
|
133
|
+
"""Execute a list of pre-request hooks in registration order.
|
|
134
|
+
|
|
135
|
+
Hooks share the same :class:`PreRequestContext` instance.
|
|
136
|
+
Supports both synchronous and asynchronous callables.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
hooks: List of pre-request hook callables.
|
|
140
|
+
ctx: The request context to pass to each hook.
|
|
141
|
+
"""
|
|
142
|
+
for hook in hooks:
|
|
143
|
+
result = hook(ctx)
|
|
144
|
+
if inspect.isawaitable(result):
|
|
145
|
+
await result
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
# "title" carries almost no signal for the LLM (it often duplicates
|
|
6
|
+
# the field name), so dropping it saves a significant number of tokens.
|
|
7
|
+
_DROP_KEYS = {"title"}
|
|
8
|
+
_INLINED_CONTAINERS = ("$defs", "definitions")
|
|
9
|
+
_MAP_KEYS = ("properties", "patternProperties")
|
|
10
|
+
_SINGLE_KEYS = ("items", "additionalProperties", "not", "contains")
|
|
11
|
+
_LIST_KEYS = ("anyOf", "oneOf", "allOf", "prefixItems")
|
|
12
|
+
_PARAM_KEYS = ("name", "in", "required", "description", "schema", "example", "examples")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _resolve_pointer(ref: str, root: dict) -> dict | None:
|
|
16
|
+
if not ref.startswith("#/"):
|
|
17
|
+
return None
|
|
18
|
+
node: Any = root
|
|
19
|
+
for raw in ref[2:].split("/"):
|
|
20
|
+
part = raw.replace("~1", "/").replace("~0", "~")
|
|
21
|
+
if isinstance(node, list):
|
|
22
|
+
try:
|
|
23
|
+
node = node[int(part)]
|
|
24
|
+
except (ValueError, IndexError):
|
|
25
|
+
return None
|
|
26
|
+
elif isinstance(node, dict) and part in node:
|
|
27
|
+
node = node[part]
|
|
28
|
+
else:
|
|
29
|
+
return None
|
|
30
|
+
return node if isinstance(node, dict) else None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def flatten_schema(
|
|
34
|
+
schema: Any,
|
|
35
|
+
root: dict,
|
|
36
|
+
*,
|
|
37
|
+
max_depth: int = 4,
|
|
38
|
+
_depth: int = 0,
|
|
39
|
+
_stack: tuple[str, ...] = (),
|
|
40
|
+
) -> Any:
|
|
41
|
+
"""Inline ``$ref`` pointers and truncate recursive / over-deep structures.
|
|
42
|
+
|
|
43
|
+
Produces a self-contained schema that can be fed directly to an LLM.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
schema: The JSON Schema fragment to process.
|
|
47
|
+
root: The root OpenAPI document (used for ``$ref`` resolution).
|
|
48
|
+
max_depth: Maximum nesting depth before truncation.
|
|
49
|
+
_depth: Internal recursion depth tracker.
|
|
50
|
+
_stack: Internal ``$ref`` chain for cycle detection.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
A flattened, self-contained schema dict.
|
|
54
|
+
"""
|
|
55
|
+
if not isinstance(schema, dict):
|
|
56
|
+
return schema
|
|
57
|
+
|
|
58
|
+
ref = schema.get("$ref")
|
|
59
|
+
if isinstance(ref, str):
|
|
60
|
+
name = ref.rsplit("/", 1)[-1]
|
|
61
|
+
if ref in _stack:
|
|
62
|
+
return {"type": "object", "description": f"Recursive reference {name}; generate one level only"}
|
|
63
|
+
target = _resolve_pointer(ref, root)
|
|
64
|
+
if target is None:
|
|
65
|
+
return {"type": "object", "description": f"Unresolvable reference {ref}"}
|
|
66
|
+
sibling = {k: v for k, v in schema.items() if k != "$ref"}
|
|
67
|
+
return flatten_schema(
|
|
68
|
+
{**target, **sibling},
|
|
69
|
+
root,
|
|
70
|
+
max_depth=max_depth,
|
|
71
|
+
_depth=_depth,
|
|
72
|
+
_stack=_stack + (ref,),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if _depth >= max_depth:
|
|
76
|
+
return {"type": schema.get("type", "object"), "description": "Reached max depth; free-form values allowed"}
|
|
77
|
+
|
|
78
|
+
out: dict[str, Any] = {}
|
|
79
|
+
for key, value in schema.items():
|
|
80
|
+
if key in _DROP_KEYS or key in _INLINED_CONTAINERS:
|
|
81
|
+
continue
|
|
82
|
+
kwargs = {"max_depth": max_depth, "_stack": _stack}
|
|
83
|
+
if key in _MAP_KEYS and isinstance(value, dict):
|
|
84
|
+
out[key] = {
|
|
85
|
+
k: flatten_schema(v, root, _depth=_depth + 1, **kwargs) for k, v in value.items()
|
|
86
|
+
}
|
|
87
|
+
elif key in _SINGLE_KEYS and isinstance(value, dict):
|
|
88
|
+
out[key] = flatten_schema(value, root, _depth=_depth + 1, **kwargs)
|
|
89
|
+
elif key in _LIST_KEYS and isinstance(value, list):
|
|
90
|
+
out[key] = [flatten_schema(v, root, _depth=_depth, **kwargs) for v in value]
|
|
91
|
+
else:
|
|
92
|
+
out[key] = value
|
|
93
|
+
return out
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _pick_body_content(request_body: dict) -> tuple[str | None, dict | None]:
|
|
97
|
+
content = request_body.get("content") or {}
|
|
98
|
+
if not content:
|
|
99
|
+
return None, None
|
|
100
|
+
for media_type in content:
|
|
101
|
+
if "json" in media_type:
|
|
102
|
+
return media_type, content[media_type]
|
|
103
|
+
media_type = next(iter(content))
|
|
104
|
+
return media_type, content[media_type]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def build_operation_spec(openapi: dict, path: str, method: str, *, max_depth: int = 4) -> dict:
|
|
108
|
+
"""Extract a self-contained description of a single OpenAPI operation.
|
|
109
|
+
|
|
110
|
+
The result is suitable for sending to an LLM to generate parameter
|
|
111
|
+
values.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
openapi: The full OpenAPI document (``app.openapi()``).
|
|
115
|
+
path: The URL path (e.g. ``"/users/{id}"``).
|
|
116
|
+
method: The HTTP method (e.g. ``"get"``, ``"post"``).
|
|
117
|
+
max_depth: Maximum schema nesting depth.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
A dict with keys ``method``, ``path``, ``summary``,
|
|
121
|
+
``description``, ``parameters``, and optionally
|
|
122
|
+
``requestBody`` and ``businessHint``.
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
KeyError: If the path or operation does not exist in the
|
|
126
|
+
OpenAPI document.
|
|
127
|
+
"""
|
|
128
|
+
path_item = (openapi.get("paths") or {}).get(path)
|
|
129
|
+
if not isinstance(path_item, dict):
|
|
130
|
+
raise KeyError(f"Path not found in OpenAPI: {path}")
|
|
131
|
+
operation = path_item.get(method.lower())
|
|
132
|
+
if not isinstance(operation, dict):
|
|
133
|
+
raise KeyError(f"Operation not found in OpenAPI: {method.upper()} {path}")
|
|
134
|
+
|
|
135
|
+
parameters = []
|
|
136
|
+
shared = path_item.get("parameters") or []
|
|
137
|
+
own = operation.get("parameters") or []
|
|
138
|
+
for raw in [*shared, *own]:
|
|
139
|
+
if not isinstance(raw, dict):
|
|
140
|
+
continue
|
|
141
|
+
if isinstance(raw.get("$ref"), str):
|
|
142
|
+
resolved = _resolve_pointer(raw["$ref"], openapi)
|
|
143
|
+
if resolved is None:
|
|
144
|
+
continue
|
|
145
|
+
raw = resolved
|
|
146
|
+
param = {k: raw[k] for k in _PARAM_KEYS if raw.get(k) is not None}
|
|
147
|
+
if isinstance(param.get("schema"), dict):
|
|
148
|
+
param["schema"] = flatten_schema(param["schema"], openapi, max_depth=max_depth)
|
|
149
|
+
parameters.append(param)
|
|
150
|
+
|
|
151
|
+
spec: dict[str, Any] = {
|
|
152
|
+
"method": method.upper(),
|
|
153
|
+
"path": path,
|
|
154
|
+
"summary": operation.get("summary"),
|
|
155
|
+
"description": operation.get("description"),
|
|
156
|
+
"parameters": parameters,
|
|
157
|
+
}
|
|
158
|
+
if operation.get("x-ai-hint"):
|
|
159
|
+
spec["businessHint"] = operation["x-ai-hint"]
|
|
160
|
+
|
|
161
|
+
request_body = operation.get("requestBody")
|
|
162
|
+
if isinstance(request_body, dict):
|
|
163
|
+
if isinstance(request_body.get("$ref"), str):
|
|
164
|
+
request_body = _resolve_pointer(request_body["$ref"], openapi) or {}
|
|
165
|
+
media_type, media = _pick_body_content(request_body)
|
|
166
|
+
if media is not None:
|
|
167
|
+
spec["requestBody"] = {
|
|
168
|
+
"contentType": media_type,
|
|
169
|
+
"required": bool(request_body.get("required")),
|
|
170
|
+
"schema": flatten_schema(media.get("schema") or {}, openapi, max_depth=max_depth),
|
|
171
|
+
"examples": media.get("examples") or media.get("example"),
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {k: v for k, v in spec.items() if v not in (None, [], {})}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility layer for Swagger UI internals.
|
|
3
|
+
*
|
|
4
|
+
* Swagger UI's internal actions and selectors are not a stable public API and
|
|
5
|
+
* may change between releases. Every call into them is confined to this file,
|
|
6
|
+
* so upgrading Swagger UI only requires adapting this module.
|
|
7
|
+
* Verified against: swagger-ui-dist 5.x
|
|
8
|
+
*/
|
|
9
|
+
(function () {
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Find the raw (meta-free) parameter object for the given name and location.
|
|
14
|
+
*
|
|
15
|
+
* Swagger UI stores parameter values under keys of the form
|
|
16
|
+
* `${in}.${name}.hash-${param.hashCode()}`, where the hash is computed from
|
|
17
|
+
* the raw parameter object that ParameterRow reads. Objects returned by
|
|
18
|
+
* `operationWithMeta()` carry `value` / `errors` fields, produce a different
|
|
19
|
+
* hash, and writing through them lands on a key nobody reads, failing
|
|
20
|
+
* silently.
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} system - Swagger UI system object.
|
|
23
|
+
* @param {string} path - Operation path, e.g. "/shops/{shop_id}".
|
|
24
|
+
* @param {string} method - Lowercase HTTP method, e.g. "get".
|
|
25
|
+
* @param {string} name - Parameter name.
|
|
26
|
+
* @param {string} location - One of "path", "query", "header", "cookie".
|
|
27
|
+
* @returns {Object|null} Raw parameter object, or null when not found.
|
|
28
|
+
*/
|
|
29
|
+
function findRawParam(system, path, method, name, location) {
|
|
30
|
+
var selectors = system.specSelectors;
|
|
31
|
+
var specs = [];
|
|
32
|
+
try {
|
|
33
|
+
if (selectors.specJsonWithResolvedSubtrees) specs.push(selectors.specJsonWithResolvedSubtrees());
|
|
34
|
+
if (selectors.specJson) specs.push(selectors.specJson());
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
var containers = [
|
|
39
|
+
["paths", path, method, "parameters"],
|
|
40
|
+
["paths", path, "parameters"],
|
|
41
|
+
];
|
|
42
|
+
for (var i = 0; i < specs.length; i++) {
|
|
43
|
+
for (var j = 0; j < containers.length; j++) {
|
|
44
|
+
var params = specs[i] && specs[i].getIn ? specs[i].getIn(containers[j]) : null;
|
|
45
|
+
if (!params || !params.find) continue;
|
|
46
|
+
var found = params.find(function (p) {
|
|
47
|
+
return p.get("name") === name && p.get("in") === location;
|
|
48
|
+
});
|
|
49
|
+
if (found) return found;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Convert a JSON value into the shape expected by Swagger UI field components.
|
|
57
|
+
*
|
|
58
|
+
* Array parameters are returned as arrays because the array input component
|
|
59
|
+
* expects the array itself; serializing them would render a single string item.
|
|
60
|
+
*
|
|
61
|
+
* @param {*} value - Raw JSON value.
|
|
62
|
+
* @returns {string|Array} Value ready to be written into the form field.
|
|
63
|
+
*/
|
|
64
|
+
function toFieldValue(value) {
|
|
65
|
+
if (value === null || value === undefined) return "";
|
|
66
|
+
if (typeof value === "string") return value;
|
|
67
|
+
if (Array.isArray(value)) return value.map(toFieldValue);
|
|
68
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
69
|
+
return String(value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Build an Error carrying a message code and its interpolation values.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} code - Message code recognized by docs-plus.js.
|
|
76
|
+
* @param {string} message - Fallback English message.
|
|
77
|
+
* @param {Object} [values] - Values interpolated into the localized message.
|
|
78
|
+
* @returns {Error} Decorated error object.
|
|
79
|
+
*/
|
|
80
|
+
function adapterError(code, message, values) {
|
|
81
|
+
var error = new Error(message);
|
|
82
|
+
error.code = code;
|
|
83
|
+
error.values = values;
|
|
84
|
+
return error;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
window.DocsPlusAdapter = {
|
|
88
|
+
/**
|
|
89
|
+
* Expand the operation block so its RequestBody component gets mounted.
|
|
90
|
+
*
|
|
91
|
+
* While collapsed, the RequestBody component is not mounted yet and its
|
|
92
|
+
* automatically generated example overwrites the values we write on mount,
|
|
93
|
+
* so the block must be expanded before filling anything.
|
|
94
|
+
*
|
|
95
|
+
* @param {HTMLElement} button - AI button inside the operation summary.
|
|
96
|
+
* @returns {Promise<void>} Resolves once the block is expanded, or
|
|
97
|
+
* immediately when it is already expanded.
|
|
98
|
+
*/
|
|
99
|
+
ensureOperationExpanded: function (button) {
|
|
100
|
+
var block = button && button.closest ? button.closest(".opblock") : null;
|
|
101
|
+
if (!block || block.querySelector(".opblock-body")) return Promise.resolve();
|
|
102
|
+
var control = block.querySelector(".opblock-summary-control");
|
|
103
|
+
if (!control) return Promise.resolve();
|
|
104
|
+
control.click();
|
|
105
|
+
return new Promise(function (resolve) {
|
|
106
|
+
setTimeout(resolve, 350);
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Write a value into a single parameter field.
|
|
112
|
+
*
|
|
113
|
+
* @param {Object} system - Swagger UI system object.
|
|
114
|
+
* @param {string} path - Operation path, e.g. "/shops/{shop_id}".
|
|
115
|
+
* @param {string} method - Lowercase HTTP method, e.g. "get".
|
|
116
|
+
* @param {string} name - Parameter name.
|
|
117
|
+
* @param {string} location - One of "path", "query", "header", "cookie".
|
|
118
|
+
* @param {*} value - Value to write.
|
|
119
|
+
* @returns {boolean} True on success.
|
|
120
|
+
* @throws {Error} With code "parameterNotFound" when the parameter is not
|
|
121
|
+
* declared in the OpenAPI document, or "paramActionMissing" when the
|
|
122
|
+
* Swagger UI version lacks the required action.
|
|
123
|
+
*/
|
|
124
|
+
setParamValue: function (system, path, method, name, location, value) {
|
|
125
|
+
var param = findRawParam(system, path, method, name, location);
|
|
126
|
+
if (!param) {
|
|
127
|
+
throw adapterError("parameterNotFound", "Parameter not found in OpenAPI: " + location + "." + name, {
|
|
128
|
+
parameter: location + "." + name,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (!system.specActions || !system.specActions.changeParamByIdentity) {
|
|
132
|
+
throw adapterError("paramActionMissing", "Swagger UI is missing the changeParamByIdentity action");
|
|
133
|
+
}
|
|
134
|
+
system.specActions.changeParamByIdentity([path, method], param, toFieldValue(value), false);
|
|
135
|
+
return true;
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Write the request body into the body editor.
|
|
140
|
+
*
|
|
141
|
+
* @param {Object} system - Swagger UI system object.
|
|
142
|
+
* @param {string} path - Operation path, e.g. "/shops/{shop_id}".
|
|
143
|
+
* @param {string} method - Lowercase HTTP method, e.g. "post".
|
|
144
|
+
* @param {*} value - Body value; non-strings are pretty-printed as JSON.
|
|
145
|
+
* @returns {boolean} True on success.
|
|
146
|
+
* @throws {Error} With code "bodyActionMissing" when the Swagger UI
|
|
147
|
+
* version lacks the required action.
|
|
148
|
+
*/
|
|
149
|
+
setRequestBody: function (system, path, method, value) {
|
|
150
|
+
var text = typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
151
|
+
if (system.oas3Actions && system.oas3Actions.setRequestBodyValue) {
|
|
152
|
+
system.oas3Actions.setRequestBodyValue({ value: text, pathMethod: [path, method] });
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
throw adapterError("bodyActionMissing", "Swagger UI is missing the setRequestBodyValue action");
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Trigger parameter validation after filling.
|
|
160
|
+
*
|
|
161
|
+
* Validation is best-effort; a failure does not affect the filled values.
|
|
162
|
+
*
|
|
163
|
+
* @param {Object} system - Swagger UI system object.
|
|
164
|
+
* @param {string} path - Operation path, e.g. "/shops/{shop_id}".
|
|
165
|
+
* @param {string} method - Lowercase HTTP method, e.g. "get".
|
|
166
|
+
*/
|
|
167
|
+
validateParams: function (system, path, method) {
|
|
168
|
+
try {
|
|
169
|
+
if (system.specActions && system.specActions.validateParams) {
|
|
170
|
+
system.specActions.validateParams([path, method], false);
|
|
171
|
+
}
|
|
172
|
+
} catch (err) {
|
|
173
|
+
/* Validation is best-effort; failures must not affect the filled values. */
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Read the path and method from an OperationSummary component's props.
|
|
179
|
+
*
|
|
180
|
+
* @param {Object} props - Props of the OperationSummary component.
|
|
181
|
+
* @returns {{path: string, method: string}|null} Path and method, or null
|
|
182
|
+
* when they cannot be determined.
|
|
183
|
+
*/
|
|
184
|
+
readPathMethod: function (props) {
|
|
185
|
+
var operationProps = props && props.operationProps;
|
|
186
|
+
if (operationProps && typeof operationProps.get === "function") {
|
|
187
|
+
var path = operationProps.get("path");
|
|
188
|
+
var method = operationProps.get("method");
|
|
189
|
+
if (path && method) return { path: path, method: method };
|
|
190
|
+
}
|
|
191
|
+
if (props && props.path && props.method) {
|
|
192
|
+
return { path: props.path, method: props.method };
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
})();
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/* ------------------------------------------------------------------ Base */
|
|
2
|
+
body {
|
|
3
|
+
margin: 0;
|
|
4
|
+
background: #fafafa;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* -------------------------------------------------------------- Top bar */
|
|
8
|
+
#docs-plus-bar {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-wrap: wrap;
|
|
11
|
+
align-items: baseline;
|
|
12
|
+
gap: 12px;
|
|
13
|
+
padding: 10px 20px;
|
|
14
|
+
background: #1b1b1b;
|
|
15
|
+
color: #eee;
|
|
16
|
+
font: 13px/1.5 -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#docs-plus-bar label {
|
|
20
|
+
display: inline-flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: 6px;
|
|
23
|
+
flex: 0 0 auto;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#docs-plus-bar select,
|
|
27
|
+
#docs-plus-bar textarea {
|
|
28
|
+
font: inherit;
|
|
29
|
+
color: #eee;
|
|
30
|
+
background: #2c2c2c;
|
|
31
|
+
border: 1px solid #444;
|
|
32
|
+
border-radius: 4px;
|
|
33
|
+
padding: 4px 6px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#docs-plus-bar select:focus-visible,
|
|
37
|
+
#docs-plus-bar textarea:focus-visible,
|
|
38
|
+
.docs-plus-ai-btn:focus-visible {
|
|
39
|
+
outline: 2px solid #9ecbff;
|
|
40
|
+
outline-offset: 2px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#docs-plus-bar textarea {
|
|
44
|
+
box-sizing: border-box;
|
|
45
|
+
width: 100%;
|
|
46
|
+
min-height: 72px;
|
|
47
|
+
margin-top: 6px;
|
|
48
|
+
font-family: ui-monospace, Consolas, monospace;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#docs-plus-bar details {
|
|
52
|
+
flex: 1 1 auto;
|
|
53
|
+
min-width: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#docs-plus-bar summary {
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
color: #9ecbff;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#docs-plus-bar .docs-plus-note {
|
|
62
|
+
color: #f0b849;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* ----------------------------------------------- Operation AI buttons */
|
|
66
|
+
.docs-plus-op-summary {
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
gap: 8px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.docs-plus-op-summary > *:first-child {
|
|
73
|
+
flex: 1 1 auto;
|
|
74
|
+
min-width: 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.docs-plus-ai-btn {
|
|
78
|
+
flex: 0 0 auto;
|
|
79
|
+
margin-right: 10px;
|
|
80
|
+
padding: 5px 12px;
|
|
81
|
+
font: 600 12px/1 -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif;
|
|
82
|
+
color: #fff;
|
|
83
|
+
background: #4a5cf0;
|
|
84
|
+
border: 0;
|
|
85
|
+
border-radius: 4px;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.docs-plus-ai-btn:disabled {
|
|
90
|
+
opacity: 0.6;
|
|
91
|
+
cursor: progress;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.docs-plus-fill-btn {
|
|
95
|
+
background: #2b7a3d;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.docs-plus-fill-btn.is-empty {
|
|
99
|
+
background: #5b6470;
|
|
100
|
+
opacity: 0.7;
|
|
101
|
+
cursor: not-allowed;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* -------------------------------------------------------------- Toast */
|
|
105
|
+
#docs-plus-toast {
|
|
106
|
+
position: fixed;
|
|
107
|
+
right: 20px;
|
|
108
|
+
bottom: 20px;
|
|
109
|
+
z-index: 9999;
|
|
110
|
+
max-width: 460px;
|
|
111
|
+
padding: 12px 16px;
|
|
112
|
+
color: #fff;
|
|
113
|
+
background: #2b7a3d;
|
|
114
|
+
border-radius: 6px;
|
|
115
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
|
|
116
|
+
font: 13px/1.5 -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif;
|
|
117
|
+
white-space: pre-wrap;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#docs-plus-toast.is-error {
|
|
121
|
+
background: #b3323c;
|
|
122
|
+
}
|