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.
- 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
|