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,226 @@
|
|
|
1
|
+
<%=
|
|
2
|
+
Gen.output.config = Gen.x.cfg['ts_indentation']
|
|
3
|
+
Gen.x.generator_info
|
|
4
|
+
%>
|
|
5
|
+
|
|
6
|
+
import type {CallExtras} from './schemas.d.ts';
|
|
7
|
+
|
|
8
|
+
<%=
|
|
9
|
+
# From https://stackoverflow.com/questions/1661197/what-characters-are-valid-for-javascript-variable-names#9337047
|
|
10
|
+
# and later comment.
|
|
11
|
+
# re = Regexp.new('^(?!(?:do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$)[$_\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}][$_\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\u200C\u200D\p{Mn}\p{Mc}\p{Nd}\p{Pc}]*$')
|
|
12
|
+
def allowed_name(str, default_name)
|
|
13
|
+
re = Regexp.new('^[$_\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}][$_\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\u200C\u200D\p{Mn}\p{Mc}\p{Nd}\p{Pc}]*$')
|
|
14
|
+
ok = []
|
|
15
|
+
str.each_char do |char|
|
|
16
|
+
if ok.empty?
|
|
17
|
+
ok.push(char) if re.match(char)
|
|
18
|
+
else
|
|
19
|
+
if re.match('A' + char) # Not first so set something to precede.
|
|
20
|
+
ok.push(char)
|
|
21
|
+
else
|
|
22
|
+
ok.push('_') unless ok.last == '_'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
ok.pop unless ok.empty? || ok.last != '_'
|
|
27
|
+
ok.join || default_name
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
out = []
|
|
31
|
+
# Has any server at top level?
|
|
32
|
+
# Default to server with '/' path.
|
|
33
|
+
servers = Gen.doc['servers'] || []
|
|
34
|
+
top = OpenAPISourceTools::ApiObjects::ServerAlternatives.new(servers)
|
|
35
|
+
Gen.doc[:servers] = top
|
|
36
|
+
Gen.doc[:servers].set_id = 0
|
|
37
|
+
overrides = []
|
|
38
|
+
opIds = []
|
|
39
|
+
opId2group = {}
|
|
40
|
+
group2opIds = {}
|
|
41
|
+
security_schemes = {}
|
|
42
|
+
(Gen.doc.dig('components', 'securitySchemes') || {}).each do |name, scheme|
|
|
43
|
+
security_schemes[name] = OpenAPISourceTools::ApiObjects::SecurityScheme.new(name, scheme)
|
|
44
|
+
end
|
|
45
|
+
security = OpenAPISourceTools::ApiObjects::SecurityAlternatives.new(
|
|
46
|
+
(Gen.doc['security'] || []).map { |s| OpenAPISourceTools::ApiObjects::SecurityRequirementObject.new(s, security_schemes) },
|
|
47
|
+
security_schemes)
|
|
48
|
+
security.set_id = 0
|
|
49
|
+
security_overrides = []
|
|
50
|
+
opId2sec_group = {}
|
|
51
|
+
sec_group2opIds = {}
|
|
52
|
+
# Find server alternatives for each path item, or use top-level.
|
|
53
|
+
# Set security for each operation object.
|
|
54
|
+
Gen.doc['paths'].each do |path, path_item|
|
|
55
|
+
servers = path_item['servers']
|
|
56
|
+
if servers.nil?
|
|
57
|
+
srvs = top
|
|
58
|
+
else
|
|
59
|
+
# If servers is exactly as at top level, this keeps it in separate group.
|
|
60
|
+
# Either servers array is an error or duplication is intentional.
|
|
61
|
+
srvs = OpenAPISourceTools::ApiObjects::ServerAlternatives.new(servers)
|
|
62
|
+
idx = overrides.index(srvs)
|
|
63
|
+
if idx.nil?
|
|
64
|
+
overrides.push(srvs)
|
|
65
|
+
srvs.set_id = overrides.size
|
|
66
|
+
else
|
|
67
|
+
srvs = overrides[index]
|
|
68
|
+
end
|
|
69
|
+
warn("Servers at path #{path} are identical to top level.") if top == srvs
|
|
70
|
+
end
|
|
71
|
+
path_item[:servers] = srvs
|
|
72
|
+
oos = OpenAPISourceTools::ApiObjects.operation_objects(path_item).values
|
|
73
|
+
oos.each do |oo|
|
|
74
|
+
oid = oo['operationId']
|
|
75
|
+
next if oid.nil?
|
|
76
|
+
opIds.push(oid)
|
|
77
|
+
opId2group[oid] = srvs
|
|
78
|
+
group2opIds[srvs.set_id] = group2opIds.fetch(srvs.set_id, []).push(oid)
|
|
79
|
+
sec = oo['security']
|
|
80
|
+
if sec.nil?
|
|
81
|
+
sec = security
|
|
82
|
+
else
|
|
83
|
+
sec = OpenAPISourceTools::ApiObjects::SecurityAlternatives.new(
|
|
84
|
+
sec.map { |s| OpenAPISourceTools::ApiObjects::SecurityRequirementObject.new(s, security_schemes) },
|
|
85
|
+
security_schemes)
|
|
86
|
+
idx = security_overrides.index(sec)
|
|
87
|
+
if idx.nil?
|
|
88
|
+
security_overrides.push(sec)
|
|
89
|
+
sec.set_id = security_overrides.size
|
|
90
|
+
else
|
|
91
|
+
sec = security_overrides[idx]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
oo[:security] = sec
|
|
95
|
+
opId2sec_group[oid] = sec
|
|
96
|
+
sec_group2opIds[sec.set_id] = sec_group2opIds.fetch(sec.set_id, []).push(oid)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Make server variable types, var name to string but names vary.
|
|
101
|
+
# ServerObject matches arguments type, function to use SVO checkers and make URL.
|
|
102
|
+
svos = []
|
|
103
|
+
svo2data = {}.compare_by_identity
|
|
104
|
+
svo2checker = {}.compare_by_identity
|
|
105
|
+
sos = []
|
|
106
|
+
so2data = {}.compare_by_identity
|
|
107
|
+
so2checker = {}.compare_by_identity
|
|
108
|
+
consts = Set.new
|
|
109
|
+
[ top ].concat(overrides).each do |sa|
|
|
110
|
+
sa.servers.each do |so|
|
|
111
|
+
index = sos.index(so)
|
|
112
|
+
if index.nil?
|
|
113
|
+
sos.push(so)
|
|
114
|
+
so2data[so] = so
|
|
115
|
+
index = sos.size
|
|
116
|
+
else
|
|
117
|
+
so2data[so] = sos[index]
|
|
118
|
+
end
|
|
119
|
+
so2checker[so] = LuckyCase.camel_case(allowed_name(so.url, "serverUrl#{index}"))
|
|
120
|
+
so.variables.each do |svo|
|
|
121
|
+
consts.add(svo.default)
|
|
122
|
+
svo.enum.each { |e| consts.add(e) }
|
|
123
|
+
index = svos.index { |x| x.default == svo.default && x.enum == svo.enum }
|
|
124
|
+
if index.nil?
|
|
125
|
+
index = svos.size
|
|
126
|
+
svos.push(svo)
|
|
127
|
+
svo2data[svo] = svo
|
|
128
|
+
svo2data[svo] = svos[index]
|
|
129
|
+
end
|
|
130
|
+
svo2checker[svo] = "serverVariable#{index}"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
value2const = {}
|
|
136
|
+
consts.to_a.sort!.each do |s|
|
|
137
|
+
value2const[s] = "srvVarConst#{value2const.size}"
|
|
138
|
+
out.push("const #{value2const[s]} = '#{s}';")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Behind the scenes a mapping from operationId to server URL string?
|
|
142
|
+
Gen.output.join(out)
|
|
143
|
+
%>
|
|
144
|
+
|
|
145
|
+
<%=
|
|
146
|
+
out = [ '// Server variable enum constants.' ]
|
|
147
|
+
enums2const = {}
|
|
148
|
+
svos.each do |svo|
|
|
149
|
+
next if svo.enum.empty?
|
|
150
|
+
key = svo.enum.join(', ')
|
|
151
|
+
unless enums2const.key?(key)
|
|
152
|
+
name = "srvVarEnumsConst#{enums2const.size}"
|
|
153
|
+
c = "const #{name} = [ #{svo.enum.map { |e| value2const[e] }.join(', ')} ];"
|
|
154
|
+
enums2const[key] = name
|
|
155
|
+
out.push(c)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
out = [ '// No server variable enum constants.' ] if out.size == 1
|
|
159
|
+
Gen.output.join(out)
|
|
160
|
+
%>
|
|
161
|
+
<%=
|
|
162
|
+
out = [ '// Server variable value check functions.' ]
|
|
163
|
+
svos.each do |svo|
|
|
164
|
+
if svo.enum.empty?
|
|
165
|
+
s = <<EOB
|
|
166
|
+
function #{svo2checker[svo]}(value: string|undefined): string {
|
|
167
|
+
return (value === undefined) ? #{value2const[svo.default]} : value;
|
|
168
|
+
}
|
|
169
|
+
EOB
|
|
170
|
+
else
|
|
171
|
+
key = svo.enum.join(', ')
|
|
172
|
+
s = <<EOB
|
|
173
|
+
function #{svo2checker[svo]}(value: string|undefined): string {
|
|
174
|
+
if (value === undefined) {
|
|
175
|
+
return #{value2const[svo.default]};
|
|
176
|
+
}
|
|
177
|
+
if (#{enums2const[key]}.includes(value)) {
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
throw new Error(`Invalid server variable #{svo.name} value: ${value}`);
|
|
181
|
+
}
|
|
182
|
+
EOB
|
|
183
|
+
end
|
|
184
|
+
out.push(s)
|
|
185
|
+
end
|
|
186
|
+
out = [ '// No server variable value check functions.' ] if out.size == 1
|
|
187
|
+
Gen.output.join(out)
|
|
188
|
+
%>
|
|
189
|
+
<%=
|
|
190
|
+
out = [ '// Server URL assign argument types and functions.']
|
|
191
|
+
so2args = {}.compare_by_identity
|
|
192
|
+
sos.each do |so|
|
|
193
|
+
Gen.x.servers.functions.push(so2checker[so])
|
|
194
|
+
if so.variables.empty?
|
|
195
|
+
s = <<EOB
|
|
196
|
+
// No variables.
|
|
197
|
+
export function #{so2checker[so]}(): string {
|
|
198
|
+
return '#{so.url}';
|
|
199
|
+
}
|
|
200
|
+
EOB
|
|
201
|
+
out.push(s)
|
|
202
|
+
next
|
|
203
|
+
end
|
|
204
|
+
iname = "#{LuckyCase.pascal_case(so2checker[so])}Args"
|
|
205
|
+
so2args[so] = iname
|
|
206
|
+
Gen.x.servers.types.push(iname)
|
|
207
|
+
s = <<EOB
|
|
208
|
+
export type #{iname} = {
|
|
209
|
+
#{so.variables.map { |v| "#{v.name}#{v.default ? '?' : ''}: string;" }.join("\n ")}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function #{so2checker[so]}(args: #{iname}): string {
|
|
213
|
+
#{so.variables.map { |v| "const #{v.name} = encodeURIComponent(#{svo2checker[v]}(args.#{v.name}));" }.join("\n ")}
|
|
214
|
+
return `#{so.url.gsub('{', '${')}`;
|
|
215
|
+
}
|
|
216
|
+
EOB
|
|
217
|
+
out.push(s)
|
|
218
|
+
end
|
|
219
|
+
Gen.x.server_data = { # Store for test generation.
|
|
220
|
+
server_objects: sos,
|
|
221
|
+
checkers: so2checker,
|
|
222
|
+
args: so2args
|
|
223
|
+
}
|
|
224
|
+
out = [ '// No server URL compose argument types or functions.'] if out.size == 1
|
|
225
|
+
Gen.output.join(out)
|
|
226
|
+
%>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<%=
|
|
2
|
+
Gen.output.config = Gen.x.cfg['ts_indentation']
|
|
3
|
+
Gen.x.generator_info
|
|
4
|
+
%>
|
|
5
|
+
|
|
6
|
+
import type {<%= %w[CallExtras].concat(Gen.x.schemas.import_types).join(', ') %>} from './schemas.d.ts';
|
|
7
|
+
import type { RequestInitModifier } from './callclasses.d.ts';
|
|
8
|
+
import type { EncoderFunction } from './schemas.d.ts';
|
|
9
|
+
// Type that holds all parameters suffixed by their type.
|
|
10
|
+
|
|
11
|
+
export type AllParameters = {
|
|
12
|
+
<%=
|
|
13
|
+
name2types = {}
|
|
14
|
+
Gen.doc['paths'].each do |path, path_item_object|
|
|
15
|
+
oos = OpenAPISourceTools::ApiObjects.operation_objects(path_item_object)
|
|
16
|
+
oos.each do |method, operation_object|
|
|
17
|
+
next if operation_object[:parameterless]
|
|
18
|
+
operation_object[:paraschemas].each do |ps|
|
|
19
|
+
types = name2types.fetch(ps[:param][:name], [])
|
|
20
|
+
types.push({
|
|
21
|
+
name: ps[:param][:name],
|
|
22
|
+
type: ps[:schema][:name],
|
|
23
|
+
ps: ps,
|
|
24
|
+
count: 0,
|
|
25
|
+
shared: '',
|
|
26
|
+
majority: false,
|
|
27
|
+
ooid: operation_object.object_id
|
|
28
|
+
})
|
|
29
|
+
name2types[ps[:param][:name]] = types
|
|
30
|
+
ps[:shared] = types.last
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
out = []
|
|
35
|
+
ap = {}
|
|
36
|
+
names = name2types.keys.sort!
|
|
37
|
+
names.each do |name|
|
|
38
|
+
types = name2types[name]
|
|
39
|
+
uniqs = types.map { |ti| ti[:type] }.uniq
|
|
40
|
+
type2count = {}
|
|
41
|
+
uniqs.each do |type|
|
|
42
|
+
type2count[type] = types.count { |ti| ti[:type] == type }
|
|
43
|
+
end
|
|
44
|
+
uniqs.sort! { |a, b| type2count[b] <=> type2count[a] }
|
|
45
|
+
if uniqs.size == 1 || uniqs.size > 1 && type2count[uniqs.first] > type2count[uniqs[1]]
|
|
46
|
+
out.push(" #{name}?: #{uniqs.first};")
|
|
47
|
+
types.select { |ti| ti[:type] == uniqs.first }.each do |ti|
|
|
48
|
+
ti[:majority] = true
|
|
49
|
+
ap[name] = ap.fetch(name, []).push(ti)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
uniqs.each do |type|
|
|
53
|
+
combo = name + type
|
|
54
|
+
out.push(" #{combo}?: #{type};")
|
|
55
|
+
types.select { |ti| ti[:type] == type }.each do |ti|
|
|
56
|
+
ti[:shared] = combo
|
|
57
|
+
ti[:count] = type2count[type]
|
|
58
|
+
ap[combo] = ap.fetch(combo, []).push(ti)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
Gen.doc[:all_params] = ap
|
|
63
|
+
Gen.output.join(out)
|
|
64
|
+
%>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type Shared = {
|
|
68
|
+
serverUrl?: string;
|
|
69
|
+
params: AllParameters;
|
|
70
|
+
extras: CallExtras;
|
|
71
|
+
modifier: RequestInitModifier|null;
|
|
72
|
+
abortSignalable: boolean;
|
|
73
|
+
encoder: EncoderFunction|null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const shared: Shared = {
|
|
77
|
+
params: {},
|
|
78
|
+
extras: {},
|
|
79
|
+
modifier: null,
|
|
80
|
+
abortSignalable: false,
|
|
81
|
+
encoder: null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Functions to get a partial of shared that contains exactly what callclass args type has.
|
|
85
|
+
|
|
86
|
+
<%=
|
|
87
|
+
out = []
|
|
88
|
+
Gen.doc['paths'].each do |path, path_item_object|
|
|
89
|
+
oos = OpenAPISourceTools::ApiObjects.operation_objects(path_item_object)
|
|
90
|
+
oos.each do |method, operation_object|
|
|
91
|
+
next if operation_object[:parameterless]
|
|
92
|
+
assigns = []
|
|
93
|
+
operation_object[:paraschemas].each do |ps|
|
|
94
|
+
ti = ps[:shared]
|
|
95
|
+
assigns.push(" if (shared.params.#{ti[:shared]} !== undefined) out.#{ti[:name]} = shared.params.#{ti[:shared]};")
|
|
96
|
+
if ti[:majority]
|
|
97
|
+
assigns.push(" else if (shared.params.#{ti[:name]} !== undefined) out.#{ti[:name]} = shared.params.#{ti[:name]};")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
func_name = "shared#{operation_object[:args_name]}"
|
|
101
|
+
Gen.x.shared.functions.push(func_name)
|
|
102
|
+
operation_object[:shared] = func_name
|
|
103
|
+
s = <<EOB
|
|
104
|
+
export function #{func_name}(): Partial<#{operation_object[:args_name]}> {
|
|
105
|
+
const out: Partial<#{operation_object[:args_name]}> = {};
|
|
106
|
+
#{assigns.join("\n")}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
EOB
|
|
110
|
+
out.push(s)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
Gen.output.join(out)
|
|
114
|
+
%>
|