openapi_generate_typescript_fetch 0.1.0
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.
- checksums.yaml +7 -0
- data/LICENSE.txt +17 -0
- data/lib/openapi_generate_typescript_fetch/schema.rb +115 -0
- data/lib/openapi_generate_typescript_fetch/taskinfo.rb +52 -0
- data/lib/openapi_generate_typescript_fetch/tasks.rb +43 -0
- data/lib/openapi_generate_typescript_fetch/version.rb +9 -0
- data/lib/openapi_generate_typescript_fetch.rb +107 -0
- data/template/package.json.erb +48 -0
- data/template/src/callclasses.ts.erb +542 -0
- data/template/src/helpers.ts.erb +191 -0
- data/template/src/index.ts.erb +19 -0
- data/template/src/schemas.ts.erb +1016 -0
- data/template/src/servers.ts.erb +226 -0
- data/template/src/shared.ts.erb +114 -0
- data/template/test/callclasses.ts.erb +844 -0
- data/template/test/helpers.ts.erb +1214 -0
- data/template/test/makers.ts.erb +1237 -0
- data/template/test/schemas.ts.erb +664 -0
- data/template/test/servers.ts.erb +109 -0
- data/template/test/shared.ts.erb +107 -0
- data/template/tsconfig.json.erb +51 -0
- metadata +112 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
<%=
|
|
2
|
+
require 'uri'
|
|
3
|
+
Gen.output.config = Gen.x.cfg['ts_indentation']
|
|
4
|
+
Gen.x.generator_info
|
|
5
|
+
%>
|
|
6
|
+
|
|
7
|
+
import type {<%= %w[String2String CallExtras EncoderFunction EncodedBodyType].concat(Gen.x.schemas.import_types).join(', ') %>} from './schemas.d.ts';
|
|
8
|
+
import { <%= %w[isObject isString jsonEncode].concat(Gen.x.schemas.imports).join(', ') %>} from './schemas.js';
|
|
9
|
+
import type {AllParameters, Shared} from './shared.d.ts';
|
|
10
|
+
import { <%= %w[shared].concat(Gen.x.shared.functions).join(', ') %> } from './shared.js';
|
|
11
|
+
import type {LoaderFunction, LoaderMap} from './helpers.d.ts';
|
|
12
|
+
import {setExtras, query, headers, getLoader} from './helpers.js';
|
|
13
|
+
|
|
14
|
+
// Used as base classes for per-operation exception classes.
|
|
15
|
+
export class RequestError extends Error {
|
|
16
|
+
constructor(msg: string) {
|
|
17
|
+
super(msg);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class ResponseError extends Error {
|
|
22
|
+
response: Response;
|
|
23
|
+
body?: any;
|
|
24
|
+
constructor(msg: string, r: Response, b?: any) {
|
|
25
|
+
super(msg);
|
|
26
|
+
this.response = r;
|
|
27
|
+
if (b)
|
|
28
|
+
this.body = b;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Classes can return this to act as fetch setup.
|
|
33
|
+
export type UrlRequestInit = {
|
|
34
|
+
url: string;
|
|
35
|
+
ri: RequestInit;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type RequestInitModifier = (ur: UrlRequestInit) => void;
|
|
39
|
+
|
|
40
|
+
export function isUrlRequestInit(x: unknown): x is UrlRequestInit {
|
|
41
|
+
if (isObject(x)) {
|
|
42
|
+
const a: any = x;
|
|
43
|
+
return a.url !== undefined && a.ri !== undefined && isString(a.url) && isObject(a.ri);
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type PrepareArgs = {
|
|
49
|
+
ignoreMismatches?: boolean;
|
|
50
|
+
createAbortSignal?: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
<%=
|
|
54
|
+
out = []
|
|
55
|
+
def known
|
|
56
|
+
{
|
|
57
|
+
'application/json' => 'scApplicationJson',
|
|
58
|
+
'application/xml' => 'scApplicationXml',
|
|
59
|
+
'text/plain' => 'scTextPlain',
|
|
60
|
+
'text/xml' => 'scTextXml',
|
|
61
|
+
'application/json; charset=utf-8' => 'scApplicationJsonUtf8',
|
|
62
|
+
'application/xml; charset=utf-8' => 'scApplicationXmlUtf8',
|
|
63
|
+
'text/plain; charset=utf-8' => 'scTextPlainUtf8',
|
|
64
|
+
'text/xml; charset=utf-8' => 'scTextXmlUtf8',
|
|
65
|
+
'application/octet-stream' => 'scApplicationOctetStream',
|
|
66
|
+
'GET' => 'scGET',
|
|
67
|
+
'POST' => 'scPOST',
|
|
68
|
+
'PUT' => 'scPUT',
|
|
69
|
+
'DELETE' => 'scDELETE',
|
|
70
|
+
'PATCH' => 'scPATCH',
|
|
71
|
+
'content-type' => 'scContentType',
|
|
72
|
+
'content-length' => 'scContentLength'
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def string_const(value)
|
|
77
|
+
k = known()
|
|
78
|
+
k.fetch(value.downcase, k.fetch(value.upcase, "'#{value}'"))
|
|
79
|
+
end
|
|
80
|
+
known().each do |str, con|
|
|
81
|
+
out.push("const #{con} = '#{str}';")
|
|
82
|
+
end
|
|
83
|
+
Gen.output.join(out)
|
|
84
|
+
%>
|
|
85
|
+
|
|
86
|
+
const defaultLoaders: Array<[string, LoaderFunction]> = [
|
|
87
|
+
[<%= string_const('application/json') %>, (response: Response) => response.json()],
|
|
88
|
+
[<%= string_const('application/xml') %>, (response: Response) => response.text()],
|
|
89
|
+
[<%= string_const('text/xml') %>, (response: Response) => response.text()],
|
|
90
|
+
[<%= string_const('text/plain') %>, (response: Response) => response.text()],
|
|
91
|
+
[<%= string_const('application/json; charset=utf-8') %>, (response: Response) => response.json()],
|
|
92
|
+
[<%= string_const('application/xml; charset=utf-8') %>, (response: Response) => response.text()],
|
|
93
|
+
[<%= string_const('text/xml; charset=utf-8') %>, (response: Response) => response.text()],
|
|
94
|
+
[<%= string_const('text/plain; charset=utf-8') %>, (response: Response) => response.text()],
|
|
95
|
+
[<%= string_const('application/octet-stream') %>, (response: Response) => response.blob()]
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
export type ResponseUnexpected = {
|
|
99
|
+
body?: any;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Call classes for each operationId.
|
|
103
|
+
|
|
104
|
+
<%=
|
|
105
|
+
# Book-keeping for:
|
|
106
|
+
# parameter name and type mapped to classes that use it.
|
|
107
|
+
paramtype2oos = {}
|
|
108
|
+
types2info = {}
|
|
109
|
+
out = []
|
|
110
|
+
Gen.doc['paths'].each do |path, path_item_object|
|
|
111
|
+
oos = OpenAPISourceTools::ApiObjects.operation_objects(path_item_object)
|
|
112
|
+
oos.each do |method, operation_object|
|
|
113
|
+
clsName = LuckyCase.pascal_case(operation_object['operationId'])
|
|
114
|
+
clsRespName = "#{clsName}Response"
|
|
115
|
+
clsReqError = "#{clsName}Error"
|
|
116
|
+
clsRespError = "#{clsRespName}Error"
|
|
117
|
+
operation_object[:name] = clsName
|
|
118
|
+
operation_object[:resp_name] = clsRespName
|
|
119
|
+
operation_object[:req_error] = clsReqError
|
|
120
|
+
operation_object[:resp_error] = clsRespError
|
|
121
|
+
paramless = operation_object[:parameterless]
|
|
122
|
+
unless paramless
|
|
123
|
+
# Book-keeping of classes that use the specific parameter name and type combination.
|
|
124
|
+
operation_object[:paraschemas].each do |ps|
|
|
125
|
+
key = "#{ps[:param][:name]}:#{ps[:schema][:name]}"
|
|
126
|
+
paramtype2oos[key] = [] unless paramtype2oos.key?(key)
|
|
127
|
+
paramtype2oos[key].push(operation_object)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
req_body = operation_object['requestBody']
|
|
131
|
+
has_encoder = false
|
|
132
|
+
default_encoder = nil
|
|
133
|
+
if req_body && req_body['content']
|
|
134
|
+
req_body['content'].each do |media_type, mto|
|
|
135
|
+
has_encoder = true
|
|
136
|
+
default_encoder = 'jsonEncode' if media_type.downcase == 'application/json'
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
operation_object[:request] = {
|
|
140
|
+
has_encoder: has_encoder
|
|
141
|
+
}
|
|
142
|
+
# Loop over all reponses and get the references and status codes.
|
|
143
|
+
# That will gather all types and codes for the response type.
|
|
144
|
+
responses = operation_object['responses'] || {}
|
|
145
|
+
response_decls = []
|
|
146
|
+
status_decls = []
|
|
147
|
+
body_decls = Set.new
|
|
148
|
+
code2content = {}
|
|
149
|
+
operation_object[:responses] = { code2content: code2content }
|
|
150
|
+
responses.each do |status_code, response_object|
|
|
151
|
+
response_object = Gen.h.dereference(response_object)
|
|
152
|
+
code2content[status_code] = {
|
|
153
|
+
code: status_code,
|
|
154
|
+
cond: status_code.downcase == 'default' ? nil : Gen.h.response_code_condition(status_code, var: 'response.status'),
|
|
155
|
+
content: {},
|
|
156
|
+
status_var: "c#{status_code.downcase == 'default' ? 'Other' : status_code.downcase}"
|
|
157
|
+
}
|
|
158
|
+
# Build status code to content-type to schema mapping to find out the
|
|
159
|
+
# isX and baseX functions.
|
|
160
|
+
response_types = {}
|
|
161
|
+
if response_object['content']
|
|
162
|
+
response_object['content'].each do |media_type, mto|
|
|
163
|
+
code2content[status_code][:content][media_type.downcase] = { mto: mto }
|
|
164
|
+
if response_types.key?(mto[:decoded][:name])
|
|
165
|
+
code2content[status_code][:content][media_type.downcase][:rt] = response_types[mto[:decoded][:name]]
|
|
166
|
+
next
|
|
167
|
+
end
|
|
168
|
+
rt = {
|
|
169
|
+
type: mto[:decoded],
|
|
170
|
+
name: LuckyCase.camel_case(mto[:decoded][:name])
|
|
171
|
+
}
|
|
172
|
+
rt[:name] = 'body' if rt[:name] == 'any'
|
|
173
|
+
rt[:decl] = "#{rt[:name]}?: #{rt[:type][:name]};"
|
|
174
|
+
response_types[mto[:decoded][:name]] = rt
|
|
175
|
+
code2content[status_code][:content][media_type.downcase][:rt] = rt
|
|
176
|
+
body_decls.add(rt[:decl])
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
if response_types.empty?
|
|
180
|
+
rt = {
|
|
181
|
+
type: response_object,
|
|
182
|
+
name: LuckyCase.camel_case(response_object[:name]),
|
|
183
|
+
}
|
|
184
|
+
rt[:decl] = "#{rt[:name]}?: #{rt[:type][:name]};"
|
|
185
|
+
response_types[rt[:name]] = rt
|
|
186
|
+
end
|
|
187
|
+
types = response_types.keys.sort!.join('|')
|
|
188
|
+
typename = "#{clsRespName}#{LuckyCase.pascal_case(status_code.downcase)}"
|
|
189
|
+
response_object[:response] = {
|
|
190
|
+
types: response_types,
|
|
191
|
+
name: typename
|
|
192
|
+
}
|
|
193
|
+
if types2info.key?(types)
|
|
194
|
+
rts = types2info[types][:response_types]
|
|
195
|
+
s = <<EOB
|
|
196
|
+
export type #{typename} = #{types2info[types][:name]};
|
|
197
|
+
#{rts.keys.sort!.map { |k| "// #{rts[k][:decl]}" }.join("\n")}
|
|
198
|
+
EOB
|
|
199
|
+
else
|
|
200
|
+
types2info[types] = {
|
|
201
|
+
response_types: response_types,
|
|
202
|
+
name: typename
|
|
203
|
+
}
|
|
204
|
+
s = <<EOB
|
|
205
|
+
export type #{typename} = {
|
|
206
|
+
#{response_types.keys.sort!.map { |k| response_types[k][:decl] }.join("\n ")}
|
|
207
|
+
}
|
|
208
|
+
EOB
|
|
209
|
+
end
|
|
210
|
+
response_decls.push(s)
|
|
211
|
+
sd = {
|
|
212
|
+
name: "c#{status_code == 'default' ? 'Other' : status_code.downcase}",
|
|
213
|
+
type: typename,
|
|
214
|
+
code: status_code
|
|
215
|
+
}
|
|
216
|
+
sd[:decl] = "#{sd[:name]}?: #{sd[:type]};"
|
|
217
|
+
status_decls.push(sd)
|
|
218
|
+
end
|
|
219
|
+
unless responses.key?('default')
|
|
220
|
+
sd = {
|
|
221
|
+
name: 'cOther',
|
|
222
|
+
type: "ResponseUnexpected"
|
|
223
|
+
}
|
|
224
|
+
sd[:decl] = "#{sd[:name]}?: #{sd[:type]};"
|
|
225
|
+
status_decls.push(sd)
|
|
226
|
+
end
|
|
227
|
+
operation_object[:status_decls] = status_decls
|
|
228
|
+
ps = operation_object[:paraschemas].map { |x| x[:param] }
|
|
229
|
+
path_params = ps.select { |p| p['in'] == 'path' }.sort! do |a, b|
|
|
230
|
+
d = b['name'].length <=> a['name'].length
|
|
231
|
+
d.zero? ? a['name'] <=> b['name'] : d
|
|
232
|
+
end
|
|
233
|
+
prepared_path = path
|
|
234
|
+
path_params.each do |pp|
|
|
235
|
+
prepared_path = prepared_path.gsub("{#{pp['name']}}", "${cand.#{pp[:name]}}");
|
|
236
|
+
end
|
|
237
|
+
query_params = ps.select { |p| p['in'] == 'query' }
|
|
238
|
+
query_func = query_params.map { |qp|
|
|
239
|
+
"if (cand.#{qp[:name]} !== undefined) q.push(`#{URI.encode_uri_component(qp['name'])})=${encodeURIComponent(cand.#{qp[:name]})}`);"
|
|
240
|
+
}.join("\n")
|
|
241
|
+
header_params = ps.select { |p| p['in'] == 'header' }
|
|
242
|
+
header_func = header_params.map { |hp|
|
|
243
|
+
"if (cand.#{hp[:name]} !== undefined) headers.set('#{hp['name']}', cand.#{hp[:name]}.toString());"
|
|
244
|
+
}.join("\n")
|
|
245
|
+
encode = '// No body encoding.'
|
|
246
|
+
if has_encoder
|
|
247
|
+
body_encodings = req_body[:body_encodings]
|
|
248
|
+
encode = <<EOB
|
|
249
|
+
static body2mediaTypes: Map<string, Array<string>> = new Map([
|
|
250
|
+
#{body_encodings.keys.sort!.map do |k|
|
|
251
|
+
mts = (body_encodings[k] || Set.new).to_a.sort!
|
|
252
|
+
"[#{string_const(k)}, [ #{mts.map { |mt| string_const(mt) }.join(', ')} ] ]"
|
|
253
|
+
end.join(",\n ")}
|
|
254
|
+
]);
|
|
255
|
+
static allBodyMediaTypes: Array<string> = [
|
|
256
|
+
#{body_encodings.values.map {|mt| mt.to_a }.flatten.uniq.sort!.map { |mt| string_const(mt) }.join(', ')}
|
|
257
|
+
];
|
|
258
|
+
private encoderResult(cand: #{operation_object[:args_name]}): [EncodedBodyType, string, number, String2String] {
|
|
259
|
+
const encoder = this.encoder || #{clsName}.encoder || shared.encoder;
|
|
260
|
+
if (encoder === null) {
|
|
261
|
+
throw new #{clsReqError}('#{clsName} no request body encoder in instance, class, nor shared.');
|
|
262
|
+
}
|
|
263
|
+
let allowedTypes: Array<string> = [];
|
|
264
|
+
let src: #{req_body[:body_types].key?('any') ? 'any' : req_body[:body_types].keys.sort!.join('|')}|undefined;
|
|
265
|
+
let bname: string = '';
|
|
266
|
+
#{req_body[:body_types].keys.sort!.map { |k|
|
|
267
|
+
body_name = req_body[:body_types][k][:body_name]
|
|
268
|
+
s = <<EOC
|
|
269
|
+
if (cand.#{body_name} !== undefined) {
|
|
270
|
+
allowedTypes = #{clsName}.body2mediaTypes.get('#{body_name}') || [];
|
|
271
|
+
src = cand.#{body_name};
|
|
272
|
+
bname = '#{body_name}';
|
|
273
|
+
}
|
|
274
|
+
EOC
|
|
275
|
+
s
|
|
276
|
+
}.join("else\n")}
|
|
277
|
+
#{req_body[:body_types].keys.empty? ? '' : 'else '}if (cand.body !== undefined) {
|
|
278
|
+
allowedTypes = #{clsName}.allBodyMediaTypes;
|
|
279
|
+
src = cand.body;
|
|
280
|
+
bname = 'body';
|
|
281
|
+
}
|
|
282
|
+
if (src === undefined)
|
|
283
|
+
throw new #{clsReqError}('#{clsName} no body present in request parameters for encoding.');
|
|
284
|
+
const [body, contentType, contentLength, headers] = encoder!(src);
|
|
285
|
+
if (!allowedTypes!.includes(contentType)) {
|
|
286
|
+
const ct = contentType.split(';')[0].trim();
|
|
287
|
+
if (!allowedTypes!.includes(ct))
|
|
288
|
+
throw new #{clsReqError}(`#{clsName} encoded content-type ${contentType} is not allowed for ${bname}. Allowed: ${allowedTypes.join(', ')}`);
|
|
289
|
+
}
|
|
290
|
+
return [body, contentType, contentLength, headers];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private encodeBody(cand: #{operation_object[:args_name]}, out: UrlRequestInit): void {
|
|
294
|
+
if (cand.encoded !== undefined) {
|
|
295
|
+
// Content-type and content-length headers are expected to have been added to extras.
|
|
296
|
+
out.ri.body = cand.encoded;
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const [body, contentType, contentLength, headers] = this.encoderResult(cand);
|
|
300
|
+
out.ri.body = body;
|
|
301
|
+
const hdrs: Headers = out.ri.headers as Headers;
|
|
302
|
+
hdrs.set(#{string_const('content-type')}, contentType);
|
|
303
|
+
hdrs.set(#{string_const('content-length')}, contentLength.toString());
|
|
304
|
+
for (const key in headers) {
|
|
305
|
+
hdrs.set(key.toLowerCase(), headers[key]);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
EOB
|
|
310
|
+
end
|
|
311
|
+
status_order = code2content.keys.sort! do |a, b|
|
|
312
|
+
ac = code2content[a][:cond]
|
|
313
|
+
bc = code2content[b][:cond]
|
|
314
|
+
if ac.nil?
|
|
315
|
+
1
|
|
316
|
+
elsif bc.nil?
|
|
317
|
+
-1
|
|
318
|
+
else
|
|
319
|
+
d = ac.length <=> bc.length
|
|
320
|
+
d.zero? ? ac <=> bc : d
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
operation_object[:responses][:status_order] = status_order
|
|
324
|
+
body_check = status_order.map do |key|
|
|
325
|
+
info = code2content[key]
|
|
326
|
+
stmt = info[:cond].nil? ? '' : "if (#{info[:cond]})"
|
|
327
|
+
if info[:content].empty?
|
|
328
|
+
s = <<EOB
|
|
329
|
+
#{stmt} {
|
|
330
|
+
this.#{info[:status_var]} = {};
|
|
331
|
+
}
|
|
332
|
+
EOB
|
|
333
|
+
else
|
|
334
|
+
s = <<EOB
|
|
335
|
+
#{stmt} {
|
|
336
|
+
#{info[:content].keys.sort!.map do |ct|
|
|
337
|
+
mr = info[:content][ct]
|
|
338
|
+
cc = <<EOC
|
|
339
|
+
if (contentType.startsWith(#{string_const(ct)})) {
|
|
340
|
+
if (#{mr[:rt][:type][:is]}(rawBody)) {
|
|
341
|
+
this.#{mr[:rt][:name]} = #{mr[:rt][:type][:base]}(rawBody);
|
|
342
|
+
this.#{info[:status_var]} = { #{mr[:rt][:name]}: this.#{mr[:rt][:name]} };
|
|
343
|
+
} else this.mismatch = 2;
|
|
344
|
+
}
|
|
345
|
+
EOC
|
|
346
|
+
cc
|
|
347
|
+
end.push('this.mismatch = 1;').join("else\n")}
|
|
348
|
+
}
|
|
349
|
+
EOB
|
|
350
|
+
end
|
|
351
|
+
s
|
|
352
|
+
end.join("\nelse\n")
|
|
353
|
+
unless code2content.key?('default')
|
|
354
|
+
body_check += " else this.mismatch = 2;"
|
|
355
|
+
end
|
|
356
|
+
Gen.x.callclasses.classes.push(clsName)
|
|
357
|
+
Gen.x.callclasses.types.push("#{clsRespName}Args")
|
|
358
|
+
Gen.x.callclasses.classes.push(clsRespName)
|
|
359
|
+
Gen.x.callclasses.classes.push(clsReqError)
|
|
360
|
+
Gen.x.callclasses.classes.push(clsRespError)
|
|
361
|
+
c = <<EOB
|
|
362
|
+
// #{path} #{method.upcase}
|
|
363
|
+
export class #{clsName} {
|
|
364
|
+
// Class-level fall-back values.
|
|
365
|
+
#{paramless ? '// No parameters.' : "static params: Partial<#{operation_object[:args_name]}> = {};"}
|
|
366
|
+
static defaultRequestInit: RequestInit = {};
|
|
367
|
+
static extras: CallExtras = {};
|
|
368
|
+
static modifier: RequestInitModifier|null|undefined;
|
|
369
|
+
static serverUrl: string|null|undefined;
|
|
370
|
+
#{has_encoder ? "static encoder: EncoderFunction|null#{default_encoder.nil? ? '' : " = #{default_encoder}"};" : '// No body encoder.'}
|
|
371
|
+
|
|
372
|
+
#{paramless ? '// No parameters.' : "params: Partial<#{operation_object[:args_name]}>;"}
|
|
373
|
+
extras?: CallExtras;
|
|
374
|
+
modifier?: RequestInitModifier|null;
|
|
375
|
+
serverUrl?: string;
|
|
376
|
+
#{has_encoder ? "encoder?: EncoderFunction|null;" : '// No body encoder.'}
|
|
377
|
+
|
|
378
|
+
static abortSignalable: boolean|undefined;
|
|
379
|
+
|
|
380
|
+
abortSignalable?: boolean;
|
|
381
|
+
abortController: AbortController | null;
|
|
382
|
+
|
|
383
|
+
urlRequestInit?: UrlRequestInit;
|
|
384
|
+
|
|
385
|
+
constructor(#{paramless ? '' : "params?: Partial<#{operation_object[:args_name]}>"}) {
|
|
386
|
+
#{paramless ? '// No parameters.' : "this.params = params || {};"}
|
|
387
|
+
this.abortController = null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Methods to set instance values and return this so calls can be chained.
|
|
391
|
+
|
|
392
|
+
setExtras(extras: CallExtras): #{clsName} {
|
|
393
|
+
this.extras = setExtras(this.extras, extras);
|
|
394
|
+
return this;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
setModifier(modifier: RequestInitModifier|null): #{clsName} {
|
|
398
|
+
this.modifier = modifier;
|
|
399
|
+
return this;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
setServerUrl(serverUrl: string): #{clsName} {
|
|
403
|
+
this.serverUrl = serverUrl;
|
|
404
|
+
return this;
|
|
405
|
+
}
|
|
406
|
+
#{has_encoder ? "\n setEncoder(encoder: EncoderFunction|null): #{clsName} { this.encoder = encoder; return this; }" : ''}
|
|
407
|
+
|
|
408
|
+
private path(cand: #{operation_object[:args_name] || 'object'}): string {
|
|
409
|
+
return `${this.serverUrl || #{clsName}.serverUrl || shared.serverUrl}#{prepared_path}`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
static queryParamNames: Array<string> = [ #{query_params.map { |qp| "'#{qp['name']}'" }.sort!.join(', ')} ];
|
|
413
|
+
private query(cand: #{operation_object[:args_name] || 'object'}): string {
|
|
414
|
+
const q: Array<string> = [];
|
|
415
|
+
#{query_func}
|
|
416
|
+
query(q, #{clsName}.extras, this.extras, #{clsName}.queryParamNames);
|
|
417
|
+
if (q.length)
|
|
418
|
+
return `?${q.join('&')}`;
|
|
419
|
+
return '';
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
static headerParamNames: Array<string> = [ #{header_params.map { |hp| "'#{hp['name']}'" }.sort!.join(', ')} ];
|
|
423
|
+
private headers(cand: #{operation_object[:args_name] || 'object'}): Headers {
|
|
424
|
+
const hdrs = new Headers();
|
|
425
|
+
#{header_func}
|
|
426
|
+
return headers(hdrs, #{clsName}.extras, this.extras, #{clsName}.headerParamNames);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
private addAbortController(force: boolean) {
|
|
430
|
+
const signalable = force || (this.abortController !== null) || ((this.abortSignalable !== undefined) ? this.abortSignalable : ((#{clsName}.abortSignalable !== undefined) ? #{clsName}.abortSignalable : shared.abortSignalable));
|
|
431
|
+
if (!signalable)
|
|
432
|
+
return;
|
|
433
|
+
if (this.abortController === null) {
|
|
434
|
+
this.abortController = new AbortController();
|
|
435
|
+
}
|
|
436
|
+
if (this.urlRequestInit!.ri.signal === undefined)
|
|
437
|
+
this.urlRequestInit!.ri.signal = this.abortController.signal;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
#{encode}
|
|
441
|
+
prepare({ ignoreMismatches = false, createAbortSignal = false }: PrepareArgs = {}): UrlRequestInit {
|
|
442
|
+
const cand = #{paramless ? '{}' : "{ ...#{operation_object[:shared]}(), ...#{clsName}.params, ...this.params! }"};
|
|
443
|
+
#{paramless ? '// cand is ok.' : "
|
|
444
|
+
if (!#{operation_object[:args_is]}(cand) && !ignoreMismatches) {
|
|
445
|
+
throw new #{clsReqError}('#{clsName} parameters do not conform to #{operation_object[:args_name]} type.');
|
|
446
|
+
}"}
|
|
447
|
+
const out: UrlRequestInit = {
|
|
448
|
+
url: this.path(cand as #{operation_object[:args_name] || 'object'}) + this.query(cand as #{operation_object[:args_name] || 'object'}),
|
|
449
|
+
ri: {
|
|
450
|
+
...#{clsName}.defaultRequestInit,
|
|
451
|
+
method: #{string_const(method)},
|
|
452
|
+
headers: this.headers(cand as #{operation_object[:args_name] || 'object'}),
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
#{has_encoder ? "this.encodeBody(cand as #{operation_object[:args_name] || 'object'}, out);" : '// No body encoding.'}
|
|
456
|
+
this.urlRequestInit = out;
|
|
457
|
+
this.addAbortController(createAbortSignal);
|
|
458
|
+
const mod = (this.modifier !== undefined) ? this.modifier : ((#{clsName}.modifier !== undefined) ? #{clsName}.modifier : shared.modifier);
|
|
459
|
+
if (mod !== null) {
|
|
460
|
+
mod(out);
|
|
461
|
+
}
|
|
462
|
+
return out;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async call(): Promise<#{clsRespName}> {
|
|
466
|
+
const ur = this.urlRequestInit || this.prepare();
|
|
467
|
+
return #{clsRespName}.obtain({ caller: this });
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// User can use prepare, fetch etc. themselves, then pass to obtain.
|
|
472
|
+
|
|
473
|
+
export type #{clsRespName}Args = {
|
|
474
|
+
caller: #{clsName};
|
|
475
|
+
response?: Response;
|
|
476
|
+
loaders?: LoaderMap; // Content-type names are lower case.
|
|
477
|
+
throwNon2xx?: boolean;
|
|
478
|
+
throwBodyMismatch?: boolean;
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
export class #{clsReqError} extends RequestError {
|
|
482
|
+
constructor(msg: string) {
|
|
483
|
+
super(msg);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export class #{clsRespError} extends ResponseError {
|
|
488
|
+
caller: #{clsName};
|
|
489
|
+
constructor(msg: string, c: #{clsName}, r: Response, b?: any) {
|
|
490
|
+
super(msg, r, b);
|
|
491
|
+
this.caller = c;
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
#{response_decls.join("\n")}
|
|
496
|
+
|
|
497
|
+
export class #{clsRespName} {
|
|
498
|
+
static args: Partial<#{clsRespName}Args> = {};
|
|
499
|
+
static loaders: LoaderMap = new Map(defaultLoaders);
|
|
500
|
+
|
|
501
|
+
static async obtain(args: #{clsRespName}Args): Promise<#{clsRespName}> {
|
|
502
|
+
if (args.response === undefined) {
|
|
503
|
+
args.response = await fetch(args.caller.urlRequestInit!.url, args.caller.urlRequestInit!.ri);
|
|
504
|
+
}
|
|
505
|
+
args = { loaders: #{clsRespName}.loaders, ...#{clsRespName}.args, ...args };
|
|
506
|
+
let raw: any;
|
|
507
|
+
if (args.response!.body) {
|
|
508
|
+
const loader = getLoader(args.response!.headers.get(#{string_const('content-type')}), args.loaders, #{clsRespName}.args.loaders, #{clsRespName}.loaders);
|
|
509
|
+
raw = await loader(args.response!);
|
|
510
|
+
}
|
|
511
|
+
if (args.throwNon2xx && (args.response!.status < 200 || 300 <= args.response!.status)) {
|
|
512
|
+
throw new #{clsRespError}(`#{clsRespName} non-2xx response status ${args.response!.status}`, args.caller, args.response!, raw);
|
|
513
|
+
}
|
|
514
|
+
return new #{clsRespName}(args.response!, args.caller, raw, args.throwBodyMismatch);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
response: Response;
|
|
518
|
+
caller: #{clsName};
|
|
519
|
+
#{status_decls.map { |sd| sd[:decl] }.join("\n")}
|
|
520
|
+
#{body_decls.to_a.sort!.join("\n")}
|
|
521
|
+
mismatch: number;
|
|
522
|
+
|
|
523
|
+
constructor(response: Response, caller: #{clsName}, rawBody?: any, throwBodyMismatch?: boolean) {
|
|
524
|
+
this.response = response;
|
|
525
|
+
this.caller = caller;
|
|
526
|
+
const contentType = (response.headers?.get(#{string_const('content-type')}) || '').toLowerCase();
|
|
527
|
+
this.mismatch = 0;
|
|
528
|
+
#{body_check}
|
|
529
|
+
if (this.mismatch == 1) {
|
|
530
|
+
throw new #{clsRespError}(`#{clsRespName} unexpected content-type: ${contentType}`, caller, response, rawBody);
|
|
531
|
+
}
|
|
532
|
+
if (this.mismatch == 2 && throwBodyMismatch) {
|
|
533
|
+
throw new #{clsRespError}('#{clsRespName} body mismatch', caller, response, rawBody);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
EOB
|
|
538
|
+
out.push(c)
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
Gen.output.join(out)
|
|
542
|
+
%>
|