ssc_codegen 0.30.0__tar.gz → 0.31.0__tar.gz
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.
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/PKG-INFO +1 -1
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/pyproject.toml +1 -1
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/__init__.py +6 -2
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/struct.py +104 -50
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/converters/js_pure.py +148 -179
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/converters/py_bs4.py +62 -309
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/converters/py_lxml.py +21 -17
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/converters/py_parsel.py +12 -6
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/converters/py_render.py +2 -2
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/converters/py_slax.py +12 -6
- ssc_codegen-0.31.0/ssc_codegen/converters/runtime/__init__.py +7 -0
- ssc_codegen-0.31.0/ssc_codegen/converters/runtime/_helpers.py +35 -0
- ssc_codegen-0.31.0/ssc_codegen/converters/runtime/js_base.py +24 -0
- ssc_codegen-0.31.0/ssc_codegen/converters/runtime/py_base.py +67 -0
- ssc_codegen-0.31.0/ssc_codegen/converters/runtime/py_lxml.py +7 -0
- ssc_codegen-0.31.0/ssc_codegen/converters/runtime/py_rest.py +133 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/core/struct_parser.py +40 -16
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/main.py +1 -1
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/.gitignore +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/LICENSE +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/README.md +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/__init__.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/_logging.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/array.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/base.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/cast.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/control.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/extract.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/helpers.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/jsondef.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/module.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/predicate_containers.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/predicate_ops.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/regex.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/selectors.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/string.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/transform.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/typedef.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/ast/types.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/converters/base.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/converters/helpers.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/core/__init__.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/core/contexts.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/core/expressions.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/core/format.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/core/linter.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/core/module_handler.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/core/predicates.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/core/reader.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/core/type_checking.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/document_utils.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/exceptions.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/health.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/kdl/__init__.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/kdl/dict_reader.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/kdl/parser.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/kdl/reader.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/parsers/__init__.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/parsers/curl.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/parsers/http.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/pseudo_selectors.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/regex_utils.py +0 -0
- {ssc_codegen-0.30.0/ssc_codegen/converters → ssc_codegen-0.31.0/ssc_codegen}/request_spec.py +0 -0
- {ssc_codegen-0.30.0 → ssc_codegen-0.31.0}/ssc_codegen/selector_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssc_codegen
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.31.0
|
|
4
4
|
Summary: Python-dsl code converter to html parser for web scraping
|
|
5
5
|
Project-URL: Documentation, https://github.com/vypivshiy/selector_schema_codegen#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/vypivshiy/selector_schema_codegen/issues
|
|
@@ -43,7 +43,10 @@ from .struct import (
|
|
|
43
43
|
TableConfig,
|
|
44
44
|
TableRow,
|
|
45
45
|
TableMatchKey,
|
|
46
|
-
|
|
46
|
+
RequestHttp,
|
|
47
|
+
MethodBase,
|
|
48
|
+
MethodFetch,
|
|
49
|
+
MethodRest,
|
|
47
50
|
ErrorResponse,
|
|
48
51
|
PlaceholderSpec,
|
|
49
52
|
Field,
|
|
@@ -150,7 +153,8 @@ __all__ = [
|
|
|
150
153
|
"Init", "InitField", "SplitDoc",
|
|
151
154
|
"Key", "Value",
|
|
152
155
|
"TableConfig", "TableRow", "TableMatchKey",
|
|
153
|
-
"
|
|
156
|
+
"RequestHttp", "MethodBase", "MethodFetch", "MethodRest",
|
|
157
|
+
"ErrorResponse", "PlaceholderSpec", "Field", "StartParse",
|
|
154
158
|
# selectors
|
|
155
159
|
"CssSelect", "CssSelectAll",
|
|
156
160
|
"XpathSelect", "XpathSelectAll",
|
|
@@ -49,6 +49,106 @@ def parse_placeholder(match: "_re.Match[str]") -> PlaceholderSpec:
|
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
# ── Request/Method nodes ──────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _iter_http_strings(http: RequestHttp):
|
|
56
|
+
"""Yield every string value that may contain placeholders."""
|
|
57
|
+
yield http.url
|
|
58
|
+
yield from http.headers.values()
|
|
59
|
+
yield from http.cookies.values()
|
|
60
|
+
yield from http.params.values()
|
|
61
|
+
if isinstance(http.body, str) and http.body:
|
|
62
|
+
yield http.body
|
|
63
|
+
elif isinstance(http.body, dict):
|
|
64
|
+
yield from _iter_dict_strings(http.body)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _iter_dict_strings(d: dict):
|
|
68
|
+
for v in d.values():
|
|
69
|
+
if isinstance(v, str):
|
|
70
|
+
yield v
|
|
71
|
+
elif isinstance(v, dict):
|
|
72
|
+
yield from _iter_dict_strings(v)
|
|
73
|
+
elif isinstance(v, list):
|
|
74
|
+
for item in v:
|
|
75
|
+
if isinstance(item, str):
|
|
76
|
+
yield item
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class RequestHttp(Node):
|
|
81
|
+
"""Parsed HTTP request — child node of MethodBase.
|
|
82
|
+
|
|
83
|
+
Created once at parse time from ``parse_to_spec(raw_payload)``;
|
|
84
|
+
converters read fields directly instead of re-parsing raw_payload.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
method: str = "GET"
|
|
88
|
+
url: str = ""
|
|
89
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
90
|
+
cookies: dict[str, str] = field(default_factory=dict)
|
|
91
|
+
params: dict[str, str] = field(default_factory=dict)
|
|
92
|
+
body_kind: str = "empty" # "empty" | "json" | "form" | "raw"
|
|
93
|
+
body: str | dict | None = None
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def placeholders(self) -> list[PlaceholderSpec]:
|
|
97
|
+
"""Unique placeholders across all string fields."""
|
|
98
|
+
seen: set[str] = set()
|
|
99
|
+
result: list[PlaceholderSpec] = []
|
|
100
|
+
for text in _iter_http_strings(self):
|
|
101
|
+
for m in PLACEHOLDER_RE.finditer(text):
|
|
102
|
+
spec = parse_placeholder(m)
|
|
103
|
+
if spec.name not in seen:
|
|
104
|
+
seen.add(spec.name)
|
|
105
|
+
result.append(spec)
|
|
106
|
+
return result
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class MethodBase(Node):
|
|
111
|
+
"""Base class for @request method nodes."""
|
|
112
|
+
|
|
113
|
+
name: str = "" # method name suffix; "" = default fetch()
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def http_request(self) -> RequestHttp:
|
|
117
|
+
return [n for n in self.body if isinstance(n, RequestHttp)][0]
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def placeholders(self) -> list[PlaceholderSpec]:
|
|
121
|
+
return self.http_request.placeholders
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def placeholder_names(self) -> list[str]:
|
|
125
|
+
return [p.name for p in self.placeholders]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@dataclass
|
|
129
|
+
class MethodFetch(MethodBase):
|
|
130
|
+
"""Fetch shortcut for regular schemas (Item/List/etc).
|
|
131
|
+
|
|
132
|
+
``fetch()`` returns a parser instance constructed from the HTTP response body.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
response_path: str = "" # dot-notation JSON path, e.g. "payload.html"
|
|
136
|
+
response_join: str = (
|
|
137
|
+
"" # join separator when response-path resolves to list[str]
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass
|
|
142
|
+
class MethodRest(MethodBase):
|
|
143
|
+
"""REST method for ``type=rest`` schemas.
|
|
144
|
+
|
|
145
|
+
Method returns ``Ok/TransportErr`` union; no HTML parsing.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
doc: str = "" # per-method docstring
|
|
149
|
+
response_schema: str = "" # json schema name for typed 2xx response
|
|
150
|
+
|
|
151
|
+
|
|
52
152
|
# ── Base struct ──────────────────────────────────────────────────────────────
|
|
53
153
|
|
|
54
154
|
|
|
@@ -68,15 +168,15 @@ class StructBase(Node):
|
|
|
68
168
|
return self.body[0] # type: ignore
|
|
69
169
|
|
|
70
170
|
@property
|
|
71
|
-
def request_config(self) ->
|
|
171
|
+
def request_config(self) -> MethodBase | None:
|
|
72
172
|
for node in self.body:
|
|
73
|
-
if isinstance(node,
|
|
173
|
+
if isinstance(node, MethodBase):
|
|
74
174
|
return node
|
|
75
175
|
return None
|
|
76
176
|
|
|
77
177
|
@property
|
|
78
|
-
def request_configs(self) -> list[
|
|
79
|
-
return [n for n in self.body if isinstance(n,
|
|
178
|
+
def request_configs(self) -> list[MethodBase]:
|
|
179
|
+
return [n for n in self.body if isinstance(n, MethodBase)]
|
|
80
180
|
|
|
81
181
|
@property
|
|
82
182
|
def use_request(self) -> bool:
|
|
@@ -350,52 +450,6 @@ class TableMatchKey(Node):
|
|
|
350
450
|
ret: VariableType = field(default=VariableType.STRING)
|
|
351
451
|
|
|
352
452
|
|
|
353
|
-
@dataclass
|
|
354
|
-
class RequestConfig(Node):
|
|
355
|
-
"""
|
|
356
|
-
Optional transport layer config for a struct.
|
|
357
|
-
DSL: @request [name="suffix"] [response-path="..."] [response-join="..."]
|
|
358
|
-
[response=JsonSchema] [doc="..."] \"""...\"""
|
|
359
|
-
|
|
360
|
-
raw_payload stores the verbatim curl or raw HTTP string (with {{placeholders}}).
|
|
361
|
-
Transport normalization (curl/HTTP parse → kwargs) happens at converter stage.
|
|
362
|
-
|
|
363
|
-
name="" (unnamed) generates fetch()/async_fetch().
|
|
364
|
-
name="by-id" generates fetch_by_id()/async_fetch_by_id() (Python)
|
|
365
|
-
or fetchById() (JS).
|
|
366
|
-
|
|
367
|
-
response_schema (type=rest only): json schema name for typed 2xx response.
|
|
368
|
-
Empty string = void return.
|
|
369
|
-
doc (type=rest only): per-method docstring.
|
|
370
|
-
"""
|
|
371
|
-
|
|
372
|
-
raw_payload: str = ""
|
|
373
|
-
response_path: str = "" # dot-notation JSON path, e.g. "payload.html"
|
|
374
|
-
response_join: str = (
|
|
375
|
-
"" # join separator when response-path resolves to list[str]
|
|
376
|
-
)
|
|
377
|
-
name: str = "" # method name suffix; "" = default fetch()
|
|
378
|
-
response_schema: str = "" # type=rest: json schema for 2xx body
|
|
379
|
-
doc: str = "" # type=rest: per-method docstring
|
|
380
|
-
|
|
381
|
-
@property
|
|
382
|
-
def placeholders(self) -> list[PlaceholderSpec]:
|
|
383
|
-
"""Unique placeholders in declaration order (dedup by name)."""
|
|
384
|
-
seen: set[str] = set()
|
|
385
|
-
result: list[PlaceholderSpec] = []
|
|
386
|
-
for m in PLACEHOLDER_RE.finditer(self.raw_payload):
|
|
387
|
-
spec = parse_placeholder(m)
|
|
388
|
-
if spec.name not in seen:
|
|
389
|
-
seen.add(spec.name)
|
|
390
|
-
result.append(spec)
|
|
391
|
-
return result
|
|
392
|
-
|
|
393
|
-
@property
|
|
394
|
-
def placeholder_names(self) -> list[str]:
|
|
395
|
-
"""Unique placeholder names in declaration order."""
|
|
396
|
-
return [p.name for p in self.placeholders]
|
|
397
|
-
|
|
398
|
-
|
|
399
453
|
@dataclass
|
|
400
454
|
class ErrorResponse(Node):
|
|
401
455
|
"""
|
|
@@ -28,12 +28,13 @@ from ssc_codegen.converters.helpers import (
|
|
|
28
28
|
to_snake_case,
|
|
29
29
|
jsonify_path_to_segments,
|
|
30
30
|
)
|
|
31
|
-
from ssc_codegen.
|
|
32
|
-
|
|
31
|
+
from ssc_codegen.request_spec import (
|
|
32
|
+
RequestSpec,
|
|
33
33
|
normalize_placeholder_names,
|
|
34
34
|
_PH,
|
|
35
35
|
)
|
|
36
36
|
from ssc_codegen.ast.struct import parse_placeholder
|
|
37
|
+
from ssc_codegen.converters.runtime.js_base import js_base_utility_lines
|
|
37
38
|
|
|
38
39
|
JS_CONVERTER = BaseConverter(indent=" " * 2)
|
|
39
40
|
|
|
@@ -202,24 +203,7 @@ def _js_module_has_rest(node) -> bool:
|
|
|
202
203
|
@JS_CONVERTER(a.Utilities)
|
|
203
204
|
def pre_utilities(node: a.Utilities, _: ConverterContext):
|
|
204
205
|
# TODO: wrong func _replMap
|
|
205
|
-
lines =
|
|
206
|
-
"const UNMATCHED_TABLE_ROW = Symbol('UNMATCHED_TABLE_ROW');",
|
|
207
|
-
"",
|
|
208
|
-
"function _replMap(s, map) {",
|
|
209
|
-
" for (const [k, v] of Object.entries(map)) s = s.split(k).join(v);",
|
|
210
|
-
" return s;",
|
|
211
|
-
"}",
|
|
212
|
-
"",
|
|
213
|
-
"function _normalizeText(s) { return s ? s.trim().replace(/\\s+/g, ' ') : ''; }",
|
|
214
|
-
"",
|
|
215
|
-
"function _unescapeText(s) {",
|
|
216
|
-
" const el = document.createElement('textarea');",
|
|
217
|
-
" el.innerHTML = s; return el.value;",
|
|
218
|
-
"}",
|
|
219
|
-
"",
|
|
220
|
-
"function _rmPrefix(s, p) { return s.startsWith(p) ? s.slice(p.length) : s; }",
|
|
221
|
-
"function _rmSuffix(s, p) { return s.endsWith(p) ? s.slice(0, -p.length) : s; }",
|
|
222
|
-
]
|
|
206
|
+
lines = js_base_utility_lines()
|
|
223
207
|
if _js_module_has_rest(node):
|
|
224
208
|
lines.extend(
|
|
225
209
|
[
|
|
@@ -1767,32 +1751,6 @@ _JS_PRIM_JSDOC = {
|
|
|
1767
1751
|
}
|
|
1768
1752
|
|
|
1769
1753
|
|
|
1770
|
-
def _js_ph_jsdoc(ph: a.PlaceholderSpec) -> str:
|
|
1771
|
-
"""JSDoc type expression for a placeholder, e.g. 'number[]' or 'string | null'."""
|
|
1772
|
-
t = _JS_PRIM_JSDOC[ph.type_name]
|
|
1773
|
-
if ph.is_array:
|
|
1774
|
-
t = f"{t}[]"
|
|
1775
|
-
return t
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
def _js_signature_jsdoc(
|
|
1779
|
-
placeholders: list[a.PlaceholderSpec], indent: str
|
|
1780
|
-
) -> list[str]:
|
|
1781
|
-
"""Build JSDoc lines describing the destructured params argument."""
|
|
1782
|
-
if not placeholders:
|
|
1783
|
-
return []
|
|
1784
|
-
lines = [f"{indent}/**", f"{indent} * @param {{object}} params"]
|
|
1785
|
-
required = [p for p in placeholders if not p.is_optional]
|
|
1786
|
-
optional = [p for p in placeholders if p.is_optional]
|
|
1787
|
-
for p in required + optional:
|
|
1788
|
-
bracket_name = (
|
|
1789
|
-
f"[params.{p.name}]" if p.is_optional else f"params.{p.name}"
|
|
1790
|
-
)
|
|
1791
|
-
lines.append(f"{indent} * @param {{{_js_ph_jsdoc(p)}}} {bracket_name}")
|
|
1792
|
-
lines.append(f"{indent} */")
|
|
1793
|
-
return lines
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
1754
|
def _js_render_json_body(raw: str) -> str:
|
|
1797
1755
|
"""Render JSON body template (with {{placeholders}}) as a JS template literal."""
|
|
1798
1756
|
inner = raw.replace("\\", "\\\\").replace("`", "\\`")
|
|
@@ -1816,31 +1774,105 @@ def _js_name(name: str) -> str:
|
|
|
1816
1774
|
return to_camel_case(to_snake_case(name))
|
|
1817
1775
|
|
|
1818
1776
|
|
|
1819
|
-
def
|
|
1820
|
-
"""
|
|
1777
|
+
def _js_build_request_args(node: a.MethodBase, ctx: ConverterContext):
|
|
1778
|
+
"""Build shared request parts for MethodRest/MethodFetch handlers."""
|
|
1779
|
+
http = node.http_request
|
|
1821
1780
|
spec = normalize_placeholder_names(
|
|
1822
|
-
|
|
1781
|
+
RequestSpec(
|
|
1782
|
+
method=http.method,
|
|
1783
|
+
url=http.url,
|
|
1784
|
+
headers=dict(http.headers),
|
|
1785
|
+
cookies=dict(http.cookies),
|
|
1786
|
+
params=dict(http.params),
|
|
1787
|
+
body_kind=http.body_kind,
|
|
1788
|
+
body=http.body,
|
|
1789
|
+
),
|
|
1790
|
+
_js_name,
|
|
1823
1791
|
)
|
|
1824
1792
|
http_client = ctx.meta.get("http_client", "fetch")
|
|
1825
1793
|
ind = ctx.indent_char
|
|
1826
|
-
i1 = ctx.indent
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1794
|
+
i1, i2, i3, i4 = (ctx.indent + ind * n for n in range(4))
|
|
1795
|
+
|
|
1796
|
+
# placeholder params (required first, optional last)
|
|
1797
|
+
ph_param = ""
|
|
1798
|
+
if spec.placeholders:
|
|
1799
|
+
ordered = [
|
|
1800
|
+
p.name
|
|
1801
|
+
for p in sorted(spec.placeholders, key=lambda p: p.is_optional)
|
|
1802
|
+
]
|
|
1803
|
+
ph_param = ", {" + ", ".join(ordered) + "}"
|
|
1804
|
+
|
|
1805
|
+
# pre-body builders + resolved expressions
|
|
1806
|
+
pre_lines: list[str] = []
|
|
1807
|
+
|
|
1808
|
+
def _resolve(d, varname, builder_fn):
|
|
1809
|
+
if not d:
|
|
1810
|
+
return None
|
|
1811
|
+
if _js_dict_needs_builder(d):
|
|
1812
|
+
pre_lines.extend(builder_fn(varname, d, i3))
|
|
1813
|
+
return varname
|
|
1814
|
+
return _js_render_obj(d)
|
|
1815
|
+
|
|
1816
|
+
params_expr = _resolve(spec.params, "_params", _js_emit_params_builder)
|
|
1817
|
+
headers_expr = _resolve(spec.headers, "_headers", _js_emit_obj_builder)
|
|
1818
|
+
cookies_expr = _resolve(spec.cookies, "_cookies", _js_emit_obj_builder)
|
|
1819
|
+
|
|
1820
|
+
body_result = _js_render_body(spec)
|
|
1821
|
+
body_expr = body_result[1] if body_result else None
|
|
1822
|
+
|
|
1823
|
+
return (
|
|
1824
|
+
spec,
|
|
1825
|
+
pre_lines,
|
|
1826
|
+
ph_param,
|
|
1827
|
+
http_client,
|
|
1828
|
+
params_expr,
|
|
1829
|
+
headers_expr,
|
|
1830
|
+
cookies_expr,
|
|
1831
|
+
body_expr,
|
|
1832
|
+
i1,
|
|
1833
|
+
i2,
|
|
1834
|
+
i3,
|
|
1835
|
+
i4,
|
|
1836
|
+
)
|
|
1837
|
+
|
|
1838
|
+
|
|
1839
|
+
def _js_ok_payload_type(node: a.MethodRest) -> str:
|
|
1840
|
+
"""Return the JSDoc type for the successful response payload."""
|
|
1841
|
+
if not node.response_schema:
|
|
1842
|
+
return "null"
|
|
1843
|
+
struct = node.parent
|
|
1844
|
+
module = struct.parent if struct is not None else None
|
|
1845
|
+
schema_type = f"{to_pascal_case(node.response_schema)}Json"
|
|
1846
|
+
if module is not None:
|
|
1847
|
+
for n in module.body:
|
|
1848
|
+
if isinstance(n, a.JsonDef) and n.name == node.response_schema:
|
|
1849
|
+
if n.is_array:
|
|
1850
|
+
return f"Array<{schema_type}>"
|
|
1851
|
+
break
|
|
1852
|
+
return schema_type
|
|
1853
|
+
|
|
1854
|
+
|
|
1855
|
+
@JS_CONVERTER(a.MethodRest)
|
|
1856
|
+
def pre_method_rest(node: a.MethodRest, ctx: ConverterContext):
|
|
1857
|
+
(
|
|
1858
|
+
spec,
|
|
1859
|
+
pre_lines,
|
|
1860
|
+
ph_param,
|
|
1861
|
+
http_client,
|
|
1862
|
+
params_expr,
|
|
1863
|
+
headers_expr,
|
|
1864
|
+
_cookies_expr,
|
|
1865
|
+
body_expr,
|
|
1866
|
+
i1,
|
|
1867
|
+
i2,
|
|
1868
|
+
i3,
|
|
1869
|
+
i4,
|
|
1870
|
+
) = _js_build_request_args(node, ctx)
|
|
1830
1871
|
|
|
1831
1872
|
raw_name = node.name or "fetch"
|
|
1832
1873
|
method_name = to_camel_case(to_snake_case(raw_name))
|
|
1833
1874
|
if raw_name == "fetch":
|
|
1834
1875
|
method_name = "fetch"
|
|
1835
|
-
|
|
1836
|
-
placeholders = spec.placeholders
|
|
1837
|
-
if placeholders:
|
|
1838
|
-
required = [p for p in placeholders if not p.is_optional]
|
|
1839
|
-
optional = [p for p in placeholders if p.is_optional]
|
|
1840
|
-
ordered_names = [p.name for p in required + optional]
|
|
1841
|
-
ph_param = ", {" + ", ".join(ordered_names) + "}"
|
|
1842
|
-
else:
|
|
1843
|
-
ph_param = ""
|
|
1844
1876
|
sig = f"static async {method_name}(client{ph_param})"
|
|
1845
1877
|
|
|
1846
1878
|
parent = node.parent
|
|
@@ -1866,42 +1898,19 @@ def _js_rest_method(node: a.RequestConfig, ctx: ConverterContext) -> list[str]:
|
|
|
1866
1898
|
for doc_line in node.doc.splitlines():
|
|
1867
1899
|
lines.append(f"{i1} * {doc_line}")
|
|
1868
1900
|
lines.append(f"{i1} */")
|
|
1869
|
-
# JSDoc: params signature + return type
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
)
|
|
1880
|
-
jsdoc_lines.append(
|
|
1881
|
-
f"{i1} * @param {{{_js_ph_jsdoc(p)}}} {bracket_name}"
|
|
1882
|
-
)
|
|
1883
|
-
jsdoc_lines.append(f"{i1} * @returns {{Promise<{return_union}>}}")
|
|
1884
|
-
jsdoc_lines.append(f"{i1} */")
|
|
1885
|
-
lines.extend(jsdoc_lines)
|
|
1901
|
+
# JSDoc: params signature + return type
|
|
1902
|
+
lines.append(f"{i1}/**")
|
|
1903
|
+
for p in sorted(spec.placeholders, key=lambda p: p.is_optional):
|
|
1904
|
+
t = _JS_PRIM_JSDOC[p.type_name]
|
|
1905
|
+
if p.is_array:
|
|
1906
|
+
t = f"{t}[]"
|
|
1907
|
+
bracket = f"[params.{p.name}]" if p.is_optional else f"params.{p.name}"
|
|
1908
|
+
lines.append(f"{i1} * @param {{{t}}} {bracket}")
|
|
1909
|
+
lines.append(f"{i1} * @returns {{Promise<{return_union}>}}")
|
|
1910
|
+
lines.append(f"{i1} */")
|
|
1886
1911
|
lines.append(f"{i1}{sig} {{")
|
|
1887
1912
|
|
|
1888
|
-
#
|
|
1889
|
-
pre_lines: list[str] = []
|
|
1890
|
-
params_var: str | None = None
|
|
1891
|
-
headers_var: str | None = None
|
|
1892
|
-
cookies_var: str | None = None
|
|
1893
|
-
if spec.params and _js_dict_needs_builder(spec.params):
|
|
1894
|
-
params_var = "_params"
|
|
1895
|
-
pre_lines.extend(_js_emit_params_builder(params_var, spec.params, i3))
|
|
1896
|
-
if spec.headers and _js_dict_needs_builder(spec.headers):
|
|
1897
|
-
headers_var = "_headers"
|
|
1898
|
-
pre_lines.extend(_js_emit_obj_builder(headers_var, spec.headers, i3))
|
|
1899
|
-
if spec.cookies and _js_dict_needs_builder(spec.cookies):
|
|
1900
|
-
cookies_var = "_cookies"
|
|
1901
|
-
pre_lines.extend(_js_emit_obj_builder(cookies_var, spec.cookies, i3))
|
|
1902
|
-
|
|
1903
|
-
# Everything below — response handling & request — goes inside try/catch
|
|
1904
|
-
# so transport failures turn into TransportErr.
|
|
1913
|
+
# try/catch so transport failures turn into TransportErr.
|
|
1905
1914
|
lines.append(f"{i2}let _resp;")
|
|
1906
1915
|
lines.append(f"{i2}try {{")
|
|
1907
1916
|
lines.extend(pre_lines)
|
|
@@ -1912,48 +1921,34 @@ def _js_rest_method(node: a.RequestConfig, ctx: ConverterContext) -> list[str]:
|
|
|
1912
1921
|
f"{i4}url: {_js_render_value(spec.url)},",
|
|
1913
1922
|
f"{i4}validateStatus: () => true,",
|
|
1914
1923
|
]
|
|
1915
|
-
if
|
|
1916
|
-
params_expr = (
|
|
1917
|
-
params_var if params_var else _js_render_obj(spec.params)
|
|
1918
|
-
)
|
|
1924
|
+
if params_expr:
|
|
1919
1925
|
req_props.append(f"{i4}params: {params_expr},")
|
|
1920
|
-
if
|
|
1921
|
-
headers_expr = (
|
|
1922
|
-
headers_var if headers_var else _js_render_obj(spec.headers)
|
|
1923
|
-
)
|
|
1926
|
+
if headers_expr:
|
|
1924
1927
|
req_props.append(f"{i4}headers: {headers_expr},")
|
|
1925
|
-
|
|
1926
|
-
if body_result:
|
|
1927
|
-
_, body_expr = body_result
|
|
1928
|
+
if body_expr:
|
|
1928
1929
|
req_props.append(f"{i4}data: {body_expr},")
|
|
1929
1930
|
lines.append(f"{i3}_resp = await client.request({{")
|
|
1930
1931
|
lines.extend(req_props)
|
|
1931
1932
|
lines.append(f"{i3}}});")
|
|
1932
1933
|
else:
|
|
1933
|
-
if
|
|
1934
|
+
if params_expr:
|
|
1934
1935
|
url_inner = _PH.sub(
|
|
1935
1936
|
lambda mm: "${" + mm.group(1) + "}",
|
|
1936
1937
|
spec.url.replace("`", "\\`"),
|
|
1937
1938
|
)
|
|
1938
|
-
if
|
|
1939
|
-
url_expr = f"`{url_inner}?${{{
|
|
1939
|
+
if params_expr == "_params":
|
|
1940
|
+
url_expr = f"`{url_inner}?${{{params_expr}.toString()}}`"
|
|
1940
1941
|
else:
|
|
1941
|
-
params_obj = _js_render_obj(spec.params)
|
|
1942
1942
|
url_expr = (
|
|
1943
|
-
f"`{url_inner}?${{new URLSearchParams({
|
|
1943
|
+
f"`{url_inner}?${{new URLSearchParams({params_expr})}}`"
|
|
1944
1944
|
)
|
|
1945
1945
|
else:
|
|
1946
1946
|
url_expr = _js_render_value(spec.url)
|
|
1947
1947
|
|
|
1948
1948
|
options: list[str] = [f"{i4}method: {spec.method!r},"]
|
|
1949
|
-
if
|
|
1950
|
-
headers_expr = (
|
|
1951
|
-
headers_var if headers_var else _js_render_obj(spec.headers)
|
|
1952
|
-
)
|
|
1949
|
+
if headers_expr:
|
|
1953
1950
|
options.append(f"{i4}headers: {headers_expr},")
|
|
1954
|
-
|
|
1955
|
-
if body_result:
|
|
1956
|
-
_, body_expr = body_result
|
|
1951
|
+
if body_expr:
|
|
1957
1952
|
options.append(f"{i4}body: {body_expr},")
|
|
1958
1953
|
lines.append(f"{i3}_resp = await client({url_expr}, {{")
|
|
1959
1954
|
lines.extend(options)
|
|
@@ -1990,79 +1985,62 @@ def _js_rest_method(node: a.RequestConfig, ctx: ConverterContext) -> list[str]:
|
|
|
1990
1985
|
return lines
|
|
1991
1986
|
|
|
1992
1987
|
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
def _js_fetch_method(node: a.RequestConfig, ctx: ConverterContext) -> list[str]:
|
|
2010
|
-
"""Generate non-REST async fetch method for JS (fetch API or axios)."""
|
|
2011
|
-
spec = normalize_placeholder_names(
|
|
2012
|
-
parse_to_spec(node.raw_payload), _js_name
|
|
2013
|
-
)
|
|
2014
|
-
http_client = ctx.meta.get("http_client", "fetch")
|
|
2015
|
-
|
|
2016
|
-
ind = ctx.indent_char
|
|
2017
|
-
i1 = ctx.indent
|
|
2018
|
-
i2 = i1 + ind
|
|
2019
|
-
i3 = i2 + ind
|
|
1988
|
+
@JS_CONVERTER(a.MethodFetch)
|
|
1989
|
+
def pre_method_fetch(node: a.MethodFetch, ctx: ConverterContext):
|
|
1990
|
+
(
|
|
1991
|
+
spec,
|
|
1992
|
+
_pre_lines,
|
|
1993
|
+
ph_param,
|
|
1994
|
+
http_client,
|
|
1995
|
+
params_expr,
|
|
1996
|
+
headers_expr,
|
|
1997
|
+
_cookies_expr,
|
|
1998
|
+
body_expr,
|
|
1999
|
+
i1,
|
|
2000
|
+
i2,
|
|
2001
|
+
i3,
|
|
2002
|
+
_i4,
|
|
2003
|
+
) = _js_build_request_args(node, ctx)
|
|
2020
2004
|
|
|
2021
2005
|
struct_name = to_pascal_case(node.parent.name) # type: ignore[union-attr]
|
|
2022
|
-
ph_names = [p.name for p in spec.placeholders]
|
|
2023
2006
|
method_name = "fetch" + (to_pascal_case(node.name) if node.name else "")
|
|
2024
|
-
ph_param = (", {" + ", ".join(ph_names) + "}") if ph_names else ""
|
|
2025
|
-
sig = f"static async {method_name}(client{ph_param})"
|
|
2026
2007
|
|
|
2027
2008
|
def _response_lines(data_expr: str) -> list[str]:
|
|
2028
|
-
|
|
2009
|
+
rl: list[str] = []
|
|
2029
2010
|
if node.response_path:
|
|
2030
2011
|
accessor = "".join(
|
|
2031
2012
|
f"[{p!r}]" for p in node.response_path.split(".")
|
|
2032
2013
|
)
|
|
2033
|
-
|
|
2014
|
+
rl.append(f"{i2}const _data = {data_expr};")
|
|
2034
2015
|
if node.response_join:
|
|
2035
|
-
|
|
2016
|
+
rl.append(
|
|
2036
2017
|
f"{i2}const _body = _data{accessor}.join({node.response_join!r});"
|
|
2037
2018
|
)
|
|
2038
2019
|
else:
|
|
2039
|
-
|
|
2020
|
+
rl.append(f"{i2}const _body = _data{accessor};")
|
|
2040
2021
|
else:
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
return
|
|
2022
|
+
rl.append(f"{i2}const _body = {data_expr};")
|
|
2023
|
+
rl.append(f"{i2}return new {struct_name}(_body);")
|
|
2024
|
+
return rl
|
|
2044
2025
|
|
|
2045
|
-
lines: list[str] = [f"{i1}{
|
|
2026
|
+
lines: list[str] = [f"{i1}static async {method_name}(client{ph_param}) {{"]
|
|
2046
2027
|
|
|
2047
2028
|
if http_client == "fetch":
|
|
2048
|
-
if
|
|
2029
|
+
if params_expr:
|
|
2049
2030
|
url_inner = _PH.sub(r"${\1}", spec.url.replace("`", "\\`"))
|
|
2050
|
-
|
|
2051
|
-
url_expr = f"`{url_inner}?${{new URLSearchParams({params_obj})}}`"
|
|
2031
|
+
url_expr = f"`{url_inner}?${{new URLSearchParams({params_expr})}}`"
|
|
2052
2032
|
else:
|
|
2053
2033
|
url_expr = _js_render_value(spec.url)
|
|
2054
2034
|
|
|
2055
2035
|
options: list[str] = [f"{i3}method: {spec.method!r},"]
|
|
2056
|
-
if
|
|
2057
|
-
options.append(f"{i3}headers: {
|
|
2036
|
+
if headers_expr:
|
|
2037
|
+
options.append(f"{i3}headers: {headers_expr},")
|
|
2058
2038
|
if spec.cookies:
|
|
2059
2039
|
cookie_str = "; ".join(f"{k}={v}" for k, v in spec.cookies.items())
|
|
2060
2040
|
options.append(
|
|
2061
2041
|
f"{i3}// cookies: {cookie_str!r} /* set via headers or credentials */"
|
|
2062
2042
|
)
|
|
2063
|
-
|
|
2064
|
-
if body_result:
|
|
2065
|
-
_, body_expr = body_result
|
|
2043
|
+
if body_expr:
|
|
2066
2044
|
options.append(f"{i3}body: {body_expr},")
|
|
2067
2045
|
|
|
2068
2046
|
lines.append(f"{i2}const _resp = await client({url_expr}, {{")
|
|
@@ -2081,15 +2059,13 @@ def _js_fetch_method(node: a.RequestConfig, ctx: ConverterContext) -> list[str]:
|
|
|
2081
2059
|
f"{i3}method: {spec.method!r},",
|
|
2082
2060
|
f"{i3}url: {_js_render_value(spec.url)},",
|
|
2083
2061
|
]
|
|
2084
|
-
if
|
|
2085
|
-
req_props.append(f"{i3}params: {
|
|
2086
|
-
if
|
|
2087
|
-
req_props.append(f"{i3}headers: {
|
|
2062
|
+
if params_expr:
|
|
2063
|
+
req_props.append(f"{i3}params: {params_expr},")
|
|
2064
|
+
if headers_expr:
|
|
2065
|
+
req_props.append(f"{i3}headers: {headers_expr},")
|
|
2088
2066
|
if spec.cookies:
|
|
2089
2067
|
req_props.append(f"{i3}// cookies: {_js_render_obj(spec.cookies)},")
|
|
2090
|
-
|
|
2091
|
-
if body_result:
|
|
2092
|
-
_, body_expr = body_result
|
|
2068
|
+
if body_expr:
|
|
2093
2069
|
req_props.append(f"{i3}data: {body_expr},")
|
|
2094
2070
|
|
|
2095
2071
|
lines.append(f"{i2}const _resp = await client.request({{")
|
|
@@ -2104,13 +2080,6 @@ def _js_fetch_method(node: a.RequestConfig, ctx: ConverterContext) -> list[str]:
|
|
|
2104
2080
|
return lines
|
|
2105
2081
|
|
|
2106
2082
|
|
|
2107
|
-
@JS_CONVERTER(a.RequestConfig)
|
|
2108
|
-
def pre_request_config(node: a.RequestConfig, ctx: ConverterContext):
|
|
2109
|
-
if isinstance(node.parent, a.StructRest):
|
|
2110
|
-
return _js_rest_method(node, ctx)
|
|
2111
|
-
return _js_fetch_method(node, ctx)
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
2083
|
@JS_CONVERTER(a.ErrorResponse)
|
|
2115
2084
|
def pre_error_response(node: a.ErrorResponse, ctx: ConverterContext):
|
|
2116
2085
|
return None
|