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,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require_relative 'ruby_file_generator_constants'
5
+
6
+ module Swgr2rb
7
+ # RubyFileGenerator is meant to be used as an abstract class
8
+ # to be inherited from. It contains methods that create a Ruby file
9
+ # and write to it the return value of generate_lines method.
10
+ # The method is implemented in its child classes, EndpointClassGenerator
11
+ # and SchemaModuleGenerator.
12
+ class RubyFileGenerator
13
+ include RubyFileGeneratorConstants
14
+
15
+ # opts can include:
16
+ # name: class/module name
17
+ # rewrite: if set to true, rewrite the existing file if it already exists
18
+ # target_dir: directory where the class/module will be created
19
+ # update_only: if set to true, do not create new file,
20
+ # only update existing
21
+ def initialize(class_config, opts)
22
+ @config = class_config
23
+ @opts = opts
24
+ end
25
+
26
+ def generate_file
27
+ if (File.exist?(filename) && !@opts[:rewrite]) ||
28
+ (!File.exist?(filename) && @opts[:update_only])
29
+ return
30
+ end
31
+
32
+ File.open(filename, 'w') do |file|
33
+ file.write(generate_lines.join("\n"))
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def filename
40
+ unless @filename
41
+ create_target_dir(@opts[:target_dir])
42
+ @filename = File.join(@opts[:target_dir],
43
+ "#{RubyFileGeneratorConstants::CAMEL_CASE_TO_SNAKE_CASE.call(@opts[:name])}.rb")
44
+ end
45
+ @filename
46
+ end
47
+
48
+ def create_target_dir(dir_str)
49
+ FileUtils.mkdir_p(dir_str) unless Dir.exist?(dir_str)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../prototypes/json_schema_data_types'
4
+
5
+ module Swgr2rb
6
+ module RubyFileGeneratorConstants
7
+ REQUIRES = proc do |required|
8
+ required.map { |hsh| "require_relative '#{hsh[:path]}'" }.join("\n") + "\n"
9
+ end
10
+
11
+ CLASS_NAME = proc do |class_name, parent_class_name|
12
+ "class #{class_name}#{parent_class_name ? " < #{parent_class_name}" : ''}"
13
+ end
14
+ MODULE_NAME = proc { |module_name| "module #{module_name}" }
15
+
16
+ INCLUDES = proc do |modules|
17
+ modules.map { |hsh| "include #{hsh[:name]}" }.join("\n") + "\n"
18
+ end
19
+
20
+ INITIALIZE = proc do |endpoint_path, supported_requests|
21
+ ['def initialize',
22
+ " @end_point_path = #{endpoint_path}",
23
+ " @supportable_request_types = %w[#{supported_requests}]",
24
+ 'end']
25
+ end
26
+
27
+ VALIDATE_RESPONSE_SCHEMA = proc do |schema_validation|
28
+ ['def validate_response_schema',
29
+ ' validate_response_code',
30
+ schema_validation,
31
+ 'end'].compact.flatten
32
+ end
33
+
34
+ END_POINT_PATH = proc do |params, param_loading|
35
+ ['def end_point_path',
36
+ param_loading,
37
+ " @end_point_path.call#{params.empty? ? '' : "(#{params.sort.join(', ')})"}",
38
+ 'end'].compact.flatten
39
+ end
40
+
41
+ GENERATE_HEADERS = proc do |request_type|
42
+ if request_type == :multipart_post
43
+ ['def generate_headers',
44
+ " { 'Content-Type': 'multipart/form-data' }",
45
+ 'end']
46
+ end
47
+ end
48
+
49
+ GENERATE_BODY = proc do |body|
50
+ ['def generate_body',
51
+ (body || 'nil'),
52
+ 'end'].flatten
53
+ end
54
+
55
+ GET_PARAM_FROM_REQUEST_OPTIONS = proc do |param|
56
+ "#{param} = request_options[:params]['#{param}'] if request_options[:params] && request_options[:params]['#{param}']"
57
+ end
58
+ COMMENT_ADD_SUB_RESULTS = '# TODO: Consider adding ability to load params from request_options[:sub_results]'
59
+ RAISE_UNLESS_PARAMS_PASSED = proc do |params, endpoint_path|
60
+ ["unless #{params.join(' && ')}",
61
+ ' raise "Harness error\n"\\',
62
+ " 'The #{endpoint_path} '\\",
63
+ " 'requires #{params.join(', ')} parameter#{params.size > 1 ? 's' : ''}'",
64
+ 'end']
65
+ end
66
+
67
+ JSON_VALIDATOR_VALIDATE_SCHEMA = 'JsonValidator.validate(expected_schema, response.body)'
68
+
69
+ COMMENT_SET_VALID_VALUES = '# TODO: Set meaningful default values in tmp'
70
+ GET_PARAM_FROM_REQUEST_PARAMS = proc do |name, type|
71
+ name = CAMEL_CASE_TO_SNAKE_CASE.call(name)
72
+ "request_options[:params]['#{name}'] if request_options[:params]"\
73
+ "#{(type == Boolean ? "&.key?('#{name}')" : " && request_options[:params]['#{name}']")}"
74
+ end
75
+ MULTIPART_REQUEST_BODY = ['# TODO: Add valid default file path',
76
+ "file_path = 'misc/default_file_path'",
77
+ "file_path = request_options[:params]['file_path'] if request_options[:params] && request_options[:params]['file_path']",
78
+ '{ filePath: file_path }'].freeze
79
+
80
+ EXPECTED_CODE = proc do |code|
81
+ ['def expected_code',
82
+ code.to_s,
83
+ 'end']
84
+ end
85
+
86
+ EXPECTED_SCHEMA = proc do |schema|
87
+ ['def expected_schema',
88
+ schema,
89
+ 'end'].flatten
90
+ end
91
+
92
+ # Helper constants
93
+
94
+ CAMEL_CASE_TO_SNAKE_CASE = proc do |str|
95
+ str.to_s.split(/([[:upper:]][[:lower:]]+)/)
96
+ .select(&:present?)
97
+ .map(&:downcase)
98
+ .join('_')
99
+ .gsub(/_+/, '_')
100
+ end
101
+
102
+ SNAKE_CASE_TO_CAMEL_CASE = proc do |str|
103
+ str.to_s.split('_').map(&:capitalize).join
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ruby_file_generator'
4
+ require_relative '../prototypes/json_schema_data_types'
5
+
6
+ module Swgr2rb
7
+ # SchemaModuleGenerator generates a Ruby module file
8
+ # for an endpoint object model schema from its config.
9
+ class SchemaModuleGenerator < RubyFileGenerator
10
+ def generate_lines
11
+ [generate_module_name,
12
+ generate_expected_code_method,
13
+ generate_expected_schema_method,
14
+ 'end'].compact.flatten
15
+ end
16
+
17
+ private
18
+
19
+ def generate_module_name
20
+ RubyFileGeneratorConstants::MODULE_NAME.call(@opts[:name])
21
+ end
22
+
23
+ def generate_expected_code_method
24
+ RubyFileGeneratorConstants::EXPECTED_CODE.call(@config.expected_response.code)
25
+ end
26
+
27
+ def generate_expected_schema_method
28
+ return unless @config.expected_response.schema.present?
29
+
30
+ RubyFileGeneratorConstants::EXPECTED_SCHEMA.call(generate_expected_schema(@config.expected_response.schema))
31
+ end
32
+
33
+ def generate_expected_schema(response_schema)
34
+ if response_schema.instance_of?(Class) || response_schema == Boolean
35
+ response_schema.to_s.sub(/^.*::/, '')
36
+ elsif response_schema.is_a?(Array)
37
+ "[\n" + generate_expected_schema(response_schema.first) + "\n]"
38
+ elsif response_schema.is_a?(Hash)
39
+ "{\n" + generate_hash_schema(response_schema) + "\n}"
40
+ end
41
+ end
42
+
43
+ def generate_hash_schema(response_schema)
44
+ response_schema.map do |name, type|
45
+ "#{name}: #{generate_expected_schema(type)},"
46
+ end.join("\n").sub(/,\Z/, '')
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../request_sender/conductor_sender'
4
+ require_relative '../request_sender/request'
5
+
6
+ module Swgr2rb
7
+ # SwaggerJsonFetcher fetches a Swagger JSON given its path.
8
+ # Since the path can either be a URL of Swagger or a path
9
+ # to a .json file, SwaggerJsonFetcher either makes a GET request
10
+ # to the given URL and returns its response, or reads and parses
11
+ # a specified .json file.
12
+ class SwaggerJsonFetcher
13
+ class << self
14
+ def get_swagger_json(path)
15
+ if URI.extract(path).present?
16
+ send_request_to_swagger(path)
17
+ else
18
+ read_file(path)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def send_request_to_swagger(endpoint_path)
25
+ request = generate_request_to_swagger(endpoint_path)
26
+ response = ConductorSender.send_request(request)
27
+ response.body
28
+ end
29
+
30
+ def read_file(file_path)
31
+ JSON.parse(File.read(file_path))
32
+ rescue IOError, JSON::ParserError
33
+ raise Swgr2rbError,
34
+ "An error occurred while trying to read JSON file '#{file_path}'"
35
+ end
36
+
37
+ def generate_request_to_swagger(endpoint_path)
38
+ Request.new(endpoint_path,
39
+ 'get',
40
+ nil,
41
+ nil)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Swgr2rb
4
+ # Boolean module is included in TrueClass and FalseClass
5
+ # so that it can be used as a type when dealing with JSON schemas
6
+ module Boolean; end
7
+ end
8
+ # rubocop:disable Style/Documentation
9
+ class TrueClass; include Swgr2rb::Boolean; end
10
+ class FalseClass; include Swgr2rb::Boolean; end
11
+ # rubocop:enable Style/Documentation
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Swgr2rb
4
+ # Swgr2rbError is a custom error that is raised when
5
+ # an error occurs that requires a change in user input.
6
+ # It is intercepted in bin/swgr2rb so that its backtrace
7
+ # is not returned to the user.
8
+ class Swgr2rbError < RuntimeError
9
+ def initialize(msg = nil)
10
+ super("#{msg}\nTry 'swgr2rb --help' for more information")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+ require_relative 'response'
5
+
6
+ module Swgr2rb
7
+ # ConductorSender uses HTTParty to send API requests
8
+ # with given properties.
9
+ class ConductorSender
10
+ class << self
11
+ def send_request(request)
12
+ response = send("send_#{request.type}_request",
13
+ request)
14
+
15
+ Response.new(response)
16
+ end
17
+
18
+ private
19
+
20
+ def send_get_request(request)
21
+ HTTParty.get(request.url,
22
+ query: request.body,
23
+ headers: request.headers,
24
+ verify: false)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Swgr2rb
4
+ # Request contains all parameters necessary for making an API request.
5
+ # Its instances are being passed to ConductorSender.send_request.
6
+ class Request
7
+ attr_reader :type, :headers, :body, :subdomain, :endpoint_name
8
+
9
+ def initialize(endpoint_name, type, headers, body)
10
+ @endpoint_name = endpoint_name
11
+ @type = type
12
+ @headers = headers
13
+ @body = body
14
+ process_request_type
15
+ end
16
+
17
+ def url
18
+ generate_url
19
+ end
20
+
21
+ private
22
+
23
+ def process_request_type
24
+ @type = type.split(' ').join('_') if type.split(' ').length > 1
25
+ end
26
+
27
+ def generate_url
28
+ "https://#{endpoint_name}"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Swgr2rb
4
+ # Response parses API response.
5
+ class Response
6
+ attr_accessor :code, :headers, :body
7
+
8
+ def initialize(response)
9
+ @response = response
10
+ check_response
11
+ end
12
+
13
+ private
14
+
15
+ def check_response
16
+ if @response.is_a?(String)
17
+ return if try_to_parse_json_from_string
18
+ end
19
+
20
+ if @response.nil? || (@response.respond_to?(:body) &&
21
+ %w[null true false].include?(@response.body))
22
+ handle_nil_response
23
+ else
24
+ handle_response
25
+ end
26
+ end
27
+
28
+ def try_to_parse_json_from_string
29
+ resp_hash = JSON.parse(@response)
30
+ return unless resp_hash
31
+
32
+ @code = 200
33
+ @headers = ''
34
+ @body = convert_hash(resp_hash)
35
+ rescue StandardError
36
+ raise Swgr2rbError,
37
+ 'An error occurred while trying to parse JSON from API response'
38
+ end
39
+
40
+ def convert_hash(base_hash)
41
+ return base_hash unless base_hash.is_a? Hash
42
+
43
+ temp_hash = base_hash.dup
44
+ base_hash.each do |k, v|
45
+ temp_hash = convert_key_value_pair(temp_hash, k, v)
46
+ end
47
+ temp_hash
48
+ end
49
+
50
+ def convert_key_value_pair(dest, key, value)
51
+ case value
52
+ when Hash
53
+ dest[key.to_sym] = convert_hash(value)
54
+ when Array
55
+ dest[key.to_sym] = value.map { |v| convert_hash(v) }
56
+ else
57
+ dest = dest.transform_keys(&:to_sym)
58
+ end
59
+ dest
60
+ end
61
+
62
+ def handle_nil_response
63
+ @code = @response.code
64
+ @headers = @response.headers
65
+
66
+ if @code == 204
67
+ unless @response.body.nil?
68
+ raise 'Received non-null body in 204 No Content response'
69
+ end
70
+
71
+ @body = nil
72
+ else
73
+ parse_body_for_nil_response
74
+ end
75
+ end
76
+
77
+ def parse_body_for_nil_response
78
+ case @response.body
79
+ when 'null', ''
80
+ @body = {}
81
+ when 'true'
82
+ @body = true
83
+ when 'false'
84
+ @body = false
85
+ else
86
+ raise 'Not implemented behavior for the empty response'
87
+ end
88
+ end
89
+
90
+ def handle_response
91
+ @code = @response.code
92
+ @headers = @response.headers
93
+ @body = parse_response_body
94
+ end
95
+
96
+ def parse_response_body
97
+ if @headers&.content_type == 'application/json'
98
+ parse_json_body
99
+ else
100
+ # parsed response is string
101
+ @response.parsed_response
102
+ end
103
+ end
104
+
105
+ def parse_json_body
106
+ if @response.parsed_response.is_a? Array
107
+ @response.parsed_response.map { |i| convert_hash(i) }
108
+ elsif @response.parsed_response.is_a? Integer
109
+ @response.parsed_response
110
+ else
111
+ convert_hash(@response.parsed_response)
112
+ end
113
+ end
114
+ end
115
+ end