swgr2rb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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