open_api_import 0.10.9 → 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,1031 +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
-
242
- if !response_example.empty?
243
- responses << "'#{k}': { "
244
- responses << "message: '#{v[:description]}', "
245
- responses << "data: "
246
- responses << response_example
247
- responses << "},"
248
-
249
- if mock_response and mock_example.size==0
250
- mock_example << "code: '#{k}',"
251
- mock_example << "message: '#{v[:description]}',"
252
- mock_example << "data: "
253
- mock_example << response_example
254
- end
255
-
256
- else
257
- responses << "'#{k}': { message: '#{v[:description]}'}, "
258
- end
259
-
260
- end
261
- end
262
- # todo: for open api 3.0 add the new Link feature: https://swagger.io/docs/specification/links/
263
- # todo: for open api 3.0 is not getting the required params in all cases
264
-
265
- # for the case open api 3 with cont.requestBody.content.'applicatin/json'.schema
266
- # example: petstore-expanded.yaml operationId=addPet
267
- if cont.key?(:requestBody) and cont[:requestBody].key?(:content) and
268
- cont[:requestBody][:content].key?(:'application/json') and cont[:requestBody][:content][:'application/json'].key?(:schema)
269
- cont[:parameters] = [] unless cont.key?(:parameters)
270
- cont[:parameters] << {in: 'body', schema: cont[:requestBody][:content][:'application/json'][:schema] }
271
- end
272
- data_examples_all_of = false
273
- if cont.key?(:parameters) && cont[:parameters].is_a?(Array)
274
- cont[:parameters].each do |p|
275
- if p.keys.include?(:schema) and p[:schema].include?(:type)
276
- type = p[:schema][:type]
277
- elsif p.keys.include?(:type)
278
- type = p[:type]
279
- else
280
- type = ""
281
- end
282
- if p[:in] == "path"
283
- if create_method_name == :operationId
284
- param_name = p[:name]
285
- path_txt.gsub!("{#{param_name}}", "\#{#{param_name}}")
286
- else
287
- param_name = p[:name].to_s.snake_case
288
- path_txt.gsub!("{#{p[:name]}}", "\#{#{param_name}}")
289
- end
290
- unless params_path.include?(param_name)
291
- if create_constants
292
- params_path << "#{param_name}: #{param_name.upcase}"
293
- required_constants << param_name.upcase
294
- else
295
- params_path << param_name
296
- end
297
- #params_required << param_name if p[:required].to_s=="true"
298
- description_parameters << "# #{p[:name]}: (#{type}) #{"(required)" if p[:required].to_s=="true"} #{p[:description]}"
299
- end
300
- elsif p[:in] == "query"
301
- params_query << p[:name]
302
- params_required << p[:name] if p[:required].to_s=="true"
303
- description_parameters << "# #{p[:name]}: (#{type}) #{"(required)" if p[:required].to_s=="true"} #{p[:description]}"
304
- elsif p[:in] == "formData" or p[:in] == "formdata"
305
- #todo: take in consideration: default, required
306
- #todo: see if we should add the required as params to the method and not required as options
307
- #todo: set on data the required fields with the values from args
308
-
309
- description_parameters << "# #{p[:name]}: (#{p[:type]}) #{p[:description]}"
310
- case p[:type]
311
- when /^string$/i
312
- data_form << "#{p[:name]}: ''"
313
- when /^boolean$/i
314
- data_form << "#{p[:name]}: true"
315
- when /^number$/i
316
- data_form << "#{p[:name]}: 0"
317
- when /^integer$/i
318
- data_form << "#{p[:name]}: 0"
319
- else
320
- puts "! on formData not supported type #{p[:type]}"
321
- end
322
-
323
- elsif p[:in] == "body"
324
- if p.keys.include?(:schema)
325
- if p[:schema].key?(:oneOf)
326
- bodies = p[:schema][:oneOf]
327
- elsif p[:schema].key?(:anyOf)
328
- bodies = p[:schema][:anyOf]
329
- elsif p[:schema].key?(:allOf)
330
- data_examples_all_of, bodies = get_data_all_of_bodies(p)
331
- data_examples_all_of = true # because we are on data and allOf already
332
- else
333
- bodies = [p[:schema]]
334
- end
335
-
336
- params_data = []
337
-
338
- bodies.each do |body|
339
- if body.keys.include?(:required) and body[:required].size > 0
340
- data_required += get_required_data(body)
341
- output << "# required data: #{data_required.inspect}"
342
- end
343
-
344
- if body.keys.include?(:properties) and body[:properties].size > 0
345
-
346
- body[:properties].each { |dpk, dpv|
347
- if dpv.keys.include?(:example)
348
- if dpv[:example].is_a?(Array) and dpv.type != 'array'
349
- valv = dpv[:example][0]
350
- else
351
- valv = dpv[:example].to_s
352
- end
353
- else
354
- if dpv.type == "object"
355
- if dpv.key?(:properties)
356
- valv = get_examples(dpv[:properties], :key_value, true).join("\n")
357
- else
358
- valv = "{}"
359
- end
360
- elsif dpv.type == 'array'
361
- if dpv.key?(:items)
362
- valv = get_examples({dpk => dpv}, :only_value)
363
- valv = valv.join("\n")
364
- else
365
- valv = "[]"
366
- end
367
- else
368
- valv = ""
369
- end
370
- end
371
- if dpv.keys.include?(:description)
372
- description_parameters << "# #{dpk}: (#{dpv[:type]}) #{dpv[:description].split("\n").join("\n#\t\t\t")}"
373
- end
374
-
375
- data_pattern += get_patterns(dpk,dpv)
376
- data_pattern.uniq!
377
- dpkeys = []
378
- data_pattern.reject! do |dp|
379
- dpkey = dp.scan(/^'[\w\.]+'/)
380
-
381
- if dpkeys.include?(dpkey)
382
- true
383
- else
384
- dpkeys << dpkey
385
- false
386
- end
387
- end
388
-
389
- if dpv.keys.include?(:readOnly) and dpv[:readOnly] == true
390
- data_read_only << dpk
391
- end
392
- if dpv.keys.include?(:default)
393
- if dpv[:default].nil?
394
- data_default << "#{dpk}: nil"
395
- elsif dpv.type != "string"
396
- data_default << "#{dpk}: #{dpv[:default]}"
397
- else
398
- data_default << "#{dpk}: '#{dpv[:default]}'"
399
- end
400
- end
401
-
402
- #todo: consider check default and insert it
403
- #todo: remove array from here and add the option to get_examples for the case thisisthekey: ['xxxx']
404
- if dpv.key?(:type) and dpv[:type]!='array'
405
- params_data << get_examples({dpk => dpv}, :only_value, true).join
406
- params_data[-1].chop!.chop! if params_data[-1].to_s[-2..-1]==', '
407
- params_data.pop if params_data[-1].match?(/^\s*$/im)
408
- else
409
- if valv.to_s == ""
410
- valv = '"' + valv + '"'
411
- end
412
- params_data << "#{dpk}: #{valv}"
413
- end
414
- }
415
- if params_data.size > 0
416
- if data_examples_all_of == true and data_examples.size > 0
417
- data_examples[0]+=params_data
418
- else
419
- data_examples << params_data
420
- end
421
- params_data = []
422
- end
423
- end
424
- end
425
- end
426
- elsif p[:in]=="header"
427
- #todo: see how we can treat those cases
428
- else
429
- puts "! not imported data with :in:#{p[:in]} => #{p.inspect}"
430
- end
431
- end
432
-
433
- params = params_path
434
-
435
- unless params_query.empty?
436
- path_txt += "?"
437
- params_required.each do |pr|
438
- if create_constants
439
- if params_query.include?(pr)
440
- if create_method_name == :operationId
441
- path_txt += "#{pr}=\#{#{pr}}&"
442
- params << "#{pr}: #{pr.upcase}"
443
- required_constants << pr.upcase
444
- else
445
- path_txt += "#{pr}=\#{#{pr.to_s.snake_case}}&"
446
- params << "#{pr.to_s.snake_case}: #{pr.to_s.snake_case.upcase}"
447
- required_constants << pr.to_s.snake_case.upcase
448
- end
449
- end
450
- else
451
- if params_query.include?(pr)
452
- if create_method_name == :operationId
453
- path_txt += "#{pr}=\#{#{pr}}&"
454
- params << "#{pr}"
455
- else
456
- path_txt += "#{pr}=\#{#{pr.to_s.snake_case}}&"
457
- params << "#{pr.to_s.snake_case}"
458
- end
459
- end
460
- end
461
- end
462
- params_query.each do |pq|
463
- unless params_required.include?(pq)
464
- if create_method_name == :operationId
465
- path_txt += "#{pq}=\#{#{pq}}&"
466
- params << "#{pq}: ''"
467
- else
468
- path_txt += "#{pq}=\#{#{pq.to_s.snake_case}}&"
469
- params << "#{pq.to_s.snake_case}: ''"
470
- end
471
- end
472
- end
473
- end
474
-
475
- end
476
-
477
- if description_parameters.size > 0
478
- output << "# parameters description: "
479
- output << description_parameters
480
- end
481
-
482
- #for the case we still have some parameters on path that were not in 'parameters'
483
- if path_txt.scan(/[^#]{\w+}/).size > 0
484
- paramst = []
485
- prms = path_txt.scan(/[^#]{(\w+)}/)
486
- prms.each do |p|
487
- #if create_constants
488
- # paramst<<"#{p[0].to_s.snake_case}: #{p[0].to_s.snake_case.upcase}"
489
- # required_constants << p[0].to_s.snake_case.upcase
490
- #else
491
- paramst<<p[0].to_s.snake_case
492
- #end
493
- path_txt.gsub!("{#{p[0]}}", "\#{#{p[0].to_s.snake_case}}")
494
- end
495
- paramst.concat params
496
- params = paramst
497
- end
498
- params.uniq!
499
- output << "def self.#{method_name} (#{params.join(", ")})"
500
-
501
- output << "{"
502
-
503
- output << "name: \"#{module_requests}.#{method_name}\","
504
-
505
- output << "path: \"#{base_path}#{path_txt}\","
506
-
507
- output << "method: :#{met}," if met.to_s != ""
508
-
509
- unless data_required.empty?
510
- output << "data_required: ["
511
- output << ":'#{data_required.uniq.join("', :'")}'"
512
- output << "],"
513
- end
514
- unless data_read_only.empty?
515
- output << "data_read_only: ["
516
- output << ":'#{data_read_only.uniq.join("', :'")}'"
517
- output << "],"
518
- end
519
- unless data_default.empty?
520
- output << "data_default: {"
521
- output << data_default.join(", \n")
522
- output << "},"
523
- end
524
-
525
- unless data_pattern.empty?
526
- output << "data_pattern: {"
527
- output << data_pattern.uniq.join(", \n")
528
- output << "},"
529
- end
530
-
531
- unless data_form.empty?
532
- data_examples << data_form
533
- end
534
-
535
- unless data_examples.empty?
536
- unless data_required.empty?
537
- reqdata = []
538
- begin
539
- data_ex = eval("{#{data_examples[0].join(", ")}}")
540
- rescue
541
- data_ex = {}
542
- end
543
- if (data_required.grep(/\./)).empty?
544
- reqdata = filter(data_ex, data_required) #not nested
545
- else
546
- reqdata = filter(data_ex, data_required, true) #nested
547
- end
548
- unless reqdata.empty?
549
- phsd = pretty_hash_symbolized(reqdata)
550
- phsd[0]="data: {"
551
- output += phsd
552
- end
553
- end
554
- unless data_read_only.empty? or !data_required.empty?
555
- reqdata = []
556
- #remove read only fields from :data
557
- data_examples[0].each do |edata|
558
- read_only = false
559
- data_read_only.each do |rdata|
560
- if edata.scan(/^#{rdata}:/).size>0
561
- read_only = true
562
- break
563
- elsif edata.scan(/:/).size==0
564
- break
565
- end
566
- end
567
- reqdata << edata unless read_only
568
- end
569
- unless reqdata.empty?
570
- output << "data: {"
571
- output << reqdata.join(", \n")
572
- output << "},"
573
- end
574
- end
575
-
576
- output << "data_examples: ["
577
- data_examples.each do |data|
578
- output << "{"
579
- output << data.join(", \n")
580
- output << "}, "
581
- end
582
- output << "],"
583
- end
584
-
585
- unless mock_example.empty?
586
- output << "mock_response: {"
587
- output << mock_example
588
- output << "},"
589
- end
590
-
591
- unless responses.empty?
592
- output << "responses: {"
593
- output << responses
594
- output << "},"
595
- end
596
-
597
- output << "}"
598
- output << "end"
599
- else
600
- @logger.warn "Not imported method: #{met} for path: #{path.path} since it is not supported by OpenApiImport"
601
- end
602
- end
603
- end
604
- output_footer = []
605
-
606
- output_footer << "end" unless (module_requests == "") && ([:path, :path_file, :tags, :tags_file].include?(name_for_module))
607
- output_footer << "end" << "end" << "end"
608
-
609
- if files.size == 0
610
- output = output_header + output + output_footer
611
- output_txt = output.join("\n")
612
- requests_file_path = file_to_convert + ".rb"
613
- File.open(requests_file_path, "w") { |file| file.write(output_txt) }
614
- res_rufo = `rufo #{requests_file_path}`
615
- message = "** Requests file: #{swagger_file}.rb that contains the code of the requests after importing the Swagger file"
616
- puts message unless silent
617
- @logger.info message
618
- @logger.error " Error formating with rufo" unless res_rufo.to_s.match?(/\AFormat:.+$\s*\z/)
619
- @logger.error " Syntax Error: #{`ruby -c #{requests_file_path}`}" unless `ruby -c #{requests_file_path}`.include?("Syntax OK")
620
- else
621
- unless files.key?(module_requests)
622
- files[module_requests] = Array.new
623
- end
624
- files[module_requests].concat(output) #for the last one
625
-
626
- requires_txt = ""
627
- message = "** Generated files that contain the code of the requests after importing the Swagger file: "
628
- puts message unless silent
629
- @logger.info message
630
- files.each do |mod, out_mod|
631
- output = output_header + out_mod + output_footer
632
- output_txt = output.join("\n")
633
- requests_file_path = file_to_convert + "_" + mod + ".rb"
634
- requires_txt += "require_relative '#{File.basename(swagger_file)}_#{mod}'\n"
635
- File.open(requests_file_path, "w") { |file| file.write(output_txt) }
636
- res_rufo = `rufo #{requests_file_path}`
637
- message = " - #{requests_file_path}"
638
- puts message unless silent
639
- @logger.info message
640
- @logger.error " Error formating with rufo" unless res_rufo.to_s.match?(/\AFormat:.+$\s*\z/)
641
- @logger.error " Syntax Error: #{`ruby -c #{requests_file_path}`}" unless `ruby -c #{requests_file_path}`.include?("Syntax OK")
642
- end
643
-
644
- requests_file_path = file_to_convert + ".rb"
645
- if required_constants.size > 0
646
- rconsts = "# Required constants\n"
647
- required_constants.uniq!
648
- required_constants.each do |rq|
649
- rconsts += "#{rq} ||= ENV['#{rq}'] ||=''\n"
650
- end
651
- rconsts += "\n\n"
652
- else
653
- rconsts = ''
654
- end
655
-
656
- File.open(requests_file_path, "w") { |file| file.write(rconsts + requires_txt) }
657
- res_rufo = `rufo #{requests_file_path}`
658
- message = "** File that contains all the requires for all Request files: \n"
659
- message += " - #{requests_file_path} "
660
- puts message unless silent
661
- @logger.info message
662
- @logger.error " Error formating with rufo" unless res_rufo.to_s.match?(/\AFormat:.+$\s*\z/)
663
- @logger.error " Syntax Error: #{`ruby -c #{requests_file_path}`}" unless `ruby -c #{requests_file_path}`.include?("Syntax OK")
664
- end
665
-
666
- begin
667
- res = eval(output_txt)
668
- rescue Exception => stack
669
- import_errors += "\n\nResult evaluating the ruby file generated: \n" + stack.to_s
670
- end
671
-
672
- if import_errors.to_s != ""
673
- File.open(file_errors, "w") { |file| file.write(import_errors) }
674
- message = "* It seems there was a problem importing the Swagger file #{file_to_convert}\n"
675
- message += "* Take a look at the detected errors at #{file_errors}\n"
676
- warn message
677
- @logger.fatal message
678
- return false
679
- else
680
- return true
681
- end
682
- rescue StandardError => stack
683
- puts stack.message
684
- @logger.fatal stack.message
685
- @logger.fatal stack.backtrace
686
- puts stack.backtrace
687
- end
688
- end
689
-
690
- class << self
691
- # Retrieve the examples from the properties hash
692
- private def get_examples(properties, type=:key_value, remove_readonly=false)
693
- #todo: consider using this method also to get data examples
694
- example = []
695
- example << "{" unless properties.empty? or type==:only_value
696
- properties.each do |prop, val|
697
- unless remove_readonly and val.key?(:readOnly) and val[:readOnly]==true
698
- if val.key?(:properties) and !val.key?(:example) and !val.key?(:type)
699
- val[:type]='object'
700
- end
701
- if val.key?(:items) and !val.key?(:example) and !val.key?(:type)
702
- val[:type]='array'
703
- end
704
- if val.key?(:example)
705
- if val[:example].is_a?(Array) and val.key?(:type) and val[:type]=='string'
706
- example << " #{prop.to_sym}: \"#{val[:example][0]}\", " # only the first example
707
- else
708
- example << if val[:example].is_a?(String) or val[:example].is_a?(Time)
709
- " #{prop.to_sym}: \"#{val[:example]}\", "
710
- else
711
- " #{prop.to_sym}: #{val[:example]}, "
712
- end
713
- end
714
- elsif val.key?(:type)
715
- format = val[:format]
716
- format = val[:type] if format.to_s == ""
717
- case val[:type].downcase
718
- when "string"
719
- example << " #{prop.to_sym}: \"#{format}\", "
720
- when "integer", "number"
721
- example << " #{prop.to_sym}: 0, "
722
- when "boolean"
723
- example << " #{prop.to_sym}: true, "
724
- when "array"
725
- if val.key?(:items) and val[:items].size==1 and val[:items].is_a?(Hash) and val[:items].key?(:type)
726
- val[:items][:enum]=[val[:items][:type]]
727
- end
728
-
729
- if val.key?(:items) and val[:items].key?(:enum)
730
- #before we were getting in all these cases a random value from the enum, now we are getting the first position by default
731
- #the reason is to avoid confusion later in case we want to compare two swaggers and verify the changes
732
- if type==:only_value
733
- if val[:items][:enum][0].is_a?(String)
734
- example << " [\"" + val[:items][:enum][0] + "\"] "
735
- else
736
- example << " [" + val[:items][:enum][0] + "] "
737
- end
738
- else
739
- if val[:items][:enum][0].is_a?(String)
740
- example << " #{prop.to_sym}: [\"" + val[:items][:enum][0] + "\"], "
741
- else
742
- example << " #{prop.to_sym}: [" + val[:items][:enum][0] + "], "
743
- end
744
- end
745
- else
746
- #todo: differ between response examples and data examples
747
- if type == :only_value
748
- example << get_response_examples({schema: val}, remove_readonly).join("\n")
749
- else
750
- example << " #{prop.to_sym}: " + get_response_examples({schema: val}, remove_readonly).join("\n") + ", "
751
- end
752
- end
753
- when "object"
754
- #todo: differ between response examples and data examples
755
- res_ex = get_response_examples({schema: val}, remove_readonly)
756
- if res_ex.size == 0
757
- res_ex = "{ }"
758
- else
759
- res_ex = res_ex.join("\n")
760
- end
761
- example << " #{prop.to_sym}: " + res_ex + ", "
762
- else
763
- example << " #{prop.to_sym}: \"#{format}\", "
764
- end
765
- end
766
- end
767
- end
768
- example << "}" unless properties.empty? or type==:only_value
769
- example
770
- end
771
-
772
- # Retrieve the response examples from the hash
773
- private def get_response_examples(v, remove_readonly = false)
774
- # TODO: take in consideration the case allOf, oneOf... schema.items.allOf[0].properties schema.items.allOf[1].properties
775
- # example on https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v2.0/yaml/petstore-expanded.yaml
776
- v=v.dup
777
- response_example = Array.new()
778
- # for open api 3.0 with responses schema inside content
779
- if v.key?(:content) && v[:content].is_a?(Hash) && v[:content].key?(:'application/json') &&
780
- v[:content][:'application/json'].key?(:schema)
781
- v=v[:content][:'application/json'].dup
782
- end
783
- if v.key?(:examples) && v[:examples].is_a?(Hash) && v[:examples].key?(:'application/json')
784
- if v[:examples][:'application/json'].is_a?(String)
785
- response_example << v[:examples][:'application/json']
786
- elsif v[:examples][:'application/json'].is_a?(Hash)
787
- exs = v[:examples][:'application/json'].to_s
788
- exs.gsub!(/:(\w+)=>/, "\n\\1: ")
789
- response_example << exs
790
- elsif v[:examples][:'application/json'].is_a?(Array)
791
- response_example << "["
792
- v[:examples][:'application/json'].each do |ex|
793
- exs = ex.to_s
794
- if ex.is_a?(Hash)
795
- exs.gsub!(/:(\w+)=>/, "\n\\1: ")
796
- end
797
- response_example << (exs + ", ")
798
- end
799
- response_example << "]"
800
- end
801
- # for open api 3.0. examples on reponses, for example: api-with-examples.yaml
802
- elsif v.key?(:content) && v[:content].is_a?(Hash) && v[:content].key?(:'application/json') &&
803
- v[:content][:'application/json'].key?(:examples)
804
- v[:content][:'application/json'][:examples].each do |tk, tv|
805
- #todo: for the moment we only take in consideration the first example of response.
806
- # we need to decide how to manage to do it correctly
807
- if tv.key?(:value)
808
- tresp = tv[:value]
809
- else
810
- tresp = ""
811
- end
812
-
813
- if tresp.is_a?(String)
814
- response_example << tresp
815
- elsif tresp.is_a?(Hash)
816
- exs = tresp.to_s
817
- exs.gsub!(/:(\w+)=>/, "\n\\1: ")
818
- response_example << exs
819
- elsif tresp.is_a?(Array)
820
- response_example << "["
821
- tresp.each do |ex|
822
- exs = ex.to_s
823
- if ex.is_a?(Hash)
824
- exs.gsub!(/:(\w+)=>/, "\n\\1: ")
825
- end
826
- response_example << (exs + ", ")
827
- end
828
- response_example << "]"
829
- end
830
- break #only the first one it is considered
831
- end
832
- elsif v.key?(:schema) && v[:schema].is_a?(Hash) &&
833
- (v[:schema].key?(:properties) ||
834
- (v[:schema].key?(:items) && v[:schema][:items].key?(:properties)) ||
835
- (v[:schema].key?(:items) && v[:schema][:items].key?(:allOf)) ||
836
- v[:schema].key?(:allOf))
837
- properties = {}
838
- if v[:schema].key?(:properties)
839
- properties = v[:schema][:properties]
840
- elsif v[:schema].key?(:allOf)
841
- v[:schema][:allOf].each do |pr|
842
- properties.merge!(pr[:properties]) if pr.key?(:properties)
843
- end
844
- elsif v[:schema][:items].key?(:properties)
845
- properties = v[:schema][:items][:properties]
846
- response_example << "["
847
- elsif v[:schema][:items].key?(:allOf)
848
- v[:schema][:items][:allOf].each do |pr|
849
- properties.merge!(pr[:properties]) if pr.key?(:properties)
850
- end
851
- response_example << "["
852
- end
853
-
854
- response_example += get_examples(properties, :key_value, remove_readonly) unless properties.empty?
855
-
856
- unless response_example.empty?
857
- if v[:schema].key?(:properties) || v[:schema].key?(:allOf)
858
- #
859
- else # array, items
860
- response_example << "]"
861
- end
862
- end
863
-
864
- elsif v.key?(:schema) and v[:schema].key?(:items) and v[:schema][:items].key?(:type)
865
- # for the case only type supplied but nothing else for the array
866
- response_example << "[\"#{v[:schema][:items][:type]}\"]"
867
- end
868
- response_example.each do |rs|
869
- #(@type Google) for the case in example the key is something like: @type:
870
- if rs.match?(/^\s*@\w+:/)
871
- rs.gsub!(/@(\w+):/,'\'@\1\':')
872
- end
873
- end
874
- return response_example
875
- end
876
-
877
-
878
- private def get_data_all_of_bodies(p)
879
- bodies = []
880
- data_examples_all_of = false
881
- if p.is_a?(Array)
882
- q = p
883
- elsif p.key?(:schema) and p[:schema].key?(:allOf)
884
- q = p[:schema][:allOf]
885
- else
886
- q =[p]
887
- end
888
- q.each do |pt|
889
- if pt.is_a?(Hash) and pt.key?(:allOf)
890
- #bodies += pt[:allOf]
891
- bodies += get_data_all_of_bodies(pt[:allOf])[1]
892
- data_examples_all_of = true
893
- else
894
- bodies << pt
895
- end
896
- end
897
- return data_examples_all_of, bodies
898
- end
899
-
900
- # Get required data
901
- private def get_required_data(body)
902
- data_required = []
903
- if body.keys.include?(:required) and body[:required].size > 0
904
- body[:required].each do |r|
905
- data_required << r.to_sym
906
- end
907
- end
908
- data_required.each do |key|
909
- if body.key?(:properties) and body[:properties][key].is_a?(Hash) and
910
- body[:properties][key].key?(:required) and body[:properties][key][:required].size>0
911
- dr = get_required_data(body[:properties][key])
912
- dr.each do |k|
913
- data_required.push("#{key}.#{k}".to_sym)
914
- end
915
- end
916
- end
917
- return data_required
918
- end
919
-
920
- # Get patterns
921
- private def get_patterns(dpk, dpv)
922
- data_pattern = []
923
- if dpv.keys.include?(:pattern)
924
- #todo: control better the cases with back slashes
925
- if dpv[:pattern].include?('\\\\/')
926
- #for cases like this: ^[^\.\\/:*?"<>|][^\\/:*?"<>|]{0,13}[^\.\\/:*?"<>|]?$
927
- data_pattern << "'#{dpk}': /#{dpv[:pattern].to_s.gsub('\/','/')}/"
928
- elsif dpv[:pattern].include?('\\x')
929
- data_pattern << "'#{dpk}': /#{dpv[:pattern].to_s.gsub('\\x','\\u')}/"
930
- else
931
- data_pattern << "'#{dpk}': /#{dpv[:pattern].to_s}/"
932
- end
933
- elsif dpv.key?(:minLength) and dpv.key?(:maxLength)
934
- data_pattern << "'#{dpk}': :'#{dpv[:minLength]}-#{dpv[:maxLength]}:LN$'"
935
- elsif dpv.key?(:minLength) and !dpv.key?(:maxLength)
936
- data_pattern << "'#{dpk}': :'#{dpv[:minLength]}:LN$'"
937
- elsif !dpv.key?(:minLength) and dpv.key?(:maxLength)
938
- data_pattern << "'#{dpk}': :'0-#{dpv[:maxLength]}:LN$'"
939
- elsif dpv.key?(:minimum) and dpv.key?(:maximum) and dpv[:type]=='string'
940
- data_pattern << "'#{dpk}': :'#{dpv[:minimum]}-#{dpv[:maximum]}:LN$'"
941
- elsif dpv.key?(:minimum) and dpv.key?(:maximum)
942
- data_pattern << "'#{dpk}': #{dpv[:minimum]}..#{dpv[:maximum]}"
943
- elsif dpv.key?(:minimum) and !dpv.key?(:maximum)
944
- if RUBY_VERSION >= '2.6.0'
945
- data_pattern << "'#{dpk}': #{dpv[:minimum]}.. "
946
- else
947
- data_pattern << "#'#{dpk}': #{dpv[:minimum]}.. # INFINITE only working on ruby>=2.6.0"
948
- end
949
- elsif !dpv.key?(:minimum) and dpv.key?(:maximum)
950
- data_pattern << "'#{dpk}': 0..#{dpv[:maximum]}"
951
- elsif dpv[:format] == 'date-time'
952
- data_pattern << "'#{dpk}': DateTime"
953
- elsif dpv[:type] == 'boolean'
954
- data_pattern << "'#{dpk}': Boolean"
955
- elsif dpv.key?(:enum)
956
- data_pattern << "'#{dpk}': :'#{dpv[:enum].join('|')}'"
957
- 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)
958
- #{:title=>"Balala", :type=>"array", :items=>{:type=>"string", :enum=>["uno","dos"], :example=>"uno"}}
959
- data_pattern << "'#{dpk}': [:'#{dpv[:items][: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].key?(:properties)
961
- #{:title=>"Balala", :type=>"array", :items=>{title: 'xxxx, properties: {server: {enum:['ibm','msa','pytan']}}}
962
- dpv[:items][:properties].each do |dpkk,dpvv|
963
- if dpk == ''
964
- data_pattern += get_patterns("#{dpkk}",dpvv)
965
- else
966
- data_pattern += get_patterns("#{dpk}.#{dpkk}",dpvv)
967
- end
968
- end
969
- elsif dpv[:type] == 'array' and dpv.key?(:items) and dpv[:items].is_a?(Hash) and
970
- !dpv[:items].key?(:enum) and !dpv[:items].key?(:properties) and dpv[:items].key?(:type)
971
- #{:title=>"labels", :description=>"Labels specified for the file system", :type=>"array", :items=>{:type=>"string", :enum=>["string"]}}
972
- data_pattern << "'#{dpk}': [ #{get_patterns('', dpv[:items]).join[4..-1]} ]"
973
- elsif dpv[:type] == 'object' and dpv.key?(:properties)
974
- dpv[:properties].each do |dpkk,dpvv|
975
- if dpk == ''
976
- data_pattern += get_patterns("#{dpkk}",dpvv)
977
- else
978
- data_pattern += get_patterns("#{dpk}.#{dpkk}",dpvv)
979
- end
980
- end
981
- end
982
- data_pattern.uniq!
983
- return data_pattern
984
-
985
- end
986
-
987
- #filter hash
988
- def filter(hash, keys, nested = false)
989
- result = {}
990
- keys = [keys] unless keys.is_a?(Array)
991
- if nested
992
- result = hash.nice_filter(keys)
993
- else
994
- #to be backwards compatible
995
- keys.each do |k|
996
- if k.is_a?(Symbol) and hash.key?(k)
997
- if hash[k].is_a?(Hash)
998
- result[k] = {}
999
- else
1000
- result[k] = hash[k]
1001
- end
1002
- 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
1003
- kn = k.to_s.split('.')
1004
- vn = kn[1].to_sym
1005
- result[kn.first.to_sym][vn] = filter(hash[kn.first.to_sym], vn).values[0]
1006
- elsif k.is_a?(Hash) and hash.key?(k.keys[0]) #nested {uno: {dos: :tres}}
1007
- result[k.keys[0]][k.values[0]] = filter(hash[k.keys[0]], k.values[0]).values[0]
1008
- end
1009
- end
1010
- end
1011
- return result
1012
- end
1013
-
1014
- #gen pretty hash symbolized
1015
- private def pretty_hash_symbolized(hash)
1016
- output = []
1017
- output << "{"
1018
- hash.each do |kr,kv|
1019
- if kv.kind_of?(Hash)
1020
- restv = pretty_hash_symbolized(kv)
1021
- restv[0] = "#{kr}: {"
1022
- output += restv
1023
- else
1024
- output << "#{kr}: #{kv.inspect}, "
1025
- end
1026
- end
1027
- output << "},"
1028
- return output
1029
- end
1030
- end
1031
- end