open_api_import 0.10.11 → 0.11.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.
@@ -1,1034 +1,17 @@
1
1
  require_relative "open_api_import/utils"
2
+ require_relative "open_api_import/filter"
3
+ require_relative "open_api_import/pretty_hash_symbolized"
4
+ require_relative "open_api_import/get_patterns"
5
+ require_relative "open_api_import/get_required_data"
6
+ require_relative "open_api_import/get_data_all_of_bodies"
7
+ require_relative "open_api_import/get_response_examples"
8
+ require_relative "open_api_import/get_examples"
9
+ require_relative "open_api_import/open_api_import"
10
+
11
+ include LibOpenApiImport
2
12
 
3
13
  require "oas_parser"
4
14
  require "rufo"
5
15
  require "nice_hash"
6
16
  require "logger"
7
17
 
8
- class OpenApiImport
9
- ##############################################################################################
10
- # Import a Swagger or Open API file and create a Ruby Request Hash file including all requests and responses.
11
- # The http methods that will be treated are: 'get','post','put','delete', 'patch'.
12
- # @param swagger_file [String]. Path and file name. Could be absolute or relative to project root folder.
13
- # @param include_responses [Boolean]. (default: true) if you want to add the examples of responses in the resultant file.
14
- # @param mock_response [Boolean]. (default:false) Add the first response on the request as mock_response to be used.
15
- # In case using nice_http gem: if NiceHttp.use_mocks = true will use it instead of getting the real response from the WS.
16
- # @param create_method_name [Symbol]. (:path, :operation_id, :operationId) (default: operation_id). How the name of the methods will be generated.
17
- # path: it will be used the path and http method, for example for a GET on path: /users/list, the method name will be get_users_list
18
- # operation_id: it will be used the operationId field but using the snake_case version, for example for listUsers: list_users
19
- # operationId: it will be used the operationId field like it is, for example: listUsers
20
- # @param name_for_module [Symbol]. (:path, :path_file, :fixed, :tags, :tags_file) (default: :path). How the module names will be created.
21
- # @param create_constants [Boolean]. (default: false) For required arguments, it will create keyword arguments assigning by default a constant.
22
- # @param silent [Boolean]. (default: false) It will display only errors.
23
- # path: It will be used the first folder of the path to create the module name, for example the path /users/list will be in the module Users and all the requests from all modules in the same file.
24
- # path_file: It will be used the first folder of the path to create the module name, for example the path /users/list will be in the module Users and each module will be in a new requests file.
25
- # tags: It will be used the tags key to create the module name, for example the tags: [users,list] will create the module UsersList and all the requests from all modules in the same file.
26
- # tags_file: It will be used the tags key to create the module name, for example the tags: [users,list] will create the module UsersList and and each module will be in a new requests file.
27
- # fixed: all the requests will be under the module Requests
28
- ##############################################################################################
29
- def self.from(swagger_file, create_method_name: :operation_id, include_responses: true, mock_response: false, name_for_module: :path, silent: false, create_constants: false)
30
- begin
31
- f = File.new("#{swagger_file}_open_api_import.log", "w")
32
- f.sync = true
33
- @logger = Logger.new f
34
- puts "Logs file: #{swagger_file}_open_api_import.log" unless silent
35
- rescue StandardError => e
36
- warn "Not possible to create the Logger file"
37
- warn e
38
- @logger = Logger.new nil
39
- end
40
-
41
- begin
42
- @logger.info "swagger_file: #{swagger_file}, include_responses: #{include_responses}, mock_response: #{mock_response}\n"
43
- @logger.info "create_method_name: #{create_method_name}, name_for_module: #{name_for_module}\n"
44
-
45
- file_to_convert = if swagger_file["./"].nil?
46
- swagger_file
47
- else
48
- Dir.pwd.to_s + "/" + swagger_file.gsub("./", "")
49
- end
50
- unless File.exist?(file_to_convert)
51
- raise "The file #{file_to_convert} doesn't exist"
52
- end
53
-
54
- file_errors = file_to_convert + ".errors.log"
55
- File.delete(file_errors) if File.exist?(file_errors)
56
- import_errors = ""
57
- required_constants = []
58
-
59
- begin
60
- definition = OasParser::Definition.resolve(swagger_file)
61
- rescue Exception => stack
62
- message = "There was a problem parsing the Open Api document using the oas_parser gem. The execution was aborted.\n"
63
- message += "Visit the github for oas_parser gem for bugs and more info: https://github.com/Nexmo/oas_parser\n"
64
- message += "Error: #{stack.message}"
65
- puts message
66
- @logger.fatal message
67
- @logger.fatal stack.backtrace
68
- exit!
69
- end
70
-
71
- raw = definition.raw.deep_symbolize_keys
72
-
73
- if raw.key?(:openapi) && (raw[:openapi].to_f > 0)
74
- raw[:swagger] = raw[:openapi]
75
- end
76
- if raw[:swagger].to_f < 2.0
77
- raise "Unsupported Swagger version. Only versions >= 2.0 are valid."
78
- end
79
-
80
- base_host = ""
81
- base_path = ""
82
-
83
- base_host = raw[:host] if raw.key?(:host)
84
- base_path = raw[:basePath] if raw.key?(:basePath)
85
- module_name = raw[:info][:title].camel_case
86
- module_version = "V#{raw[:info][:version].to_s.snake_case}"
87
-
88
- output = []
89
- output_header = []
90
- output_header << "#" * 50
91
- output_header << "# #{raw[:info][:title]}"
92
- output_header << "# version: #{raw[:info][:version]}"
93
- output_header << "# description: "
94
- raw[:info][:description].to_s.split("\n").each do |d|
95
- output_header << "# #{d}" unless d == ""
96
- end
97
- output_header << "#" * 50
98
-
99
- output_header << "module Swagger"
100
- output_header << "module #{module_name}"
101
- output_header << "module #{module_version}"
102
- output_header << "module Requests" if name_for_module == :fixed
103
-
104
- files = {}
105
-
106
- module_requests = ""
107
-
108
- definition.paths.each do |path|
109
-
110
- raw = path.raw.deep_symbolize_keys
111
-
112
- if raw.key?(:parameters)
113
- raw.each do |met, cont|
114
- if met != :parameters
115
- if raw[met].key?(:parameters)
116
- #in case parameters for all methods in path is present
117
- raw[met][:parameters] = raw[met][:parameters] + raw[:parameters]
118
- else
119
- raw[met][:parameters] = raw[:parameters]
120
- end
121
- end
122
- end
123
- raw.delete(:parameters)
124
- end
125
-
126
- raw.each do |met, cont|
127
-
128
- if %w[get post put delete patch].include?(met.to_s.downcase)
129
- params = []
130
- params_path = []
131
- params_query = []
132
- params_required = []
133
- params_data = []
134
- description_parameters = []
135
- data_form = []
136
- data_required = []
137
- #todo: add nested one.true.three to data_read_only
138
- data_read_only = []
139
- data_default = []
140
- data_examples = []
141
- data_pattern = []
142
- responses = []
143
-
144
- # for the case operationId is missing
145
- cont[:operationId] = "undefined" unless cont.key?(:operationId)
146
-
147
- if create_method_name == :path
148
- method_name = (met.to_s + "_" + path.path.to_s).snake_case
149
- method_name.chop! if method_name[-1] == "_"
150
- elsif create_method_name == :operation_id
151
- if (name_for_module == :tags or name_for_module == :tags_file) and cont.key?(:tags) and cont[:tags].is_a?(Array) and cont[:tags].size>0
152
- metnametmp = cont[:operationId].gsub(/^#{cont[:tags].join}[\s_]*/, '')
153
- cont[:tags].join.split(' ').each do |tag|
154
- metnametmp.gsub!(/^#{tag}[\s_]*/i, '')
155
- end
156
- metnametmp = met if metnametmp == ''
157
- else
158
- metnametmp = cont[:operationId]
159
- end
160
- method_name = metnametmp.to_s.snake_case
161
- else
162
- if (name_for_module == :tags or name_for_module == :tags_file) and cont.key?(:tags) and cont[:tags].is_a?(Array) and cont[:tags].size>0
163
- method_name = cont[:operationId].gsub(/^#{cont[:tags].join}[\s_]*/, '')
164
- cont[:tags].join.split(' ').each do |tag|
165
- method_name.gsub!(/^#{tag}[\s_]*/i, '')
166
- end
167
- method_name = met if method_name == ''
168
- else
169
- method_name = cont[:operationId]
170
- end
171
- end
172
- path_txt = path.path.dup.to_s
173
- if [:path, :path_file, :tags, :tags_file].include?(name_for_module)
174
- old_module_requests = module_requests
175
- if [:path, :path_file].include?(name_for_module)
176
- # to remove version from path fex: /v1/Customer
177
- path_requests = path_txt.gsub(/^\/v[\d\.]*\//i, "")
178
- # to remove version from path fex: /1.0/Customer
179
- path_requests = path_requests.gsub(/^\/[\d\.]*\//i, "")
180
- if (path_requests == path_txt) && (path_txt.scan("/").size == 1)
181
- # no folder in path
182
- module_requests = "Root"
183
- else
184
- res_path = path_requests.scan(/(\w+)/)
185
- module_requests = res_path[0][0].camel_case
186
- end
187
- else
188
- if cont.key?(:tags) and cont[:tags].is_a?(Array) and cont[:tags].size>0
189
- module_requests = cont[:tags].join(" ").camel_case
190
- else
191
- module_requests = "Undefined"
192
- end
193
- end
194
-
195
- # to remove from method_name: v1_list_regions and add it to module
196
- if /^(?<vers>v\d+)/i =~ method_name
197
- method_name.gsub!(/^#{vers}_?/,'')
198
- module_requests = (vers.capitalize + module_requests).camel_case unless module_requests.start_with?(vers)
199
- end
200
-
201
- if old_module_requests != module_requests
202
- output << "end" unless old_module_requests == "" or name_for_module == :path_file or name_for_module == :tags_file
203
- if name_for_module == :path or name_for_module == :tags
204
- # to add the end for the previous module unless is the first one
205
- output << "module #{module_requests}"
206
- else #:path_file, :tags_file
207
- if old_module_requests != ""
208
- unless files.key?(old_module_requests)
209
- files[old_module_requests] = Array.new
210
- end
211
- files[old_module_requests].concat(output)
212
- output = Array.new
213
- end
214
- output << "module #{module_requests}" unless files.key?(module_requests) # don't add in case already existed
215
- end
216
- end
217
- end
218
-
219
- output << ""
220
- output << "# operationId: #{cont[:operationId]}, method: #{met}"
221
- output << "# summary: #{cont[:summary]}"
222
- if !cont[:description].to_s.split("\n").empty?
223
- output << "# description: "
224
- cont[:description].to_s.split("\n").each do |d|
225
- output << "# #{d}" unless d == ""
226
- end
227
- else
228
- output << "# description: #{cont[:description]}"
229
- end
230
-
231
- mock_example = []
232
-
233
- if include_responses && cont.key?(:responses) && cont[:responses].is_a?(Hash)
234
- cont[:responses].each do |k, v|
235
- response_example = []
236
- response_example = get_response_examples(v)
237
-
238
- data_pattern += get_patterns('', v[:schema]) if v.key?(:schema)
239
- data_pattern.uniq!
240
- v[:description] = v[:description].to_s.gsub("'", %q(\\\'))
241
- if !response_example.empty?
242
- responses << "'#{k}': { "
243
- responses << "message: '#{v[:description]}', "
244
- responses << "data: "
245
- responses << response_example
246
- responses << "},"
247
- if mock_response and mock_example.size==0
248
- mock_example << "code: '#{k}',"
249
- mock_example << "message: '#{v[:description]}',"
250
- mock_example << "data: "
251
- mock_example << response_example
252
- end
253
-
254
- else
255
- responses << "'#{k}': { message: '#{v[:description]}'}, "
256
- end
257
- end
258
- end
259
- # todo: for open api 3.0 add the new Link feature: https://swagger.io/docs/specification/links/
260
- # todo: for open api 3.0 is not getting the required params in all cases
261
-
262
- # for the case open api 3 with cont.requestBody.content.'applicatin/json'.schema
263
- # example: petstore-expanded.yaml operationId=addPet
264
- if cont.key?(:requestBody) and cont[:requestBody].key?(:content) and
265
- cont[:requestBody][:content].key?(:'application/json') and cont[:requestBody][:content][:'application/json'].key?(:schema)
266
- cont[:parameters] = [] unless cont.key?(:parameters)
267
- cont[:parameters] << {in: 'body', schema: cont[:requestBody][:content][:'application/json'][:schema] }
268
- end
269
- data_examples_all_of = false
270
- if cont.key?(:parameters) && cont[:parameters].is_a?(Array)
271
- cont[:parameters].each do |p|
272
- if p.keys.include?(:schema) and p[:schema].include?(:type)
273
- type = p[:schema][:type]
274
- elsif p.keys.include?(:type)
275
- type = p[:type]
276
- else
277
- type = ""
278
- end
279
- if p[:in] == "path"
280
- if create_method_name == :operationId
281
- param_name = p[:name]
282
- path_txt.gsub!("{#{param_name}}", "\#{#{param_name}}")
283
- else
284
- param_name = p[:name].to_s.snake_case
285
- path_txt.gsub!("{#{p[:name]}}", "\#{#{param_name}}")
286
- end
287
- unless params_path.include?(param_name)
288
- if create_constants
289
- params_path << "#{param_name}: #{param_name.upcase}"
290
- required_constants << param_name.upcase
291
- else
292
- params_path << param_name
293
- end
294
- #params_required << param_name if p[:required].to_s=="true"
295
- description_parameters << "# #{p[:name]}: (#{type}) #{"(required)" if p[:required].to_s=="true"} #{p[:description].split("\n").join("\n#\t\t\t")}"
296
- end
297
- elsif p[:in] == "query"
298
- params_query << p[:name]
299
- params_required << p[:name] if p[:required].to_s=="true"
300
- description_parameters << "# #{p[:name]}: (#{type}) #{"(required)" if p[:required].to_s=="true"} #{p[:description].split("\n").join("\n#\t\t\t")}"
301
- elsif p[:in] == "formData" or p[:in] == "formdata"
302
- #todo: take in consideration: default, required
303
- #todo: see if we should add the required as params to the method and not required as options
304
- #todo: set on data the required fields with the values from args
305
-
306
- description_parameters << "# #{p[:name]}: (#{p[:type]}) #{p[:description].split("\n").join("\n#\t\t\t")}"
307
-
308
- case p[:type]
309
- when /^string$/i
310
- data_form << "#{p[:name]}: ''"
311
- when /^boolean$/i
312
- data_form << "#{p[:name]}: true"
313
- when /^number$/i
314
- data_form << "#{p[:name]}: 0"
315
- when /^integer$/i
316
- data_form << "#{p[:name]}: 0"
317
- else
318
- puts "! on formData not supported type #{p[:type]}"
319
- end
320
-
321
- elsif p[:in] == "body"
322
- if p.keys.include?(:schema)
323
- if p[:schema].key?(:oneOf)
324
- bodies = p[:schema][:oneOf]
325
- elsif p[:schema].key?(:anyOf)
326
- bodies = p[:schema][:anyOf]
327
- elsif p[:schema].key?(:allOf)
328
- data_examples_all_of, bodies = get_data_all_of_bodies(p)
329
- data_examples_all_of = true # because we are on data and allOf already
330
- else
331
- bodies = [p[:schema]]
332
- end
333
-
334
- params_data = []
335
-
336
- bodies.each do |body|
337
- if body.keys.include?(:required) and body[:required].size > 0
338
- data_required += get_required_data(body)
339
- output << "# required data: #{data_required.inspect}"
340
- end
341
-
342
- if body.keys.include?(:properties) and body[:properties].size > 0
343
-
344
- body[:properties].each { |dpk, dpv|
345
- if dpv.keys.include?(:example)
346
- if dpv[:example].is_a?(Array) and dpv.type != 'array'
347
- valv = dpv[:example][0]
348
- else
349
- valv = dpv[:example].to_s
350
- end
351
- else
352
- if dpv.type == "object"
353
- if dpv.key?(:properties)
354
- valv = get_examples(dpv[:properties], :key_value, true).join("\n")
355
- else
356
- valv = "{}"
357
- end
358
- elsif dpv.type == 'array'
359
- if dpv.key?(:items)
360
- valv = get_examples({dpk => dpv}, :only_value)
361
- valv = valv.join("\n")
362
- else
363
- valv = "[]"
364
- end
365
- else
366
- valv = ""
367
- end
368
- end
369
-
370
- if dpv.keys.include?(:description)
371
- description_parameters << "# #{dpk}: (#{dpv[:type]}) #{dpv[:description].split("\n").join("\n#\t\t\t")}"
372
- end
373
-
374
- data_pattern += get_patterns(dpk,dpv)
375
- data_pattern.uniq!
376
- dpkeys = []
377
- data_pattern.reject! do |dp|
378
- dpkey = dp.scan(/^'[\w\.]+'/)
379
-
380
- if dpkeys.include?(dpkey)
381
- true
382
- else
383
- dpkeys << dpkey
384
- false
385
- end
386
- end
387
-
388
- if dpv.keys.include?(:readOnly) and dpv[:readOnly] == true
389
- data_read_only << dpk
390
- end
391
- if dpv.keys.include?(:default)
392
- if dpv[:default].nil?
393
- data_default << "#{dpk}: nil"
394
- elsif dpv.type != "string"
395
- data_default << "#{dpk}: #{dpv[:default]}"
396
- else
397
- data_default << "#{dpk}: '#{dpv[:default]}'"
398
- end
399
- end
400
-
401
- #todo: consider check default and insert it
402
- #todo: remove array from here and add the option to get_examples for the case thisisthekey: ['xxxx']
403
- if dpv.key?(:type) and dpv[:type]!='array'
404
- params_data << get_examples({dpk => dpv}, :only_value, true).join
405
- params_data[-1].chop!.chop! if params_data[-1].to_s[-2..-1]==', '
406
- params_data.pop if params_data[-1].match?(/^\s*$/im)
407
- else
408
- if valv.to_s == ""
409
- valv = '""'
410
- elsif valv.include?('"')
411
- valv.gsub!('"',"'")
412
- end
413
- params_data << "#{dpk}: #{valv}"
414
- end
415
- }
416
- if params_data.size > 0
417
- if data_examples_all_of == true and data_examples.size > 0
418
- data_examples[0]+=params_data
419
- else
420
- data_examples << params_data
421
- end
422
- params_data = []
423
- end
424
- end
425
- end
426
- end
427
- elsif p[:in]=="header"
428
- #todo: see how we can treat those cases
429
- else
430
- puts "! not imported data with :in:#{p[:in]} => #{p.inspect}"
431
- end
432
- end
433
-
434
- params = params_path
435
-
436
- unless params_query.empty?
437
- path_txt += "?"
438
- params_required.each do |pr|
439
- if create_constants
440
- if params_query.include?(pr)
441
- if create_method_name == :operationId
442
- path_txt += "#{pr}=\#{#{pr}}&"
443
- params << "#{pr}: #{pr.upcase}"
444
- required_constants << pr.upcase
445
- else
446
- path_txt += "#{pr}=\#{#{pr.to_s.snake_case}}&"
447
- params << "#{pr.to_s.snake_case}: #{pr.to_s.snake_case.upcase}"
448
- required_constants << pr.to_s.snake_case.upcase
449
- end
450
- end
451
- else
452
- if params_query.include?(pr)
453
- if create_method_name == :operationId
454
- path_txt += "#{pr}=\#{#{pr}}&"
455
- params << "#{pr}"
456
- else
457
- path_txt += "#{pr}=\#{#{pr.to_s.snake_case}}&"
458
- params << "#{pr.to_s.snake_case}"
459
- end
460
- end
461
- end
462
- end
463
- params_query.each do |pq|
464
- unless params_required.include?(pq)
465
- if create_method_name == :operationId
466
- path_txt += "#{pq}=\#{#{pq}}&"
467
- params << "#{pq}: ''"
468
- else
469
- path_txt += "#{pq}=\#{#{pq.to_s.snake_case}}&"
470
- params << "#{pq.to_s.snake_case}: ''"
471
- end
472
- end
473
- end
474
- end
475
-
476
- end
477
-
478
- if description_parameters.size > 0
479
- output << "# parameters description: "
480
- output << description_parameters
481
- end
482
-
483
- #for the case we still have some parameters on path that were not in 'parameters'
484
- if path_txt.scan(/[^#]{\w+}/).size > 0
485
- paramst = []
486
- prms = path_txt.scan(/[^#]{(\w+)}/)
487
- prms.each do |p|
488
- #if create_constants
489
- # paramst<<"#{p[0].to_s.snake_case}: #{p[0].to_s.snake_case.upcase}"
490
- # required_constants << p[0].to_s.snake_case.upcase
491
- #else
492
- paramst<<p[0].to_s.snake_case
493
- #end
494
- path_txt.gsub!("{#{p[0]}}", "\#{#{p[0].to_s.snake_case}}")
495
- end
496
- paramst.concat params
497
- params = paramst
498
- end
499
- params.uniq!
500
- output << "def self.#{method_name} (#{params.join(", ")})"
501
-
502
- output << "{"
503
-
504
- output << "name: \"#{module_requests}.#{method_name}\","
505
-
506
- output << "path: \"#{base_path}#{path_txt}\","
507
-
508
- output << "method: :#{met}," if met.to_s != ""
509
-
510
- unless data_required.empty?
511
- output << "data_required: ["
512
- output << ":'#{data_required.uniq.join("', :'")}'"
513
- output << "],"
514
- end
515
- unless data_read_only.empty?
516
- output << "data_read_only: ["
517
- output << ":'#{data_read_only.uniq.join("', :'")}'"
518
- output << "],"
519
- end
520
- unless data_default.empty?
521
- output << "data_default: {"
522
- output << data_default.join(", \n")
523
- output << "},"
524
- end
525
-
526
- unless data_pattern.empty?
527
- output << "data_pattern: {"
528
- output << data_pattern.uniq.join(", \n")
529
- output << "},"
530
- end
531
-
532
- unless data_form.empty?
533
- data_examples << data_form
534
- end
535
-
536
- unless data_examples.empty?
537
- unless data_required.empty?
538
- reqdata = []
539
- begin
540
- data_ex = eval("{#{data_examples[0].join(", ")}}")
541
- rescue
542
- data_ex = {}
543
- end
544
- if (data_required.grep(/\./)).empty?
545
- reqdata = filter(data_ex, data_required) #not nested
546
- else
547
- reqdata = filter(data_ex, data_required, true) #nested
548
- end
549
- unless reqdata.empty?
550
- phsd = pretty_hash_symbolized(reqdata)
551
- phsd[0]="data: {"
552
- output += phsd
553
- end
554
- end
555
- unless data_read_only.empty? or !data_required.empty?
556
- reqdata = []
557
- #remove read only fields from :data
558
- data_examples[0].each do |edata|
559
- read_only = false
560
- data_read_only.each do |rdata|
561
- if edata.scan(/^#{rdata}:/).size>0
562
- read_only = true
563
- break
564
- elsif edata.scan(/:/).size==0
565
- break
566
- end
567
- end
568
- reqdata << edata unless read_only
569
- end
570
- unless reqdata.empty?
571
- output << "data: {"
572
- output << reqdata.join(", \n")
573
- output << "},"
574
- end
575
- end
576
-
577
- output << "data_examples: ["
578
- data_examples.each do |data|
579
- output << "{"
580
- output << data.join(", \n")
581
- output << "}, "
582
- end
583
- output << "],"
584
- end
585
-
586
- unless mock_example.empty?
587
- output << "mock_response: {"
588
- output << mock_example
589
- output << "},"
590
- end
591
-
592
- unless responses.empty?
593
- output << "responses: {"
594
- output << responses
595
- output << "},"
596
- end
597
-
598
- output << "}"
599
- output << "end"
600
- else
601
- @logger.warn "Not imported method: #{met} for path: #{path.path} since it is not supported by OpenApiImport"
602
- end
603
- end
604
- end
605
- output_footer = []
606
-
607
- output_footer << "end" unless (module_requests == "") && ([:path, :path_file, :tags, :tags_file].include?(name_for_module))
608
- output_footer << "end" << "end" << "end"
609
-
610
- if files.size == 0
611
- output = output_header + output + output_footer
612
- output_txt = output.join("\n")
613
- requests_file_path = file_to_convert + ".rb"
614
- File.open(requests_file_path, "w") { |file| file.write(output_txt) }
615
- res_rufo = `rufo #{requests_file_path}`
616
- message = "** Requests file: #{swagger_file}.rb that contains the code of the requests after importing the Swagger file"
617
- puts message unless silent
618
- @logger.info message
619
- @logger.error " Error formating with rufo" unless res_rufo.to_s.match?(/\AFormat:.+$\s*\z/)
620
- @logger.error " Syntax Error: #{`ruby -c #{requests_file_path}`}" unless `ruby -c #{requests_file_path}`.include?("Syntax OK")
621
- else
622
- unless files.key?(module_requests)
623
- files[module_requests] = Array.new
624
- end
625
- files[module_requests].concat(output) #for the last one
626
-
627
- requires_txt = ""
628
- message = "** Generated files that contain the code of the requests after importing the Swagger file: "
629
- puts message unless silent
630
- @logger.info message
631
- files.each do |mod, out_mod|
632
- output = output_header + out_mod + output_footer
633
- output_txt = output.join("\n")
634
- requests_file_path = file_to_convert + "_" + mod + ".rb"
635
- requires_txt += "require_relative '#{File.basename(swagger_file)}_#{mod}'\n"
636
- File.open(requests_file_path, "w") { |file| file.write(output_txt) }
637
- res_rufo = `rufo #{requests_file_path}`
638
- message = " - #{requests_file_path}"
639
- puts message unless silent
640
- @logger.info message
641
- @logger.error " Error formating with rufo" unless res_rufo.to_s.match?(/\AFormat:.+$\s*\z/)
642
- @logger.error " Syntax Error: #{`ruby -c #{requests_file_path}`}" unless `ruby -c #{requests_file_path}`.include?("Syntax OK")
643
- end
644
-
645
- requests_file_path = file_to_convert + ".rb"
646
- if required_constants.size > 0
647
- rconsts = "# Required constants\n"
648
- required_constants.uniq!
649
- required_constants.each do |rq|
650
- rconsts += "#{rq} ||= ENV['#{rq}'] ||=''\n"
651
- end
652
- rconsts += "\n\n"
653
- else
654
- rconsts = ''
655
- end
656
-
657
- File.open(requests_file_path, "w") { |file| file.write(rconsts + requires_txt) }
658
- res_rufo = `rufo #{requests_file_path}`
659
- message = "** File that contains all the requires for all Request files: \n"
660
- message += " - #{requests_file_path} "
661
- puts message unless silent
662
- @logger.info message
663
- @logger.error " Error formating with rufo" unless res_rufo.to_s.match?(/\AFormat:.+$\s*\z/)
664
- @logger.error " Syntax Error: #{`ruby -c #{requests_file_path}`}" unless `ruby -c #{requests_file_path}`.include?("Syntax OK")
665
- end
666
-
667
- begin
668
- res = eval(output_txt)
669
- rescue Exception => stack
670
- import_errors += "\n\nResult evaluating the ruby file generated: \n" + stack.to_s
671
- end
672
-
673
- if import_errors.to_s != ""
674
- File.open(file_errors, "w") { |file| file.write(import_errors) }
675
- message = "* It seems there was a problem importing the Swagger file #{file_to_convert}\n"
676
- message += "* Take a look at the detected errors at #{file_errors}\n"
677
- warn message
678
- @logger.fatal message
679
- return false
680
- else
681
- return true
682
- end
683
- rescue StandardError => stack
684
- puts stack.message
685
- @logger.fatal stack.message
686
- @logger.fatal stack.backtrace
687
- puts stack.backtrace
688
- end
689
- end
690
-
691
- class << self
692
- # Retrieve the examples from the properties hash
693
- private def get_examples(properties, type=:key_value, remove_readonly=false)
694
- #todo: consider using this method also to get data examples
695
- example = []
696
- example << "{" unless properties.empty? or type==:only_value
697
- properties.each do |prop, val|
698
- unless remove_readonly and val.key?(:readOnly) and val[:readOnly]==true
699
- if val.key?(:properties) and !val.key?(:example) and !val.key?(:type)
700
- val[:type]='object'
701
- end
702
- if val.key?(:items) and !val.key?(:example) and !val.key?(:type)
703
- val[:type]='array'
704
- end
705
- if val.key?(:example)
706
- if val[:example].is_a?(Array) and val.key?(:type) and val[:type]=='string'
707
- example << " #{prop.to_sym}: \"#{val[:example][0]}\", " # only the first example
708
- else
709
- if val[:example].is_a?(String)
710
- val[:example].gsub!('"', "'")
711
- example << " #{prop.to_sym}: \"#{val[:example]}\", "
712
- elsif val[:example].is_a?(Time)
713
- example << " #{prop.to_sym}: \"#{val[:example]}\", "
714
- else
715
- example << " #{prop.to_sym}: #{val[:example]}, "
716
- end
717
- end
718
- elsif val.key?(:type)
719
- format = val[:format]
720
- format = val[:type] if format.to_s == ""
721
- case val[:type].downcase
722
- when "string"
723
- example << " #{prop.to_sym}: \"#{format}\", "
724
- when "integer", "number"
725
- example << " #{prop.to_sym}: 0, "
726
- when "boolean"
727
- example << " #{prop.to_sym}: true, "
728
- when "array"
729
- if val.key?(:items) and val[:items].size==1 and val[:items].is_a?(Hash) and val[:items].key?(:type)
730
- val[:items][:enum]=[val[:items][:type]]
731
- end
732
-
733
- if val.key?(:items) and val[:items].key?(:enum)
734
- #before we were getting in all these cases a random value from the enum, now we are getting the first position by default
735
- #the reason is to avoid confusion later in case we want to compare two swaggers and verify the changes
736
- if type==:only_value
737
- if val[:items][:enum][0].is_a?(String)
738
- example << " [\"" + val[:items][:enum][0] + "\"] "
739
- else
740
- example << " [" + val[:items][:enum][0] + "] "
741
- end
742
- else
743
- if val[:items][:enum][0].is_a?(String)
744
- example << " #{prop.to_sym}: [\"" + val[:items][:enum][0] + "\"], "
745
- else
746
- example << " #{prop.to_sym}: [" + val[:items][:enum][0] + "], "
747
- end
748
- end
749
- else
750
- #todo: differ between response examples and data examples
751
- if type == :only_value
752
- example << get_response_examples({schema: val}, remove_readonly).join("\n")
753
- else
754
- example << " #{prop.to_sym}: " + get_response_examples({schema: val}, remove_readonly).join("\n") + ", "
755
- end
756
- end
757
- when "object"
758
- #todo: differ between response examples and data examples
759
- res_ex = get_response_examples({schema: val}, remove_readonly)
760
- if res_ex.size == 0
761
- res_ex = "{ }"
762
- else
763
- res_ex = res_ex.join("\n")
764
- end
765
- example << " #{prop.to_sym}: " + res_ex + ", "
766
- else
767
- example << " #{prop.to_sym}: \"#{format}\", "
768
- end
769
- end
770
- end
771
- end
772
- example << "}" unless properties.empty? or type==:only_value
773
- example
774
- end
775
-
776
- # Retrieve the response examples from the hash
777
- private def get_response_examples(v, remove_readonly = false)
778
- # TODO: take in consideration the case allOf, oneOf... schema.items.allOf[0].properties schema.items.allOf[1].properties
779
- # example on https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v2.0/yaml/petstore-expanded.yaml
780
- v=v.dup
781
- response_example = Array.new()
782
- # for open api 3.0 with responses schema inside content
783
- if v.key?(:content) && v[:content].is_a?(Hash) && v[:content].key?(:'application/json') &&
784
- v[:content][:'application/json'].key?(:schema)
785
- v=v[:content][:'application/json'].dup
786
- end
787
- if v.key?(:examples) && v[:examples].is_a?(Hash) && v[:examples].key?(:'application/json')
788
- if v[:examples][:'application/json'].is_a?(String)
789
- response_example << v[:examples][:'application/json']
790
- elsif v[:examples][:'application/json'].is_a?(Hash)
791
- exs = v[:examples][:'application/json'].to_s
792
- exs.gsub!(/:(\w+)=>/, "\n\\1: ")
793
- response_example << exs
794
- elsif v[:examples][:'application/json'].is_a?(Array)
795
- response_example << "["
796
- v[:examples][:'application/json'].each do |ex|
797
- exs = ex.to_s
798
- if ex.is_a?(Hash)
799
- exs.gsub!(/:(\w+)=>/, "\n\\1: ")
800
- end
801
- response_example << (exs + ", ")
802
- end
803
- response_example << "]"
804
- end
805
- # for open api 3.0. examples on reponses, for example: api-with-examples.yaml
806
- elsif v.key?(:content) && v[:content].is_a?(Hash) && v[:content].key?(:'application/json') &&
807
- v[:content][:'application/json'].key?(:examples)
808
- v[:content][:'application/json'][:examples].each do |tk, tv|
809
- #todo: for the moment we only take in consideration the first example of response.
810
- # we need to decide how to manage to do it correctly
811
- if tv.key?(:value)
812
- tresp = tv[:value]
813
- else
814
- tresp = ""
815
- end
816
- if tresp.is_a?(String)
817
- response_example << tresp
818
- elsif tresp.is_a?(Hash)
819
- exs = tresp.to_s
820
- exs.gsub!(/:(\w+)=>/, "\n\\1: ")
821
- response_example << exs
822
- elsif tresp.is_a?(Array)
823
- response_example << "["
824
- tresp.each do |ex|
825
- exs = ex.to_s
826
- if ex.is_a?(Hash)
827
- exs.gsub!(/:(\w+)=>/, "\n\\1: ")
828
- end
829
- response_example << (exs + ", ")
830
- end
831
- response_example << "]"
832
- end
833
- break #only the first one it is considered
834
- end
835
- elsif v.key?(:schema) && v[:schema].is_a?(Hash) &&
836
- (v[:schema].key?(:properties) ||
837
- (v[:schema].key?(:items) && v[:schema][:items].key?(:properties)) ||
838
- (v[:schema].key?(:items) && v[:schema][:items].key?(:allOf)) ||
839
- v[:schema].key?(:allOf))
840
- properties = {}
841
- if v[:schema].key?(:properties)
842
- properties = v[:schema][:properties]
843
- elsif v[:schema].key?(:allOf)
844
- v[:schema][:allOf].each do |pr|
845
- properties.merge!(pr[:properties]) if pr.key?(:properties)
846
- end
847
- elsif v[:schema][:items].key?(:properties)
848
- properties = v[:schema][:items][:properties]
849
- response_example << "["
850
- elsif v[:schema][:items].key?(:allOf)
851
- v[:schema][:items][:allOf].each do |pr|
852
- properties.merge!(pr[:properties]) if pr.key?(:properties)
853
- end
854
- response_example << "["
855
- end
856
-
857
- response_example += get_examples(properties, :key_value, remove_readonly) unless properties.empty?
858
-
859
- unless response_example.empty?
860
- if v[:schema].key?(:properties) || v[:schema].key?(:allOf)
861
- #
862
- else # array, items
863
- response_example << "]"
864
- end
865
- end
866
-
867
- elsif v.key?(:schema) and v[:schema].key?(:items) and v[:schema][:items].key?(:type)
868
- # for the case only type supplied but nothing else for the array
869
- response_example << "[\"#{v[:schema][:items][:type]}\"]"
870
- end
871
- response_example.each do |rs|
872
- #(@type Google) for the case in example the key is something like: @type:
873
- if rs.match?(/^\s*@\w+:/)
874
- rs.gsub!(/@(\w+):/,'\'@\1\':')
875
- end
876
- end
877
- return response_example
878
- end
879
-
880
-
881
- private def get_data_all_of_bodies(p)
882
- bodies = []
883
- data_examples_all_of = false
884
- if p.is_a?(Array)
885
- q = p
886
- elsif p.key?(:schema) and p[:schema].key?(:allOf)
887
- q = p[:schema][:allOf]
888
- else
889
- q =[p]
890
- end
891
- q.each do |pt|
892
- if pt.is_a?(Hash) and pt.key?(:allOf)
893
- #bodies += pt[:allOf]
894
- bodies += get_data_all_of_bodies(pt[:allOf])[1]
895
- data_examples_all_of = true
896
- else
897
- bodies << pt
898
- end
899
- end
900
- return data_examples_all_of, bodies
901
- end
902
-
903
- # Get required data
904
- private def get_required_data(body)
905
- data_required = []
906
- if body.keys.include?(:required) and body[:required].size > 0
907
- body[:required].each do |r|
908
- data_required << r.to_sym
909
- end
910
- end
911
- data_required.each do |key|
912
- if body.key?(:properties) and body[:properties][key].is_a?(Hash) and
913
- body[:properties][key].key?(:required) and body[:properties][key][:required].size>0
914
- dr = get_required_data(body[:properties][key])
915
- dr.each do |k|
916
- data_required.push("#{key}.#{k}".to_sym)
917
- end
918
- end
919
- end
920
- return data_required
921
- end
922
-
923
- # Get patterns
924
- private def get_patterns(dpk, dpv)
925
- data_pattern = []
926
- if dpv.keys.include?(:pattern)
927
- #todo: control better the cases with back slashes
928
- if dpv[:pattern].include?('\\\\/')
929
- #for cases like this: ^[^\.\\/:*?"<>|][^\\/:*?"<>|]{0,13}[^\.\\/:*?"<>|]?$
930
- data_pattern << "'#{dpk}': /#{dpv[:pattern].to_s.gsub('\/','/')}/"
931
- elsif dpv[:pattern].include?('\\x')
932
- data_pattern << "'#{dpk}': /#{dpv[:pattern].to_s.gsub('\\x','\\u')}/"
933
- else
934
- data_pattern << "'#{dpk}': /#{dpv[:pattern].to_s}/"
935
- end
936
- elsif dpv.key?(:minLength) and dpv.key?(:maxLength)
937
- data_pattern << "'#{dpk}': :'#{dpv[:minLength]}-#{dpv[:maxLength]}:LN$'"
938
- elsif dpv.key?(:minLength) and !dpv.key?(:maxLength)
939
- data_pattern << "'#{dpk}': :'#{dpv[:minLength]}:LN$'"
940
- elsif !dpv.key?(:minLength) and dpv.key?(:maxLength)
941
- data_pattern << "'#{dpk}': :'0-#{dpv[:maxLength]}:LN$'"
942
- elsif dpv.key?(:minimum) and dpv.key?(:maximum) and dpv[:type]=='string'
943
- data_pattern << "'#{dpk}': :'#{dpv[:minimum]}-#{dpv[:maximum]}:LN$'"
944
- elsif dpv.key?(:minimum) and dpv.key?(:maximum)
945
- data_pattern << "'#{dpk}': #{dpv[:minimum]}..#{dpv[:maximum]}"
946
- elsif dpv.key?(:minimum) and !dpv.key?(:maximum)
947
- if RUBY_VERSION >= '2.6.0'
948
- data_pattern << "'#{dpk}': #{dpv[:minimum]}.. "
949
- else
950
- data_pattern << "#'#{dpk}': #{dpv[:minimum]}.. # INFINITE only working on ruby>=2.6.0"
951
- end
952
- elsif !dpv.key?(:minimum) and dpv.key?(:maximum)
953
- data_pattern << "'#{dpk}': 0..#{dpv[:maximum]}"
954
- elsif dpv[:format] == 'date-time'
955
- data_pattern << "'#{dpk}': DateTime"
956
- elsif dpv[:type] == 'boolean'
957
- data_pattern << "'#{dpk}': Boolean"
958
- elsif dpv.key?(:enum)
959
- data_pattern << "'#{dpk}': :'#{dpv[:enum].join('|')}'"
960
- elsif dpv[:type] == 'array' and dpv.key?(:items) and dpv[:items].is_a?(Hash) and dpv[:items].key?(:enum) and dpv[:items][:enum].is_a?(Array)
961
- #{:title=>"Balala", :type=>"array", :items=>{:type=>"string", :enum=>["uno","dos"], :example=>"uno"}}
962
- data_pattern << "'#{dpk}': [:'#{dpv[:items][:enum].join('|')}']"
963
- elsif dpv[:type] == 'array' and dpv.key?(:items) and dpv[:items].is_a?(Hash) and !dpv[:items].key?(:enum) and dpv[:items].key?(:properties)
964
- #{:title=>"Balala", :type=>"array", :items=>{title: 'xxxx, properties: {server: {enum:['ibm','msa','pytan']}}}
965
- dpv[:items][:properties].each do |dpkk,dpvv|
966
- if dpk == ''
967
- data_pattern += get_patterns("#{dpkk}",dpvv)
968
- else
969
- data_pattern += get_patterns("#{dpk}.#{dpkk}",dpvv)
970
- end
971
- end
972
- elsif dpv[:type] == 'array' and dpv.key?(:items) and dpv[:items].is_a?(Hash) and
973
- !dpv[:items].key?(:enum) and !dpv[:items].key?(:properties) and dpv[:items].key?(:type)
974
- #{:title=>"labels", :description=>"Labels specified for the file system", :type=>"array", :items=>{:type=>"string", :enum=>["string"]}}
975
- data_pattern << "'#{dpk}': [ #{get_patterns('', dpv[:items]).join[4..-1]} ]"
976
- elsif dpv[:type] == 'object' and dpv.key?(:properties)
977
- dpv[:properties].each do |dpkk,dpvv|
978
- if dpk == ''
979
- data_pattern += get_patterns("#{dpkk}",dpvv)
980
- else
981
- data_pattern += get_patterns("#{dpk}.#{dpkk}",dpvv)
982
- end
983
- end
984
- end
985
- data_pattern.uniq!
986
- return data_pattern
987
-
988
- end
989
-
990
- #filter hash
991
- def filter(hash, keys, nested = false)
992
- result = {}
993
- keys = [keys] unless keys.is_a?(Array)
994
- if nested
995
- result = hash.nice_filter(keys)
996
- else
997
- #to be backwards compatible
998
- keys.each do |k|
999
- if k.is_a?(Symbol) and hash.key?(k)
1000
- if hash[k].is_a?(Hash)
1001
- result[k] = {}
1002
- else
1003
- result[k] = hash[k]
1004
- end
1005
- elsif k.is_a?(Symbol) and k.to_s.include?('.') and hash.key?((k.to_s.scan(/(\w+)\./).join).to_sym) #nested 'uno.dos.tres
1006
- kn = k.to_s.split('.')
1007
- vn = kn[1].to_sym
1008
- result[kn.first.to_sym][vn] = filter(hash[kn.first.to_sym], vn).values[0]
1009
- elsif k.is_a?(Hash) and hash.key?(k.keys[0]) #nested {uno: {dos: :tres}}
1010
- result[k.keys[0]][k.values[0]] = filter(hash[k.keys[0]], k.values[0]).values[0]
1011
- end
1012
- end
1013
- end
1014
- return result
1015
- end
1016
-
1017
- #gen pretty hash symbolized
1018
- private def pretty_hash_symbolized(hash)
1019
- output = []
1020
- output << "{"
1021
- hash.each do |kr,kv|
1022
- if kv.kind_of?(Hash)
1023
- restv = pretty_hash_symbolized(kv)
1024
- restv[0] = "#{kr}: {"
1025
- output += restv
1026
- else
1027
- output << "#{kr}: #{kv.inspect}, "
1028
- end
1029
- end
1030
- output << "},"
1031
- return output
1032
- end
1033
- end
1034
- end