swgr2rb 1.0.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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +69 -0
  4. data/assets/Gemfile +8 -0
  5. data/assets/README.md +94 -0
  6. data/assets/configs/conductor_sender_configs.yaml +4 -0
  7. data/assets/endpoint_object_models/config_loaders/base_config.rb +28 -0
  8. data/assets/endpoint_object_models/config_loaders/conductor_config_loader.rb +18 -0
  9. data/assets/endpoint_object_models/json_validator/json_data_validator.rb +44 -0
  10. data/assets/endpoint_object_models/json_validator/json_schema_validator.rb +52 -0
  11. data/assets/endpoint_object_models/json_validator/json_validator.rb +34 -0
  12. data/assets/endpoint_object_models/loader.rb +16 -0
  13. data/assets/endpoint_object_models/object_models/base_endpoint_object_model.rb +22 -0
  14. data/assets/endpoint_object_models/object_models/base_endpoint_object_model_constants.rb +2 -0
  15. data/assets/endpoint_object_models/object_models/base_endpoint_object_model_methods.rb +82 -0
  16. data/assets/endpoint_object_models/prototypes/json_schema_data_types.rb +3 -0
  17. data/assets/endpoint_object_models/prototypes/request.rb +31 -0
  18. data/assets/features/step_definitions/base_steps.rb +56 -0
  19. data/assets/features/support/env.rb +9 -0
  20. data/assets/features/support/instance_variables.rb +9 -0
  21. data/assets/features/support/world.rb +12 -0
  22. data/assets/request_sender/conductor_sender/conductor_sender.rb +53 -0
  23. data/assets/request_sender/conductor_sender/response.rb +105 -0
  24. data/assets/request_sender/loader.rb +16 -0
  25. data/bin/swgr2rb +8 -0
  26. data/lib/cli/cli_options_parser.rb +139 -0
  27. data/lib/endpoint_class_config_generator/endpoint_class_config.rb +25 -0
  28. data/lib/endpoint_class_config_generator/endpoint_class_config_generator.rb +91 -0
  29. data/lib/endpoint_class_config_generator/json_paths_parser_methods.rb +68 -0
  30. data/lib/endpoint_class_config_generator/json_schema_definitions_parser_methods.rb +59 -0
  31. data/lib/endpoint_class_generator/endpoint_class_generator.rb +168 -0
  32. data/lib/endpoint_class_generator/endpoint_classes_generator.rb +76 -0
  33. data/lib/endpoint_class_generator/ruby_file_generator.rb +52 -0
  34. data/lib/endpoint_class_generator/ruby_file_generator_constants.rb +106 -0
  35. data/lib/endpoint_class_generator/schema_module_generator.rb +49 -0
  36. data/lib/json_fetcher/swagger_json_fetcher.rb +45 -0
  37. data/lib/prototypes/json_schema_data_types.rb +11 -0
  38. data/lib/prototypes/swgr2rb_error.rb +13 -0
  39. data/lib/request_sender/conductor_sender.rb +28 -0
  40. data/lib/request_sender/request.rb +31 -0
  41. data/lib/request_sender/response.rb +115 -0
  42. data/lib/scaffold_generator/feature_file_generator.rb +62 -0
  43. data/lib/scaffold_generator/scaffold_generator.rb +51 -0
  44. data/lib/scaffold_generator/scaffold_generator_constants.rb +19 -0
  45. data/lib/swgr2rb.rb +53 -0
  46. metadata +206 -0
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Swgr2rb
4
+ # EndpointClassConfig is a class that contains all
5
+ # necessary parameters for endpoint model generation
6
+ # as its attributes.
7
+ class EndpointClassConfig
8
+ attr_reader :endpoint_path, :request_type, :expected_response,
9
+ :request_params, :version
10
+ attr_accessor :operation_id
11
+
12
+ def initialize(endpoint_path, request_type, expected_response,
13
+ request_params, operation_id, version)
14
+ @endpoint_path = endpoint_path
15
+ @request_type = request_type
16
+ @expected_response = expected_response
17
+ @request_params = request_params
18
+ @operation_id = operation_id
19
+ @version = version
20
+ end
21
+
22
+ ExpectedResponse = Struct.new(:code, :schema)
23
+ RequestParams = Struct.new(:path, :query, :body, :form_data)
24
+ end
25
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'active_support'
5
+ require 'active_support/core_ext'
6
+ require_relative '../json_fetcher/swagger_json_fetcher'
7
+ require_relative 'json_schema_definitions_parser_methods'
8
+ require_relative 'endpoint_class_config'
9
+ require_relative 'json_paths_parser_methods'
10
+
11
+ module Swgr2rb
12
+ # EndpointClassConfigGenerator parses Swagger JSON, extracts
13
+ # all parameters necessary for endpoint models generation,
14
+ # and generates an array of EndpointClassConfig instances.
15
+ class EndpointClassConfigGenerator
16
+ include JsonSchemaDefinitionsParserMethods
17
+ include JsonPathsParserMethods
18
+
19
+ def initialize(swagger_path)
20
+ @json = fetch_swagger_json(swagger_path)
21
+ @schema_definitions = {}
22
+ end
23
+
24
+ def generate_configs
25
+ generate_response_schema_definitions
26
+ configs = @json[:paths].map do |request_path, request_hash|
27
+ request_hash.map do |request_type, request_properties|
28
+ generate_endpoint_config(request_path,
29
+ request_type,
30
+ request_properties)
31
+ end
32
+ end.flatten
33
+ generate_uniq_identifiers(configs)
34
+ configs
35
+ end
36
+
37
+ private
38
+
39
+ def fetch_swagger_json(swagger_endpoint_path)
40
+ JSON.parse(SwaggerJsonFetcher.get_swagger_json(swagger_endpoint_path)
41
+ .to_json,
42
+ symbolize_names: true)
43
+ end
44
+
45
+ def generate_endpoint_config(request_path, request_type, request_properties)
46
+ EndpointClassConfig.new(request_path.to_s,
47
+ generate_request_type(request_type,
48
+ request_properties),
49
+ generate_expected_response(request_properties),
50
+ generate_request_params(request_properties),
51
+ generate_operation_id(request_properties),
52
+ generate_version)
53
+ end
54
+
55
+ def generate_uniq_identifiers(configs)
56
+ configs.group_by(&:operation_id)
57
+ .select { |_operation_id, config_arr| config_arr.size > 1 }
58
+ .each do |operation_id, config_arr|
59
+ puts "Name conflict for operationId '#{operation_id}'. "\
60
+ 'Changing operationId for:'\
61
+ "#{config_arr.map { |c| "\n\t#{c.endpoint_path}" }.join}"
62
+ common_prefix = common_prefix(config_arr.map(&:endpoint_path))
63
+ config_arr.each { |config| update_operation_id(config, common_prefix) }
64
+ end
65
+ end
66
+
67
+ def update_operation_id(config, common_prefix)
68
+ uniq_suffix = config.endpoint_path.dup.delete_prefix(common_prefix)
69
+ .gsub(/[{}]/, '').split('/').select(&:present?)
70
+ .map { |substr| substr[0].upcase + substr[1..] }.join
71
+ config.operation_id = config.operation_id + uniq_suffix
72
+ end
73
+
74
+ def common_prefix(strings)
75
+ if strings.size < 2
76
+ strings.first
77
+ else
78
+ common_prefix(strings.each_slice(2).map do |str1, str2|
79
+ str2.nil? ? str1 : common_prefix_for_two_strings(str1, str2)
80
+ end)
81
+ end
82
+ end
83
+
84
+ def common_prefix_for_two_strings(str1, str2)
85
+ arr1 = str1.split('/')
86
+ arr2 = str2.split('/')
87
+ differ_at = (1...arr1.size).to_a.find { |i| arr1[i] != arr2[i] }
88
+ arr1[0...differ_at].join('/')
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,68 @@
1
+ module Swgr2rb
2
+ module JsonPathsParserMethods
3
+ def generate_request_type(request_type, request_properties)
4
+ if request_properties[:parameters].find { |param| param[:in] == 'formData' && param[:type] == 'file' }
5
+ :multipart_post
6
+ else
7
+ request_type
8
+ end
9
+ end
10
+
11
+ def generate_expected_response(request_properties)
12
+ EndpointClassConfig::ExpectedResponse.new(generate_expected_response_code(request_properties),
13
+ generate_expected_response_schema(request_properties))
14
+ end
15
+
16
+ def generate_request_params(request_properties)
17
+ params = EndpointClassConfig::RequestParams.new([], [], [], [])
18
+ request_properties[:parameters].select { |hsh| hsh[:required] }.each do |param_hash|
19
+ param_schema = case param_hash
20
+ in { schema: }
21
+ parse_field_properties(schema)
22
+ in { type: }
23
+ field_type_to_ruby_class(type)
24
+ end
25
+ params.send(param_hash[:in] == 'formData' ? :form_data : param_hash[:in].to_sym) << {
26
+ name: param_hash[:name],
27
+ schema: param_schema
28
+ }
29
+ end
30
+ params
31
+ end
32
+
33
+ def generate_operation_id(request_properties)
34
+ request_properties[:operationId]
35
+ end
36
+
37
+ def generate_version
38
+ @json[:info][:version].to_i
39
+ end
40
+
41
+ private
42
+
43
+ def generate_expected_response_code(request_hash)
44
+ request_hash[:responses].keys.map(&:to_s).select { |k| k.match?(/^2/) }.last.to_i
45
+ end
46
+
47
+ def generate_expected_response_schema(request_hash)
48
+ successful_response = request_hash[:responses].select { |code, _response| code.to_s.match?(/^2/) }.to_a.last[1]
49
+ case successful_response
50
+ in { schema: { type: 'array', items: } }
51
+ get_response_item_schema(items)
52
+ in { schema: }
53
+ get_response_item_schema(schema)
54
+ else
55
+ nil
56
+ end
57
+ end
58
+
59
+ def get_response_item_schema(schema_properties)
60
+ case schema_properties
61
+ in { '$ref': ref }
62
+ @schema_definitions[get_schema_name(ref)]
63
+ in { type: }
64
+ field_type_to_ruby_class(type)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,59 @@
1
+ module Swgr2rb
2
+ module JsonSchemaDefinitionsParserMethods
3
+ def generate_response_schema_definitions
4
+ @json[:definitions].keys.each do |name|
5
+ generate_schema_definition(name)
6
+ end
7
+ end
8
+
9
+ private
10
+
11
+ def generate_schema_definition(name)
12
+ @schema_definitions[name] ||= parse_schema_definition(@json[:definitions][name], name)
13
+ end
14
+
15
+ def parse_schema_definition(hash, name = '')
16
+ case hash
17
+ in { type: 'object', properties: }
18
+ properties.map do |field_name, field_properties|
19
+ { field_name.to_sym => parse_field_properties(field_properties, [name, field_name.to_sym]) }
20
+ end.reduce(&:merge)
21
+ end
22
+ end
23
+
24
+ def parse_field_properties(field_properties, parent_schema_names = [])
25
+ case field_properties
26
+ in { type: 'array', items: }
27
+ [parse_single_field(items, parent_schema_names)]
28
+ else
29
+ parse_single_field(field_properties, parent_schema_names)
30
+ end
31
+ end
32
+
33
+ def parse_single_field(field_hash, parent_schema_names)
34
+ case field_hash
35
+ in { '$ref': ref }
36
+ get_child_schema(ref, parent_schema_names)
37
+ in { type: }
38
+ field_type_to_ruby_class(type)
39
+ end
40
+ end
41
+
42
+ def get_child_schema(ref, parent_schema_names)
43
+ schema_name = get_schema_name(ref)
44
+ parent_schema_names.include?(schema_name) ? Hash : generate_schema_definition(schema_name)
45
+ end
46
+
47
+ def field_type_to_ruby_class(type)
48
+ case type
49
+ when 'number' then Float
50
+ when 'object' then Hash
51
+ else eval(type.to_s.capitalize)
52
+ end
53
+ end
54
+
55
+ def get_schema_name(ref)
56
+ ref.split('/').last.to_sym
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,168 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext'
3
+ require_relative 'ruby_file_generator'
4
+ require_relative '../prototypes/json_schema_data_types'
5
+
6
+ module Swgr2rb
7
+ # EndpointClassGenerator generates a Ruby class file
8
+ # for an endpoint object model from its config.
9
+ class EndpointClassGenerator < RubyFileGenerator
10
+ def generate_lines
11
+ [generate_requires,
12
+ generate_class_name,
13
+ generate_modules_to_include,
14
+ generate_initialize_method,
15
+ generate_validate_response_schema_method,
16
+ 'private',
17
+ generate_end_point_path_method,
18
+ generate_generate_headers_method,
19
+ generate_generate_body_method,
20
+ 'end'].compact.flatten
21
+ end
22
+
23
+ private
24
+
25
+ def generate_requires
26
+ RubyFileGeneratorConstants::REQUIRES.call([@opts[:parent_class]].compact + @opts[:modules_to_include].to_a)
27
+ end
28
+
29
+ def generate_class_name
30
+ RubyFileGeneratorConstants::CLASS_NAME.call(@opts[:name], @opts[:parent_class]&.fetch(:name))
31
+ end
32
+
33
+ def generate_modules_to_include
34
+ RubyFileGeneratorConstants::INCLUDES.call(@opts[:modules_to_include].to_a)
35
+ end
36
+
37
+ def generate_initialize_method
38
+ RubyFileGeneratorConstants::INITIALIZE.call(generate_endpoint_path,
39
+ @config.request_type)
40
+ end
41
+
42
+ def generate_validate_response_schema_method
43
+ RubyFileGeneratorConstants::VALIDATE_RESPONSE_SCHEMA.call(generate_schema_validation)
44
+ end
45
+
46
+ def generate_end_point_path_method
47
+ unknown_params = path_params[:snake_case] + query_params[:snake_case]
48
+ param_loading = generate_endpoint_path_param_loading(unknown_params)
49
+ RubyFileGeneratorConstants::END_POINT_PATH.call(unknown_params,
50
+ param_loading)
51
+ .compact.flatten
52
+ end
53
+
54
+ def generate_generate_headers_method
55
+ RubyFileGeneratorConstants::GENERATE_HEADERS.call(@config.request_type)
56
+ end
57
+
58
+ def generate_generate_body_method
59
+ RubyFileGeneratorConstants::GENERATE_BODY.call(generate_request_body(@config.request_params))
60
+ end
61
+
62
+ def generate_endpoint_path
63
+ path = @config.endpoint_path.gsub('{', '#{')
64
+ path_params[:camel_case].zip(path_params[:snake_case]) do |cc_param, sc_param|
65
+ path.sub!(cc_param, sc_param)
66
+ end
67
+ path.sub!('#{version}', @config.version.to_s)
68
+ if query_params[:camel_case].present?
69
+ path << '?' << query_params[:camel_case].zip(query_params[:snake_case])
70
+ .map { |cc, sc| "#{cc}=\#{#{sc}}" }.join('&')
71
+ end
72
+ proc_params = path_params[:snake_case] + query_params[:snake_case]
73
+ if proc_params.empty?
74
+ "proc { '#{path}' }"
75
+ else
76
+ "proc { |#{proc_params.sort.join(", ")}| \"#{path}\" }"
77
+ end
78
+ end
79
+
80
+ def path_params
81
+ @path_params ||= generate_params(@config.request_params.path.reject { |p| p[:name] == 'version' })
82
+ end
83
+
84
+ def query_params
85
+ @query_params ||= generate_params(@config.request_params.query)
86
+ end
87
+
88
+ def generate_params(params)
89
+ camel_case_params = params.map { |hsh| hsh[:name] }
90
+ snake_case_params = camel_case_params.map { |s| RubyFileGeneratorConstants::CAMEL_CASE_TO_SNAKE_CASE.call(s) }
91
+ { camel_case: camel_case_params, snake_case: snake_case_params }
92
+ end
93
+
94
+ def generate_schema_validation
95
+ RubyFileGeneratorConstants::JSON_VALIDATOR_VALIDATE_SCHEMA if @config.expected_response.schema.present?
96
+ end
97
+
98
+ def generate_endpoint_path_param_loading(params)
99
+ if params.present?
100
+ lines = params.map do |param|
101
+ RubyFileGeneratorConstants::GET_PARAM_FROM_REQUEST_OPTIONS.call(param)
102
+ end
103
+ lines << RubyFileGeneratorConstants::COMMENT_ADD_SUB_RESULTS
104
+ lines << RubyFileGeneratorConstants::RAISE_UNLESS_PARAMS_PASSED.call(params, @config.endpoint_path)
105
+ lines.flatten
106
+ end
107
+ end
108
+
109
+ def generate_request_body(params)
110
+ case params
111
+ in { body: [{ schema: } => param, *] }
112
+ [RubyFileGeneratorConstants::COMMENT_SET_VALID_VALUES,
113
+ "tmp = #{generate_default_request_body(schema)}",
114
+ RubyFileGeneratorConstants::COMMENT_ADD_SUB_RESULTS,
115
+ generate_request_body_set_params(param),
116
+ 'tmp.to_json'].flatten
117
+ in { form_data: [Hash, *] }
118
+ RubyFileGeneratorConstants::MULTIPART_REQUEST_BODY
119
+ else
120
+ 'nil'
121
+ end
122
+ end
123
+
124
+ def generate_default_request_body(schema)
125
+ if schema.instance_of?(Class) || schema == Boolean
126
+ default_value_for_type(schema)
127
+ elsif schema.is_a?(Array)
128
+ "[\n" + generate_default_request_body(schema.first) + "\n]"
129
+ elsif schema.is_a?(Hash)
130
+ schema = schema.map { |name, type| "#{name}: #{generate_default_request_body(type)}," }
131
+ .join("\n").sub(/,\Z/, '')
132
+ "{\n" + schema + "\n}"
133
+ end
134
+ end
135
+
136
+ def generate_request_body_set_params(params)
137
+ case params
138
+ in { schema: Class | Boolean => schema, name: }
139
+ "tmp = #{RubyFileGeneratorConstants::GET_PARAM_FROM_REQUEST_PARAMS.call(name, schema)}"
140
+ in { schema: [Hash => item, *] }
141
+ item.map do |name, type|
142
+ "tmp.first[:#{name}] = #{RubyFileGeneratorConstants::GET_PARAM_FROM_REQUEST_PARAMS.call(name, type)}"
143
+ end
144
+ in { schema: [type], name: }
145
+ ["tmp = #{RubyFileGeneratorConstants::GET_PARAM_FROM_REQUEST_PARAMS.call(name, type)}",
146
+ 'tmp = tmp.split(/,\s*/)']
147
+ in { schema: Hash => schema }
148
+ schema.map do |name, type|
149
+ "tmp[:#{name}] = #{RubyFileGeneratorConstants::GET_PARAM_FROM_REQUEST_PARAMS.call(name, type)}"
150
+ end
151
+ end
152
+ end
153
+
154
+ def default_value_for_type(type)
155
+ if type == String
156
+ "'string'"
157
+ elsif type == Integer
158
+ '0'
159
+ elsif type == Float
160
+ '0.0'
161
+ elsif type == Boolean
162
+ 'false'
163
+ else
164
+ raise "Unexpected type: #{type}"
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../endpoint_class_config_generator/endpoint_class_config_generator'
4
+ require_relative 'endpoint_class_generator'
5
+ require_relative 'schema_module_generator'
6
+
7
+ module Swgr2rb
8
+ # EndpointClassesGenerator calls a component that generates an array
9
+ # of configs for endpoint models from Swagger JSON, and then invokes
10
+ # Ruby file generators for each config.
11
+ class EndpointClassesGenerator
12
+ def initialize(swagger_endpoint_path, params)
13
+ @swagger_endpoint_path = swagger_endpoint_path
14
+ @params = params
15
+ end
16
+
17
+ def generate_endpoint_classes
18
+ EndpointClassConfigGenerator.new(@swagger_endpoint_path)
19
+ .generate_configs.map do |endpoint_config|
20
+ endpoint_class_name = generate_class_name(endpoint_config.operation_id)
21
+ SchemaModuleGenerator.new(endpoint_config,
22
+ generate_schema_opts(endpoint_class_name))
23
+ .generate_file
24
+
25
+ EndpointClassGenerator.new(endpoint_config,
26
+ generate_class_opts(endpoint_class_name))
27
+ .generate_file
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def generate_class_name(operation_id)
34
+ operation_id.gsub('_', '').split(/([[:upper:]][[:lower:]]+)/)
35
+ .select(&:present?).map(&:capitalize).join
36
+ end
37
+
38
+ def generate_schema_opts(endpoint_class_name)
39
+ {
40
+ target_dir: File.join(@params[:target_dir],
41
+ @params[:component],
42
+ 'object_model_schemas'),
43
+ name: "#{endpoint_class_name}Schema",
44
+ update_only: @params[:update_only],
45
+ rewrite: @params[:rewrite_schemas]
46
+ }
47
+ end
48
+
49
+ def generate_class_opts(class_name)
50
+ {
51
+ target_dir: File.join(@params[:target_dir], @params[:component]),
52
+ name: class_name,
53
+ modules_to_include: [base_config('BaseEndpointObjectModelMethods'),
54
+ generate_config_for_schema(class_name)].compact,
55
+ parent_class: base_config('BaseEndpointObjectModel'),
56
+ update_only: @params[:update_only],
57
+ rewrite: false
58
+ }
59
+ end
60
+
61
+ def generate_config_for_schema(class_name)
62
+ {
63
+ name: "#{class_name}Schema",
64
+ path: File.join('object_model_schemas',
65
+ RubyFileGeneratorConstants::CAMEL_CASE_TO_SNAKE_CASE.call("#{class_name}Schema"))
66
+ }
67
+ end
68
+
69
+ def base_config(name)
70
+ {
71
+ name: name,
72
+ path: File.join('..', RubyFileGeneratorConstants::CAMEL_CASE_TO_SNAKE_CASE.call(name))
73
+ }
74
+ end
75
+ end
76
+ end