swgr2rb 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +69 -0
- data/assets/Gemfile +8 -0
- data/assets/README.md +94 -0
- data/assets/configs/conductor_sender_configs.yaml +4 -0
- data/assets/endpoint_object_models/config_loaders/base_config.rb +28 -0
- data/assets/endpoint_object_models/config_loaders/conductor_config_loader.rb +18 -0
- data/assets/endpoint_object_models/json_validator/json_data_validator.rb +44 -0
- data/assets/endpoint_object_models/json_validator/json_schema_validator.rb +52 -0
- data/assets/endpoint_object_models/json_validator/json_validator.rb +34 -0
- data/assets/endpoint_object_models/loader.rb +16 -0
- data/assets/endpoint_object_models/object_models/base_endpoint_object_model.rb +22 -0
- data/assets/endpoint_object_models/object_models/base_endpoint_object_model_constants.rb +2 -0
- data/assets/endpoint_object_models/object_models/base_endpoint_object_model_methods.rb +82 -0
- data/assets/endpoint_object_models/prototypes/json_schema_data_types.rb +3 -0
- data/assets/endpoint_object_models/prototypes/request.rb +31 -0
- data/assets/features/step_definitions/base_steps.rb +56 -0
- data/assets/features/support/env.rb +9 -0
- data/assets/features/support/instance_variables.rb +9 -0
- data/assets/features/support/world.rb +12 -0
- data/assets/request_sender/conductor_sender/conductor_sender.rb +53 -0
- data/assets/request_sender/conductor_sender/response.rb +105 -0
- data/assets/request_sender/loader.rb +16 -0
- data/bin/swgr2rb +8 -0
- data/lib/cli/cli_options_parser.rb +139 -0
- data/lib/endpoint_class_config_generator/endpoint_class_config.rb +25 -0
- data/lib/endpoint_class_config_generator/endpoint_class_config_generator.rb +91 -0
- data/lib/endpoint_class_config_generator/json_paths_parser_methods.rb +68 -0
- data/lib/endpoint_class_config_generator/json_schema_definitions_parser_methods.rb +59 -0
- data/lib/endpoint_class_generator/endpoint_class_generator.rb +168 -0
- data/lib/endpoint_class_generator/endpoint_classes_generator.rb +76 -0
- data/lib/endpoint_class_generator/ruby_file_generator.rb +52 -0
- data/lib/endpoint_class_generator/ruby_file_generator_constants.rb +106 -0
- data/lib/endpoint_class_generator/schema_module_generator.rb +49 -0
- data/lib/json_fetcher/swagger_json_fetcher.rb +45 -0
- data/lib/prototypes/json_schema_data_types.rb +11 -0
- data/lib/prototypes/swgr2rb_error.rb +13 -0
- data/lib/request_sender/conductor_sender.rb +28 -0
- data/lib/request_sender/request.rb +31 -0
- data/lib/request_sender/response.rb +115 -0
- data/lib/scaffold_generator/feature_file_generator.rb +62 -0
- data/lib/scaffold_generator/scaffold_generator.rb +51 -0
- data/lib/scaffold_generator/scaffold_generator_constants.rb +19 -0
- data/lib/swgr2rb.rb +53 -0
- 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
|