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,1016 @@
|
|
|
1
|
+
<%=
|
|
2
|
+
Gen.output.config = Gen.x.cfg['ts_indentation']
|
|
3
|
+
Gen.x.generator_info
|
|
4
|
+
%>
|
|
5
|
+
|
|
6
|
+
import type {SchemaCheckers, String2Any} from './helpers.d.ts';
|
|
7
|
+
import {isSchema, baseSchema} from './helpers.js';
|
|
8
|
+
|
|
9
|
+
function unknown(x: unknown, name: string): Error {
|
|
10
|
+
if (isArray(x)) {
|
|
11
|
+
return new Error(`unknown of type array, length ${x.length}, is not ${name}`);
|
|
12
|
+
}
|
|
13
|
+
if (isObject(x)) {
|
|
14
|
+
const keyType: Array<string> = Object.keys(x).map((key) => `${key}: ${typeof x[key as keyof object]}`);
|
|
15
|
+
if (keyType.length == 0)
|
|
16
|
+
return new Error(`unknown of type object with no keys is not ${name}`);
|
|
17
|
+
if (keyType.length == 1)
|
|
18
|
+
return new Error(`unknown of type object { ${keyType[0] } is not ${name}`)
|
|
19
|
+
return new Error(`unknown of type object {\n ${keyType.join('\n ')}\n} is not ${name}`)
|
|
20
|
+
}
|
|
21
|
+
return new Error(`unknown of type ${typeof x} is not ${name}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// For completeness. Used by is/unknown2/base functions later.
|
|
25
|
+
|
|
26
|
+
export function isNot(x: unknown): x is any {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isString(x: unknown): x is string {
|
|
31
|
+
return typeof x == typeof '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function unknown2String(x: unknown): string {
|
|
35
|
+
if (isString(x))
|
|
36
|
+
return x as string;
|
|
37
|
+
throw unknown(x, 'string');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function baseString(s: string): string {
|
|
41
|
+
return s;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isNumber(x: unknown): x is number {
|
|
45
|
+
return typeof x == typeof 1 && !Number.isNaN(x);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function unknown2Number(x: unknown): number {
|
|
49
|
+
if (isNumber(x))
|
|
50
|
+
return x as number;
|
|
51
|
+
throw unknown(x, 'number');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function baseNumber(n: number): number {
|
|
55
|
+
return n;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isBoolean(x: unknown): x is boolean {
|
|
59
|
+
return typeof x == typeof true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function unknown2Boolean(x: unknown): boolean {
|
|
63
|
+
if (isBoolean(x))
|
|
64
|
+
return x as boolean;
|
|
65
|
+
throw unknown(x, 'boolean');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function baseBoolean(b: boolean): boolean {
|
|
69
|
+
return b;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function baseArray(a: Array<any>): Array<any> {
|
|
73
|
+
const out = [];
|
|
74
|
+
for (const x of a) {
|
|
75
|
+
if (isArray(x))
|
|
76
|
+
out.push(baseArray(x as Array<any>));
|
|
77
|
+
else if (isObject(x))
|
|
78
|
+
out.push(baseObject(x as object));
|
|
79
|
+
else
|
|
80
|
+
out.push(x); // Boolean, string, number, null, or undefined maybe.
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function isArray(x: unknown): x is Array<any> {
|
|
86
|
+
return Array.isArray(x);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function unknown2Array(x: unknown): Array<any> {
|
|
90
|
+
if (isArray(x))
|
|
91
|
+
return x as Array<any>;
|
|
92
|
+
throw unknown(x, 'array');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function isObject(x: unknown): x is object {
|
|
96
|
+
return typeof x == typeof {} && x !== null && !Array.isArray(x);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function unknown2Object(x: unknown): object {
|
|
100
|
+
if (isObject(x))
|
|
101
|
+
return x as object;
|
|
102
|
+
throw unknown(x, 'object');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function baseObject(o: object): object {
|
|
106
|
+
const keys = Object.keys(o);
|
|
107
|
+
const out: String2Any = {};
|
|
108
|
+
for (const k of keys) {
|
|
109
|
+
const x: unknown = o[k as keyof object];
|
|
110
|
+
if (isArray(x))
|
|
111
|
+
out[k] = baseArray(x as Array<any>);
|
|
112
|
+
else if (isObject(x))
|
|
113
|
+
out[k] = baseObject(x as object);
|
|
114
|
+
else
|
|
115
|
+
out[k] = x;
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function isAny(a: any): a is any {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function baseAny(a: any): any {
|
|
125
|
+
return a;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function unknown2Any(x: unknown): any {
|
|
129
|
+
return x as any;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Interfaces, types, and checker functions.
|
|
133
|
+
<%=
|
|
134
|
+
# Names first.
|
|
135
|
+
sis = Gen.x.order
|
|
136
|
+
sis.each do |si|
|
|
137
|
+
si.schema[:name] = si.name # For use later.
|
|
138
|
+
si.schema[:is] = "is#{LuckyCase.pascal_case(si.name)}"
|
|
139
|
+
si.schema[:unknown] = "unknown2#{si.name}"
|
|
140
|
+
si.schema[:base] = "base#{LuckyCase.pascal_case(si.name)}"
|
|
141
|
+
si.schema[:checkers] = "checkers#{LuckyCase.pascal_case(si.name)}"
|
|
142
|
+
si.schema[:obj] = OpenAPIGenerateTypeScriptFetch::ObjectSchema.new(si.schema) if si.schema['type'] == 'object'
|
|
143
|
+
end
|
|
144
|
+
# Output.
|
|
145
|
+
out = []
|
|
146
|
+
checker = []
|
|
147
|
+
sis = Gen.x.order
|
|
148
|
+
sis.each do |si|
|
|
149
|
+
Gen.x.schemas.functions.push(si.schema[:is])
|
|
150
|
+
Gen.x.schemas.functions.push(si.schema[:base])
|
|
151
|
+
Gen.x.schemas.functions.push(si.schema[:unknown])
|
|
152
|
+
comment = [ si.schema['summary'], si.schema['description'] ].compact!
|
|
153
|
+
unless comment.empty?
|
|
154
|
+
s = <<EOB
|
|
155
|
+
/* #{si.name}:
|
|
156
|
+
#{comment.join("\n\n")}
|
|
157
|
+
*/
|
|
158
|
+
EOB
|
|
159
|
+
out.push(s)
|
|
160
|
+
end
|
|
161
|
+
case si.schema['type']
|
|
162
|
+
when 'object'
|
|
163
|
+
Gen.x.schemas.types.push(si.name)
|
|
164
|
+
os = si.schema[:obj]
|
|
165
|
+
props = si.schema['properties'] || {}
|
|
166
|
+
# Empty object special case.
|
|
167
|
+
if os.props.empty?
|
|
168
|
+
s = <<EOB
|
|
169
|
+
export type #{si.name} = object;
|
|
170
|
+
|
|
171
|
+
export function #{si.schema[:is]}(x: unknown): x is #{si.name} {
|
|
172
|
+
return isObject(x);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function #{si.schema[:unknown]}(x: unknown): #{si.name} {
|
|
176
|
+
if (#{si.schema[:is]}(x))
|
|
177
|
+
return x as #{si.name};
|
|
178
|
+
throw unknown(x, '#{si.name}');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function #{si.schema[:base]}(x: #{si.name}): #{si.name} {
|
|
182
|
+
return x as #{si.name}; // Object without known properties, nothing to do.
|
|
183
|
+
}
|
|
184
|
+
EOB
|
|
185
|
+
out.push(s)
|
|
186
|
+
next
|
|
187
|
+
end
|
|
188
|
+
# Normal object.
|
|
189
|
+
# Call class constructor type.
|
|
190
|
+
# Named properties, required and optional.
|
|
191
|
+
reqd = si.schema['required'] || []
|
|
192
|
+
o = [ true ]
|
|
193
|
+
preq = []
|
|
194
|
+
popt = []
|
|
195
|
+
kreq = []
|
|
196
|
+
os.props.select { |p| !p.pattern && !p.additional }.each do |p|
|
|
197
|
+
ps = Gen.h.dereference(p.spec)
|
|
198
|
+
# oneOf support requires that preq/popt item can be an array of hashes.
|
|
199
|
+
warn("Property '#{p.name}' type '#{p.type}' has nil schema, fully proicessed source?") if ps.nil?
|
|
200
|
+
o.push("#{p.name}#{p.req ? '' : '?'}: #{p.type};")
|
|
201
|
+
(p.req ? preq : popt).push({
|
|
202
|
+
name: p.name,
|
|
203
|
+
is: ps[:is],
|
|
204
|
+
base: ps[:base]
|
|
205
|
+
})
|
|
206
|
+
kreq.push(p.name) if p.req
|
|
207
|
+
end
|
|
208
|
+
# Pattern properties.
|
|
209
|
+
ppat = []
|
|
210
|
+
cres = []
|
|
211
|
+
os.props.select { |p| p.pattern }.each do |p|
|
|
212
|
+
ps = Gen.h.dereference(p.spec)
|
|
213
|
+
o.push("// Pattern: #{p.name}: #{p.type}")
|
|
214
|
+
cre_name = "re#{si.name}_#{cres.size}"
|
|
215
|
+
ppat.push({
|
|
216
|
+
is: ps[:is],
|
|
217
|
+
base: ps[:base],
|
|
218
|
+
cre: cre_name
|
|
219
|
+
})
|
|
220
|
+
cres.push("const #{cre_name} = new RegExp(atob('#{Base64.strict_encode64(p.name)}')); // #{p.name}")
|
|
221
|
+
end
|
|
222
|
+
if os.additional
|
|
223
|
+
add_props = os.props.select(&:additional).first
|
|
224
|
+
if add_props.is_a?(Hash)
|
|
225
|
+
ps = Gen.h.dereference(add_props)
|
|
226
|
+
o.push("// Additional properties of type: #{p.type}")
|
|
227
|
+
ap = "additional: { is: #{ps[:is]}, base: #{ps[:base]} },"
|
|
228
|
+
else
|
|
229
|
+
ap = "additional: { is: isAny, base: baseAny },"
|
|
230
|
+
end
|
|
231
|
+
else
|
|
232
|
+
o.push('// Additional properties are not allowed.')
|
|
233
|
+
ap = "additional: { is: isNot },"
|
|
234
|
+
end
|
|
235
|
+
s = <<EOB
|
|
236
|
+
export type #{si.name} = {
|
|
237
|
+
#{Gen.output.join(o)}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#{cres.join("\n")}
|
|
241
|
+
const #{si.schema[:checkers]}: SchemaCheckers = {
|
|
242
|
+
requiredKeys: [#{kreq.map { |key| "'#{key}'" }.join(', ')}],
|
|
243
|
+
required: {
|
|
244
|
+
#{preq.map { |p| "'#{p[:name]}': { is: #{p[:is]}, base: #{p[:base]} }" }.join(",\n")}
|
|
245
|
+
},
|
|
246
|
+
optional: {
|
|
247
|
+
#{popt.map { |p| "'#{p[:name]}': { is: #{p[:is]}, base: #{p[:base]} }" }.join(",\n")}
|
|
248
|
+
},
|
|
249
|
+
#{ap}
|
|
250
|
+
patterns: [
|
|
251
|
+
#{ppat.map { |p| "{ re: #{p[:cre]}, funcs: { is: #{p[:is]}, base: #{p[:base]} }}" }.join(",\n") }
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function #{si.schema[:is]}(x: unknown): x is #{si.name} {
|
|
256
|
+
if (!isObject(x))
|
|
257
|
+
return false;
|
|
258
|
+
return isSchema(x as object, #{si.schema[:checkers]});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function #{si.schema[:unknown]}(x: unknown): #{si.name} {
|
|
262
|
+
if (#{si.schema[:is]}(x))
|
|
263
|
+
return x as #{si.name};
|
|
264
|
+
throw unknown(x, '#{si.name}');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function #{si.schema[:base]}(x: #{si.name}): #{si.name} {
|
|
268
|
+
return baseSchema(x, #{si.schema[:checkers]}) as #{si.name};
|
|
269
|
+
}
|
|
270
|
+
EOB
|
|
271
|
+
out.push(s)
|
|
272
|
+
when 'array'
|
|
273
|
+
Gen.x.schemas.types.push(si.name)
|
|
274
|
+
min_items = si.schema.fetch('minItems', 0)
|
|
275
|
+
max_items = si.schema['maxItems']
|
|
276
|
+
min_contains = si.schema.fetch('minContains', 0)
|
|
277
|
+
max_contains = si.schema['maxContains']
|
|
278
|
+
# Opportunity to warn.
|
|
279
|
+
max_contains = nil if !max_items.nil? && !max_contains.nil? && max_items < max_contains
|
|
280
|
+
# Uniqueness might be better implemented using custom code.
|
|
281
|
+
# Checking here could use lodash or underscore.
|
|
282
|
+
uniq = si.schema.fetch('uniqueItems', false)
|
|
283
|
+
# This requires that the array has optional types.
|
|
284
|
+
# items could be false, so prefixItems handles all?
|
|
285
|
+
# contains can add another type.
|
|
286
|
+
# Prefix sounds like they'd be better as actual named fields.
|
|
287
|
+
prefix = si.schema['prefixItems']
|
|
288
|
+
unless prefix.nil?
|
|
289
|
+
# Opportunity to warn.
|
|
290
|
+
prefix = prefix[0...max_items] if !max_items.nil? && max_items < prefix.size
|
|
291
|
+
prefix = prefix.map { |p| Gen.h.dereference(p) }
|
|
292
|
+
end
|
|
293
|
+
contains = si.schema['contains']
|
|
294
|
+
contains = Gen.h.dereference(contains) unless contains.nil?
|
|
295
|
+
items = si.schema['items']
|
|
296
|
+
items = Gen.h.dereference(items) unless items.nil?
|
|
297
|
+
# Opportunity to warn.
|
|
298
|
+
items = nil if !prefix.nil? && !max_items.nil? && prefix.size == max_items
|
|
299
|
+
alts = []
|
|
300
|
+
bases = []
|
|
301
|
+
if prefix.nil?
|
|
302
|
+
needs_loop = true
|
|
303
|
+
is_prefix = [ '// No prefixItems.' ]
|
|
304
|
+
base_prefix = [ '// No prefixItems.' ]
|
|
305
|
+
else
|
|
306
|
+
needs_loop = !max_items.nil? && prefix.size < max_items
|
|
307
|
+
prefix.each do |p|
|
|
308
|
+
alts.push(p[:name])
|
|
309
|
+
bases.push(p)
|
|
310
|
+
end
|
|
311
|
+
is_prefix = []
|
|
312
|
+
base_prefix = []
|
|
313
|
+
# Prefix schema may be close enough for contains. One derived from another.
|
|
314
|
+
prefix.size.times do |idx|
|
|
315
|
+
p = prefix[idx]
|
|
316
|
+
cont = '// No contains check.'
|
|
317
|
+
unless contains.nil?
|
|
318
|
+
if contains[:is] == p[:is]
|
|
319
|
+
cont = 'else ++contains;'
|
|
320
|
+
elsif contains['type'] == p['type']
|
|
321
|
+
cont = "if (#{contains[:is]}(a[#{idx})]) ++contains;"
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
s = <<EOB
|
|
325
|
+
if (a.length > #{idx}) {
|
|
326
|
+
if (!#{p[:is]}(a[#{idx}])) return false;
|
|
327
|
+
#{cont}
|
|
328
|
+
}
|
|
329
|
+
EOB
|
|
330
|
+
is_prefix.push(s)
|
|
331
|
+
base_prefix.push("if (a.length > #{idx}) out.push(#{p[:base]}(a[#{idx}]));")
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
# Contains and items may be the same or derive from same.
|
|
335
|
+
if items.nil?
|
|
336
|
+
alts.push('any') if max_items.nil? || prefix.size < max_items
|
|
337
|
+
is_item = ''
|
|
338
|
+
item_ch = '// No items check, anything allowed.'
|
|
339
|
+
base_item = 'out.push(x[k]);'
|
|
340
|
+
else
|
|
341
|
+
alts.push(items[:name])
|
|
342
|
+
is_item = items[:is]
|
|
343
|
+
item_ch = "if (!#{is_item}(a[k])) return false;"
|
|
344
|
+
base_item = "out.push(#{items[:base]}(x[k]));"
|
|
345
|
+
end
|
|
346
|
+
c_incr = '// No contains check.'
|
|
347
|
+
if contains.nil?
|
|
348
|
+
cont = '// No contains counter check.'
|
|
349
|
+
base_cont = '// No contains schema.'
|
|
350
|
+
else
|
|
351
|
+
t_min_ch = min_contains.positive? ? "contains < #{min_contains}" : nil
|
|
352
|
+
t_max_ch = max_contains.nil? ? nil : "#{max_contains} < contains"
|
|
353
|
+
if !t_min_ch.nil? || !t_max_ch.nil?
|
|
354
|
+
c_incr = "#{is_item != contains[:is] ? "if (#{contains[:is]}(a[k])) " : ''}++contains;"
|
|
355
|
+
end
|
|
356
|
+
cont = [ t_min_ch, t_max_ch ].compact.join(' || ') || '// No contains counter check.'
|
|
357
|
+
needs_loop = prefix.nil? || max_items.nil? || prefix.size < max_items || !items.nil?
|
|
358
|
+
base_cont = <<EOB
|
|
359
|
+
if (#{contains[:is]}) {
|
|
360
|
+
out.push(#{contains[:base]}(a[k]));
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
EOB
|
|
364
|
+
end
|
|
365
|
+
s = <<EOB
|
|
366
|
+
export type #{si.name} = Array<#{alts.uniq.sort!.join('|')}>;
|
|
367
|
+
|
|
368
|
+
export function #{si.schema[:is]}(x: unknown): x is #{si.name} {
|
|
369
|
+
if (!Array.isArray(x))
|
|
370
|
+
return false;
|
|
371
|
+
#{min_items.positive? ? "if (x.length < #{min_items}) return false;" : '// No min item limit.'}
|
|
372
|
+
#{max_items.nil? ? '// No max item limit.' : "if (#{max_items} < x.length) return false;"}
|
|
373
|
+
const a: Array<any> = x as Array<any>;
|
|
374
|
+
#{contains.nil? ? '// No contains counter.' : 'let contains = 0;'}
|
|
375
|
+
#{is_prefix.join("\n")}
|
|
376
|
+
#{needs_loop ? '' : '/* '}for (let k = #{prefix.nil? ? 0 : prefix.size}; k < a.length; ++k) {
|
|
377
|
+
#{item_ch}
|
|
378
|
+
#{c_incr}
|
|
379
|
+
#{uniq ? '// Uniqueness check not implemented.' : '// Uniqueness not required.'}
|
|
380
|
+
}#{needs_loop ? '' : ' */'}
|
|
381
|
+
#{cont}
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function #{si.schema[:unknown]}(x: unknown): #{si.name} {
|
|
386
|
+
if (#{si.schema[:is]}(x))
|
|
387
|
+
return x as #{si.name};
|
|
388
|
+
throw unknown(x, '#{si.name}');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function #{si.schema[:base]}(x: #{si.name}): #{si.name} {
|
|
392
|
+
const out: #{si.name} = [];
|
|
393
|
+
#{base_prefix.join("\n")}
|
|
394
|
+
#{needs_loop ? '' : '/* '}for (let k = #{prefix.nil? ? 0 : prefix.size}; k < x.length; ++k) {
|
|
395
|
+
#{base_cont}
|
|
396
|
+
#{base_item}
|
|
397
|
+
}#{needs_loop ? '' : ' */'}
|
|
398
|
+
return out;
|
|
399
|
+
}
|
|
400
|
+
EOB
|
|
401
|
+
out.push(s)
|
|
402
|
+
when 'string'
|
|
403
|
+
Gen.x.schemas.types.push(si.name)
|
|
404
|
+
checks = {
|
|
405
|
+
'maxLength' => "if (value.length > #{si.schema['maxLength']}) throw new Error(`#{si.name} length ${value.length} > #{si.schema['maxLength']}`);",
|
|
406
|
+
'minLength' => "if (value.length < #{si.schema['minLength']}) throw new Error(`#{si.name} length ${value.length} < #{si.schema['minLength']}`);",
|
|
407
|
+
'pattern' => "if (!re#{si.name}.test(value)) throw new Error(`#{si.name} value '${value}' does not match pattern ${re#{si.name}.source}`);"
|
|
408
|
+
}
|
|
409
|
+
re = si.schema.key?('pattern') ? "const re#{si.name} = new RegExp(atob('#{Base64.strict_encode64(si.schema['pattern'])}')); // #{si.schema['pattern']}" : nil
|
|
410
|
+
present = checks.keys.map { |k| si.schema.key?(k) ? checks[k] : nil }.compact
|
|
411
|
+
s = <<EOB
|
|
412
|
+
export type #{si.name} = string;
|
|
413
|
+
|
|
414
|
+
#{re || '// No content pattern.'}
|
|
415
|
+
export function check#{LuckyCase.pascal_case(si.name)}(value: string): #{si.name} {
|
|
416
|
+
#{present.join("\n ")}
|
|
417
|
+
return value as #{si.name};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export function #{si.schema[:is]}(x: unknown): x is #{si.name} {
|
|
421
|
+
#{present.empty? ? "return isString(x);" : "if (!isString(x))
|
|
422
|
+
return false;
|
|
423
|
+
try {
|
|
424
|
+
check#{LuckyCase.pascal_case(si.name)}(x as string);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
return true;"}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function #{si.schema[:unknown]}(x: unknown): #{si.name} {
|
|
433
|
+
if (#{si.schema[:is]}(x))
|
|
434
|
+
return x as #{si.name};
|
|
435
|
+
throw unknown(x, '#{si.name}');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function #{si.schema[:base]}(x: #{si.name}): #{si.name} {
|
|
439
|
+
return x; // String, nothing to do.
|
|
440
|
+
}
|
|
441
|
+
EOB
|
|
442
|
+
out.push(s)
|
|
443
|
+
when 'integer', 'number'
|
|
444
|
+
Gen.x.schemas.types.push(si.name)
|
|
445
|
+
si.schema[:native] = si.schema['type'] == 'integer' ? :integer : :float
|
|
446
|
+
checks = {
|
|
447
|
+
'exclusiveMinimum' => "if (value <= #{si.schema['exclusiveMinimum']}) throw new Error(`#{si.name} value ${value} <= #{si.schema['exclusiveMinimum']}`);",
|
|
448
|
+
'exclusiveMaximum' => "if (value >= #{si.schema['exclusiveMaximum']}) throw new Error(`#{si.name} value ${value} >= #{si.schema['exclusiveMaximum']}`);",
|
|
449
|
+
'minimum' => "if (value < #{si.schema['minimum']}) throw new Error(`#{si.name} value ${value} < #{si.schema['minimum']}`);",
|
|
450
|
+
'maximum' => "if (value > #{si.schema['maximum']}) throw new Error(`#{si.name} value ${value} > #{si.schema['maximum']}`);",
|
|
451
|
+
'multipleOf' => "if (value % #{si.schema['multipleOf']} !== 0) throw new Error(`#{si.name} value ${value} not a multiple of #{si.schema['multipleOf']}`);"
|
|
452
|
+
}
|
|
453
|
+
present = checks.keys.map { |k| si.schema.key?(k) ? checks[k] : nil }.compact
|
|
454
|
+
present.push("if (!Number.isSafeInteger(value)) throw new Error(`#{si.name} value ${value} is not a safe integer`);") if si.schema[:native] == :integer
|
|
455
|
+
s = <<EOB
|
|
456
|
+
export type #{si.name} = number;
|
|
457
|
+
|
|
458
|
+
export function check#{LuckyCase.pascal_case(si.name)}(value: number): #{si.name} {
|
|
459
|
+
#{present.join("\n ")}
|
|
460
|
+
return value as #{si.name};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export function #{si.schema[:is]}(x: unknown): x is #{si.name} {
|
|
464
|
+
if (!isNumber(x))
|
|
465
|
+
return false;
|
|
466
|
+
try {
|
|
467
|
+
check#{LuckyCase.pascal_case(si.name)}(x as number);
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function #{si.schema[:unknown]}(x: unknown): #{si.name} {
|
|
476
|
+
if (#{si.schema[:is]}(x))
|
|
477
|
+
return x as #{si.name};
|
|
478
|
+
throw unknown(x, '#{si.name}');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function #{si.schema[:base]}(x: #{si.name}): #{si.name} {
|
|
482
|
+
return x; // Number, nothing to do.
|
|
483
|
+
}
|
|
484
|
+
EOB
|
|
485
|
+
out.push(s)
|
|
486
|
+
when 'boolean'
|
|
487
|
+
Gen.x.schemas.types.push(si.name)
|
|
488
|
+
s = <<EOB
|
|
489
|
+
export type #{si.name} = boolean;
|
|
490
|
+
|
|
491
|
+
export function #{si.schema[:is]}(x: unknown): x is #{si.name} {
|
|
492
|
+
return isBoolean(x);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export function #{si.schema[:unknown]}(x: unknown): #{si.name} {
|
|
496
|
+
if (#{si.schema[:is]}(x))
|
|
497
|
+
return x as #{si.name};
|
|
498
|
+
throw unknown(x, '#{si.name}');
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export function #{si.schema[:base]}(x: #{si.name}): #{si.name} {
|
|
502
|
+
return x; // Boolean, nothing to do.
|
|
503
|
+
}
|
|
504
|
+
EOB
|
|
505
|
+
out.push(s)
|
|
506
|
+
else
|
|
507
|
+
raise StandardError, "Unhandled type: '#{si.schema['type']}' in #{si.name}"
|
|
508
|
+
end
|
|
509
|
+
out.push('')
|
|
510
|
+
end
|
|
511
|
+
Gen.output.join(out)
|
|
512
|
+
%>
|
|
513
|
+
|
|
514
|
+
// Security requirement types.
|
|
515
|
+
<%=
|
|
516
|
+
out = []
|
|
517
|
+
secs = Gen.doc.dig('components', 'securitySchemes')
|
|
518
|
+
if secs.nil?
|
|
519
|
+
out.push('// No security schemes.')
|
|
520
|
+
else
|
|
521
|
+
has_apikey = false
|
|
522
|
+
secs.each do |sname, scheme|
|
|
523
|
+
desc = scheme['description']
|
|
524
|
+
unless desc.nil?
|
|
525
|
+
desc.strip!
|
|
526
|
+
if desc.lines.size < 2
|
|
527
|
+
out.push("// #{desc}")
|
|
528
|
+
else
|
|
529
|
+
out.push("/*\n#{desc}\n*/")
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
# The configuration should allow a drop-in type as multi-line text.
|
|
533
|
+
case scheme['type'].downcase
|
|
534
|
+
when 'apikey'
|
|
535
|
+
next if has_apikey
|
|
536
|
+
has_apikey = true
|
|
537
|
+
scheme[:name] = "SecurityApiKey"
|
|
538
|
+
scheme[:is] = 'isString'
|
|
539
|
+
scheme[:base] = 'baseString'
|
|
540
|
+
scheme[:unknown] = 'unknown2String'
|
|
541
|
+
Gen.x.schemas.types.push(scheme[:name])
|
|
542
|
+
out.push("export type #{scheme[:name]} = string;")
|
|
543
|
+
when 'http'
|
|
544
|
+
scheme[:name] = "SecurityHttp#{LuckyCase.pascal_case(sname)}"
|
|
545
|
+
scheme[:is] = 'isObject'
|
|
546
|
+
scheme[:base] = 'baseObject'
|
|
547
|
+
scheme[:unknown] = 'unknown2Object'
|
|
548
|
+
Gen.x.schemas.types.push(scheme[:name])
|
|
549
|
+
out.push("export type #{scheme[:name]} = Object;")
|
|
550
|
+
when 'oauth2'
|
|
551
|
+
# Produce a constant object that contains all data as is.
|
|
552
|
+
scheme[:name] = "SecurityOAuth2#{LuckyCase.pascal_case(sname)}"
|
|
553
|
+
scheme[:is] = 'isObject'
|
|
554
|
+
scheme[:base] = 'baseObject'
|
|
555
|
+
scheme[:unknown] = 'unknown2Object'
|
|
556
|
+
Gen.x.schemas.consts.push(scheme[:name])
|
|
557
|
+
out.push("export const #{scheme[:name]} = #{JSON.pretty_generate(scheme['flows'])}")
|
|
558
|
+
when 'openidconnect'
|
|
559
|
+
scheme[:name] = "SecurityOpenIdConnect#{LuckyCase.pascal_case(sname)}"
|
|
560
|
+
scheme[:is] = 'isString'
|
|
561
|
+
scheme[:base] = 'baseString'
|
|
562
|
+
scheme[:unknown] = 'unknown2String'
|
|
563
|
+
Gen.x.schemas.consts.push(scheme[:name])
|
|
564
|
+
out.push("export const #{scheme[:name]} = #{JSON.pretty_generate(scheme['openIdConnectUrl'])};")
|
|
565
|
+
else
|
|
566
|
+
msg = "Unhandled security scheme type: #{scheme['type']}"
|
|
567
|
+
out.push("// #{msg}")
|
|
568
|
+
warn(msg)
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
Gen.output.join(out)
|
|
573
|
+
%>
|
|
574
|
+
|
|
575
|
+
// Types for making requests.
|
|
576
|
+
|
|
577
|
+
export type String2String = Record<string, string>;
|
|
578
|
+
|
|
579
|
+
// Type for passing extra parameters in addition to operation parameters.
|
|
580
|
+
export type CallExtras = {
|
|
581
|
+
headers?: String2String;
|
|
582
|
+
query?: String2String;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const utf8encoder = new TextEncoder();
|
|
586
|
+
const applicationJsonUtf8 = 'application/json; charset=utf-8';
|
|
587
|
+
|
|
588
|
+
export type EncodedBodyType = string|ArrayBuffer|Uint8Array<ArrayBuffer>|Blob|DataView|File|FormData|URLSearchParams|ReadableStream;
|
|
589
|
+
// Returns body, Content-Type, Content-Length, and additional headers, if any.
|
|
590
|
+
export type EncoderFunction = (body: any) => [body: EncodedBodyType, contentType: string, contentLength: number, headers: String2String];
|
|
591
|
+
|
|
592
|
+
export function jsonEncode(body: any): [EncodedBodyType, string, number, String2String] {
|
|
593
|
+
const b = utf8encoder.encode(JSON.stringify(body));
|
|
594
|
+
return [b, applicationJsonUtf8, b.length, {}];
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export function isEncodedBodyType(x: unknown): x is EncodedBodyType {
|
|
598
|
+
if (ArrayBuffer.isView(x)) return true; // DataView, TypedArray.
|
|
599
|
+
if (isString(x)) return true;
|
|
600
|
+
if (x instanceof ArrayBuffer) return true;
|
|
601
|
+
if (x instanceof Blob) return true; // Blob, File
|
|
602
|
+
if (x instanceof FormData) return true;
|
|
603
|
+
if (x instanceof URLSearchParams) return true;
|
|
604
|
+
if (x instanceof ReadableStream) return true;
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
export function baseEncodedBodyType(x: unknown): EncodedBodyType {
|
|
609
|
+
return x as EncodedBodyType;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Operation parameter types.
|
|
613
|
+
|
|
614
|
+
<%=
|
|
615
|
+
encoderTypes = Set.new
|
|
616
|
+
json_encoder_names = Set.new
|
|
617
|
+
json_encoders = {}
|
|
618
|
+
out = []
|
|
619
|
+
schemes = Gen.doc.dig('components', 'securitySchemes')
|
|
620
|
+
Gen.doc['paths'].each do |path, path_item_object|
|
|
621
|
+
oos = OpenAPISourceTools::ApiObjects.operation_objects(path_item_object)
|
|
622
|
+
next if oos.empty?
|
|
623
|
+
oos.each do |method, operation_object|
|
|
624
|
+
operation_object[:args_name] = "#{LuckyCase.pascal_case(operation_object['operationId'])}Args"
|
|
625
|
+
Gen.x.schemas.types.push(operation_object[:args_name])
|
|
626
|
+
operation_object[:args_is] = "is#{operation_object[:args_name]}"
|
|
627
|
+
operation_object[:args_base] = "base#{operation_object[:args_name]}"
|
|
628
|
+
Gen.x.schemas.functions.push(operation_object[:args_is])
|
|
629
|
+
Gen.x.schemas.functions.push(operation_object[:args_base])
|
|
630
|
+
paraschemas = (operation_object['parameters'] || []).map do |pref|
|
|
631
|
+
p = Gen.h.dereference(pref)
|
|
632
|
+
p[:name] = LuckyCase.camel_case(p['name'])
|
|
633
|
+
s = Gen.h.dereference(p['schema'])
|
|
634
|
+
{
|
|
635
|
+
param: p,
|
|
636
|
+
schema: s
|
|
637
|
+
}
|
|
638
|
+
end
|
|
639
|
+
operation_object[:paraschemas] = paraschemas
|
|
640
|
+
# Get all common fields and then create all body-type specific variations
|
|
641
|
+
# using it to ensure the fields remain the same.
|
|
642
|
+
common = [ '// Parameters:' ]
|
|
643
|
+
is_func = []
|
|
644
|
+
base_func = []
|
|
645
|
+
base_opt_func = []
|
|
646
|
+
paraschemas.each do |ps|
|
|
647
|
+
p = ps[:param]
|
|
648
|
+
common.push("#{p[:name]}#{p['required'] ? '' : '?'}: #{ps[:schema][:name]};")
|
|
649
|
+
if p['required']
|
|
650
|
+
is_func.push("if (!#{ps[:schema][:is]}(y.#{p[:name]})) return false;")
|
|
651
|
+
base_func.push("#{p[:name]}: #{ps[:schema][:base]}(y.#{p[:name]})")
|
|
652
|
+
else
|
|
653
|
+
is_func.push("if (y.#{p[:name]} !== undefined && !#{ps[:schema][:is]}(y.#{p[:name]})) return false;")
|
|
654
|
+
base_opt_func.push("if (y.#{p[:name]} !== undefined) out.#{p[:name]} = #{ps[:schema][:base]}(y.#{p[:name]});")
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
common.pop unless common.last.end_with?(';') # Remove comment if no parameters.
|
|
658
|
+
# No security-related parameters, set like server URLs.
|
|
659
|
+
# Remove code below once serves no example value.
|
|
660
|
+
if false
|
|
661
|
+
common.push('// Security-related:')
|
|
662
|
+
secs = operation_object['security']
|
|
663
|
+
optional = secs.size > 1 || !(secs.find { |s| s.empty? }).nil?
|
|
664
|
+
secs.push({}) if secs.empty?
|
|
665
|
+
used = Set.new
|
|
666
|
+
operation_object[:security_params] = []
|
|
667
|
+
secs.each do |s|
|
|
668
|
+
next if s.empty?
|
|
669
|
+
s.keys.sort!.each do |sname|
|
|
670
|
+
next if used.include?(sname)
|
|
671
|
+
used.add(sname)
|
|
672
|
+
scheme = schemes[sname]
|
|
673
|
+
# API key adds a query or header parameter. Cookie is beyond the scope.
|
|
674
|
+
# http adds authentication that is placed to Authentication header.
|
|
675
|
+
# mutualTLS is beyond the scope.
|
|
676
|
+
# oauth2 specifies flow type, beyond the scope. Check.
|
|
677
|
+
# openIdConnect specifies URL and is beyond the scope.
|
|
678
|
+
common.push("#{sname}#{optional ? '?' : ''}: #{scheme[:name]};")
|
|
679
|
+
if optional
|
|
680
|
+
is_func.push("if (y.#{sname} !== undefined && !#{scheme[:is]}(y.#{sname})) return false;")
|
|
681
|
+
base_opt_func.push("if (y.#{sname} !== undefined) out.#{sname} = #{scheme[:base]}(y.#{sname});")
|
|
682
|
+
else
|
|
683
|
+
is_func.push("if (!#{scheme[:is]}(y.#{sname})) return false;")
|
|
684
|
+
base_func.push("#{sname}: #{scheme[:base]}(y.#{sname})")
|
|
685
|
+
end
|
|
686
|
+
operation_object[:security_params].push({
|
|
687
|
+
name: sname,
|
|
688
|
+
scheme: scheme,
|
|
689
|
+
optional: optional
|
|
690
|
+
})
|
|
691
|
+
end
|
|
692
|
+
common.pop unless common.last.end_with?(';') # Remove comment if no security members.
|
|
693
|
+
end
|
|
694
|
+
end
|
|
695
|
+
req_body = operation_object['requestBody']
|
|
696
|
+
if !req_body && common.empty? # Takes nothing whatsoever.
|
|
697
|
+
s = <<EOB
|
|
698
|
+
// No parameters for #{operation_object['operationId']}.
|
|
699
|
+
export type #{operation_object[:args_name]} = object;
|
|
700
|
+
|
|
701
|
+
export function #{operation_object[:args_is]}(x: unknown): x is #{operation_object[:args_name]} {
|
|
702
|
+
return isObject(x);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
export function #{operation_object[:args_base]}(x: #{operation_object[:args_name]}): #{operation_object[:args_name]} {
|
|
706
|
+
return baseObject(x);
|
|
707
|
+
}
|
|
708
|
+
EOB
|
|
709
|
+
out.push(s)
|
|
710
|
+
operation_object[:parameterless] = true
|
|
711
|
+
next
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
# Proper pairing check after encoding can be done since we know the input type. Check
|
|
715
|
+
# string against what was in the API spec.
|
|
716
|
+
# Order of checking is specific match to body, instance generic, class specific match,
|
|
717
|
+
# class generic.
|
|
718
|
+
|
|
719
|
+
# Pass also schema name to encoder just to avoid run-time checks provided you trust
|
|
720
|
+
# the programmer? Maybe other uses? Over-designing?
|
|
721
|
+
|
|
722
|
+
body_types = {}
|
|
723
|
+
body_encodings = {}
|
|
724
|
+
|
|
725
|
+
comments = []
|
|
726
|
+
req = false
|
|
727
|
+
if req_body
|
|
728
|
+
req_body[:body_types] = body_types
|
|
729
|
+
req_body[:body_encodings] = body_encodings
|
|
730
|
+
req = req_body.fetch('required', false)
|
|
731
|
+
(req_body['content'] || {}).each do |media_type, mto|
|
|
732
|
+
mto[:source] = Gen.h.dereference(mto['schema']) || {
|
|
733
|
+
name: 'any',
|
|
734
|
+
is: 'isAny',
|
|
735
|
+
base: 'baseAny',
|
|
736
|
+
unknown: 'unknown2Any'
|
|
737
|
+
}
|
|
738
|
+
body_types[mto[:source][:name]] = mto[:source]
|
|
739
|
+
mto[:source][:body_name] = "body#{LuckyCase.pascal_case(mto[:source][:name])}"
|
|
740
|
+
mt = media_type.downcase
|
|
741
|
+
encs = body_encodings[mto[:source][:body_name]] || Set.new
|
|
742
|
+
encs.add(mt)
|
|
743
|
+
body_encodings[mto[:source][:body_name]] = encs
|
|
744
|
+
comments.push("// content type #{mt}, source type: #{mto[:source][:name]}.")
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
operation_object[:body_req] = req
|
|
748
|
+
body_props = nil
|
|
749
|
+
has_name = "hasBody#{operation_object[:args_name]}"
|
|
750
|
+
has_body = nil
|
|
751
|
+
base_name = "baseBody#{operation_object[:args_name]}"
|
|
752
|
+
base_body = nil
|
|
753
|
+
base_snip = nil
|
|
754
|
+
if body_types.key?('any')
|
|
755
|
+
bodies = body_types.keys.reject { |k| k == 'any' }.sort!
|
|
756
|
+
body_props = <<EOB
|
|
757
|
+
#{bodies.map { |name| "body#{name}?: #{name};" }.join("\n")}
|
|
758
|
+
body?: any|undefined;
|
|
759
|
+
encoder?: EncoderFunction;
|
|
760
|
+
encoded?: EncodedBodyType;
|
|
761
|
+
EOB
|
|
762
|
+
has_body = <<EOB
|
|
763
|
+
function #{has_name}(x: unknown): boolean {
|
|
764
|
+
const y = x as #{operation_object[:args_name]};
|
|
765
|
+
const hasEnc: boolean = y.encoded !== undefined;
|
|
766
|
+
const encOk: boolean = hasEnc && isEncodedBodyType(y.encoded);
|
|
767
|
+
#{bodies.size.times.map do |idx|
|
|
768
|
+
name = bodies[idx]
|
|
769
|
+
s = <<EOC
|
|
770
|
+
if (y.body#{name} !== undefined) {
|
|
771
|
+
if (!#{body_types[name][:is]}(y.body#{name})) return false;
|
|
772
|
+
#{((idx + 1)...bodies.size).each { |k| "if (y.body#{bodies[k]} !== undefined) throw new Error('#{operation_object[:args_name]} has two body members set: body#{name}, body#{bodies[k]}')" }.join("\n")}
|
|
773
|
+
if (y.body !== undefined && !Object.is(y.body, y.body#{name})) {
|
|
774
|
+
throw new Error('#{operation_object[:args_name]} body and body#{name} are set to different objects.')
|
|
775
|
+
}
|
|
776
|
+
return hasEnc ? encOk : true;
|
|
777
|
+
}
|
|
778
|
+
EOC
|
|
779
|
+
s
|
|
780
|
+
end.join("\n")}
|
|
781
|
+
if (y.body !== undefined) return hasEnc ? encOk : true;
|
|
782
|
+
return #{req ? 'false' : 'true'};
|
|
783
|
+
}
|
|
784
|
+
EOB
|
|
785
|
+
body_type = 'any'
|
|
786
|
+
base_body = <<EOB
|
|
787
|
+
export function #{base_name}(x: unknown): #{body_type} {
|
|
788
|
+
return x as #{body_type};
|
|
789
|
+
}
|
|
790
|
+
EOB
|
|
791
|
+
base_snip = <<EOB
|
|
792
|
+
if (y.body !== undefined) out.body = #{base_name}(y.body);
|
|
793
|
+
if (y.encoded !== undefined) out.encoded = baseEncodedBodyType(y.encoded);
|
|
794
|
+
EOB
|
|
795
|
+
elsif !body_types.empty?
|
|
796
|
+
bodies = body_types.keys.sort!
|
|
797
|
+
body_type = body_types.keys.sort!.join('|')
|
|
798
|
+
body_props = <<EOB
|
|
799
|
+
#{bodies.map { |name| "body#{name}?: #{name};" }.join("\n")}
|
|
800
|
+
body?: #{body_type}|undefined;
|
|
801
|
+
encoder?: EncoderFunction;
|
|
802
|
+
encoded?: EncodedBodyType;
|
|
803
|
+
EOB
|
|
804
|
+
has_body = <<EOB
|
|
805
|
+
function #{has_name}(x: unknown): boolean {
|
|
806
|
+
const y = x as #{operation_object[:args_name]};
|
|
807
|
+
if (y.encoded !== undefined)
|
|
808
|
+
return isEncodedBodyType(y.encoded);
|
|
809
|
+
#{bodies.size.times.map do |idx|
|
|
810
|
+
name = bodies[idx]
|
|
811
|
+
s = <<EOC
|
|
812
|
+
if (y.body#{name} !== undefined) {
|
|
813
|
+
if (!#{body_types[name][:is]}(y.body#{name})) return false;
|
|
814
|
+
#{((idx + 1)...bodies.size).to_a.map { |k| "if (y.body#{bodies[k]} !== undefined) throw new Error('#{operation_object[:args_name]} has two body members set: body#{name}, body#{bodies[k]}')" }.join("\n")}
|
|
815
|
+
if (y.body !== undefined && !Object.is(y.body, y.body#{name})) {
|
|
816
|
+
throw new Error('#{operation_object[:args_name]} body and body#{name} are set to different objects.')
|
|
817
|
+
}
|
|
818
|
+
return true;
|
|
819
|
+
}
|
|
820
|
+
EOC
|
|
821
|
+
s
|
|
822
|
+
end.join("\n")}
|
|
823
|
+
if (y.body !== undefined) {
|
|
824
|
+
#{bodies.map { |name| "if (#{body_types[name][:is]}(y.body)) return true;" }.join("\n") }
|
|
825
|
+
}
|
|
826
|
+
return #{req ? 'false' : 'true'};
|
|
827
|
+
}
|
|
828
|
+
EOB
|
|
829
|
+
base_body = <<EOB
|
|
830
|
+
export function #{base_name}(x: unknown): #{body_type}|undefined {
|
|
831
|
+
#{body_types.keys.sort!.map { |name| "if (#{body_types[name][:is]}(x)) return #{body_types[name][:base]}(x);" }.join("\n")}
|
|
832
|
+
}
|
|
833
|
+
EOB
|
|
834
|
+
base_snip = <<EOB
|
|
835
|
+
if (y.encoded !== undefined) out.encoded = baseEncodedBodyType(y.encoded); else
|
|
836
|
+
#{bodies.map { |name| <<EOB
|
|
837
|
+
if (y.body#{name} !== undefined) {
|
|
838
|
+
out.body#{name} = #{body_types[name][:base]}(y.body#{name});
|
|
839
|
+
if (y.body !== undefined && Object.is(y.body#{name}, y.body))
|
|
840
|
+
out.body = out.body#{name};
|
|
841
|
+
} else
|
|
842
|
+
EOB
|
|
843
|
+
}.join("\n")}
|
|
844
|
+
if (y.body !== undefined) out.body = #{base_name}(y.body);
|
|
845
|
+
EOB
|
|
846
|
+
end
|
|
847
|
+
out.concat([
|
|
848
|
+
"export type #{operation_object[:args_name]} = {",
|
|
849
|
+
], common,
|
|
850
|
+
comments,
|
|
851
|
+
[
|
|
852
|
+
body_props,
|
|
853
|
+
'}',
|
|
854
|
+
has_body,
|
|
855
|
+
"\nexport function #{operation_object[:args_is]}(x: unknown): x is #{operation_object[:args_name]} {",
|
|
856
|
+
"if (!isObject(x)) return false;",
|
|
857
|
+
"const y = x as #{operation_object[:args_name]};"
|
|
858
|
+
], is_func, [
|
|
859
|
+
body_types.empty? ? nil : "if (!#{has_name}(x)) return false;",
|
|
860
|
+
'return true;',
|
|
861
|
+
'}',
|
|
862
|
+
base_body,
|
|
863
|
+
"\nexport function #{operation_object[:args_base]}(x: unknown): #{operation_object[:args_name]} {",
|
|
864
|
+
"const y = x as #{operation_object[:args_name]};",
|
|
865
|
+
"const out: #{operation_object[:args_name]} = {",
|
|
866
|
+
base_func.join(",\n"),
|
|
867
|
+
'}'
|
|
868
|
+
], base_opt_func, [
|
|
869
|
+
base_snip,
|
|
870
|
+
'return out;',
|
|
871
|
+
'}',
|
|
872
|
+
''
|
|
873
|
+
])
|
|
874
|
+
end
|
|
875
|
+
end
|
|
876
|
+
Gen.output.join(out)
|
|
877
|
+
%>
|
|
878
|
+
|
|
879
|
+
// Types containing Response information.
|
|
880
|
+
|
|
881
|
+
<%=
|
|
882
|
+
ignore = %w[content-type] # Get from configuration.
|
|
883
|
+
done = {}
|
|
884
|
+
out = []
|
|
885
|
+
Gen.doc.dig('components', 'responses').each do |name, response|
|
|
886
|
+
# First get info about possible headers type and body types.
|
|
887
|
+
headers = response['headers'] || {}
|
|
888
|
+
remain = headers.keys.reject { |h| ignore.include?(h.downcase) }
|
|
889
|
+
name2type = {}
|
|
890
|
+
remain.each do |h|
|
|
891
|
+
s = Gen.h.dereference(Gen.h.dereference(headers[h])['schema'])
|
|
892
|
+
name2type[h] = {
|
|
893
|
+
name: LuckyCase.camel_case(h),
|
|
894
|
+
type: s[:name],
|
|
895
|
+
schema: s
|
|
896
|
+
}
|
|
897
|
+
end
|
|
898
|
+
content = response['content'] || {}
|
|
899
|
+
content.each do |media_type, mto|
|
|
900
|
+
mt = media_type.downcase
|
|
901
|
+
if mt == 'application/json'
|
|
902
|
+
mto[:source] = 'any'
|
|
903
|
+
elsif mt == 'application/octet-stream'
|
|
904
|
+
mto[:source] = 'Blob'
|
|
905
|
+
elsif mt.start_with?('text/')
|
|
906
|
+
mto[:source] = 'string'
|
|
907
|
+
else
|
|
908
|
+
mto[:source] = 'Blob'
|
|
909
|
+
end
|
|
910
|
+
mto[:decoded] = Gen.h.dereference(mto['schema']) || {
|
|
911
|
+
name: 'any',
|
|
912
|
+
is: 'isAny',
|
|
913
|
+
base: 'baseAny',
|
|
914
|
+
unknown: 'unknown2Any'
|
|
915
|
+
}
|
|
916
|
+
end
|
|
917
|
+
if remain.empty? && content.empty?
|
|
918
|
+
response[:name] = 'object'
|
|
919
|
+
response[:is] = "isObject"
|
|
920
|
+
response[:base] = "baseObject"
|
|
921
|
+
response[:unknown] = "unknown2Object"
|
|
922
|
+
out.push("// Nothing for response #{name}.")
|
|
923
|
+
next
|
|
924
|
+
end
|
|
925
|
+
response[:name2type] = name2type
|
|
926
|
+
# Type with single property could be treated as the property directly but this
|
|
927
|
+
# leaves code unchanged if more parameters are added later.
|
|
928
|
+
# Same for no properties at all.
|
|
929
|
+
# Short-cuts can be added later.
|
|
930
|
+
response[:name] = "#{LuckyCase.pascal_case(name)}Response"
|
|
931
|
+
Gen.x.schemas.types.push(response[:name])
|
|
932
|
+
defs = []
|
|
933
|
+
is_func = []
|
|
934
|
+
base_func = []
|
|
935
|
+
name2type.each do |h, n2t|
|
|
936
|
+
defs.push("#{n2t[:name]}: #{n2t[:type]}|null;")
|
|
937
|
+
is_func.push("if (y.#{n2t[:name]} !== null && !#{n2t[:schema][:is]}(y.#{n2t[:name]})) return false;")
|
|
938
|
+
base_func.push("#{n2t[:name]}: x.#{n2t[:name]} === null ? null : #{n2t[:schema][:base]}(x.#{n2t[:name]})")
|
|
939
|
+
end
|
|
940
|
+
response[:is] = "is#{response[:name]}"
|
|
941
|
+
response[:base] = "base#{response[:name]}"
|
|
942
|
+
response[:unknown] = "unknown2#{response[:name]}"
|
|
943
|
+
Gen.x.schemas.functions.push(response[:is])
|
|
944
|
+
Gen.x.schemas.functions.push(response[:base])
|
|
945
|
+
Gen.x.schemas.functions.push(response[:unknown])
|
|
946
|
+
s = <<EOB
|
|
947
|
+
export type #{response[:name]} = {
|
|
948
|
+
#{defs.join("\n")}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
export function #{response[:is]}(x: unknown): x is #{response[:name]} {
|
|
952
|
+
if (!isObject(x)) return false;
|
|
953
|
+
const y = x as #{response[:name]};
|
|
954
|
+
#{is_func.join("\n")}
|
|
955
|
+
return true;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
export function #{response[:base]}(x: #{response[:name]}): #{response[:name]} {
|
|
959
|
+
return {
|
|
960
|
+
#{base_func.join(",\n")}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
export function #{response[:unknown]}(x: unknown): #{response[:name]} {
|
|
965
|
+
if (#{response[:is]}(x))
|
|
966
|
+
return x as #{response[:name]};
|
|
967
|
+
throw unknown(x, '#{response[:name]}');
|
|
968
|
+
}
|
|
969
|
+
EOB
|
|
970
|
+
out.push(s)
|
|
971
|
+
# For each body type, if any, extend with the body property.
|
|
972
|
+
content.each do |media_type, mto|
|
|
973
|
+
if done.key?(mto[:decoded][:name])
|
|
974
|
+
out.push("// Content-type #{media_type} covered by #{done[mto[:decoded][:name]]}.\n")
|
|
975
|
+
next
|
|
976
|
+
end
|
|
977
|
+
mto[:name] = "#{response[:name]}#{LuckyCase.pascal_case(mto[:decoded][:name])}"
|
|
978
|
+
Gen.x.schemas.types.push(mto[:name])
|
|
979
|
+
mto[:is] = "is#{mto[:name]}"
|
|
980
|
+
mto[:base] = "base#{mto[:name]}"
|
|
981
|
+
mto[:unknown] = "unknown2#{mto[:name]}"
|
|
982
|
+
Gen.x.schemas.functions.push(mto[:is])
|
|
983
|
+
Gen.x.schemas.functions.push(mto[:base])
|
|
984
|
+
Gen.x.schemas.functions.push(mto[:unknown])
|
|
985
|
+
s = <<EOB
|
|
986
|
+
// For content-type #{media_type}
|
|
987
|
+
export type #{mto[:name]} = #{response[:name]} & {
|
|
988
|
+
body: #{mto[:decoded][:name]};
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
export function #{mto[:is]}(x: unknown): x is #{mto[:name]} {
|
|
992
|
+
const y = x as #{mto[:name]};
|
|
993
|
+
if (y.body === undefined || !#{mto[:decoded][:is]}(y.body)) return false;
|
|
994
|
+
if (!#{response[:is]}(y)) return false;
|
|
995
|
+
return true;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
export function #{mto[:base]}(x: #{mto[:name]}): #{mto[:name]} {
|
|
999
|
+
return {
|
|
1000
|
+
...#{response[:base]}(x),
|
|
1001
|
+
body: #{mto[:decoded][:base]}(x.body)
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
export function #{mto[:unknown]}(x: unknown): #{mto[:name]} {
|
|
1006
|
+
if (#{mto[:is]}(x))
|
|
1007
|
+
return x as #{mto[:name]};
|
|
1008
|
+
throw unknown(x, '#{mto[:name]}');
|
|
1009
|
+
}
|
|
1010
|
+
EOB
|
|
1011
|
+
out.push(s)
|
|
1012
|
+
done[mto[:decoded][:name]] = mto[:name]
|
|
1013
|
+
end
|
|
1014
|
+
end
|
|
1015
|
+
Gen.output.join(out)
|
|
1016
|
+
%>
|