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.
@@ -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
+ %>