gemfather-stable 2.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +145 -0
- data/bin/gemfather +6 -0
- data/gem_template/%app_name%.gemspec.tt +32 -0
- data/gem_template/.gitignore.tt +26 -0
- data/gem_template/.rubocop-rspec.yml.tt +38 -0
- data/gem_template/.rubocop-ruby.yml.tt +54 -0
- data/gem_template/.rubocop.yml.tt +11 -0
- data/gem_template/Gemfile.tt +17 -0
- data/gem_template/Makefile.tt +21 -0
- data/gem_template/README.md.tt +180 -0
- data/gem_template/Rakefile.tt +6 -0
- data/gem_template/bin/console.tt +21 -0
- data/gem_template/bin/generate.tt +6 -0
- data/gem_template/bin/rubocop.tt +29 -0
- data/gem_template/lib/%app_name%.rb.tt +25 -0
- data/gem_template/lib/%app_short_name%/client.rb.tt +18 -0
- data/gem_template/lib/%app_short_name%/http_errors.rb.tt +20 -0
- data/gem_template/lib/%app_short_name%/railtie.rb.tt +5 -0
- data/gem_template/lib/%app_short_name%/version.rb.tt +5 -0
- data/gem_template/log/.keep.tt +0 -0
- data/gem_template/spec/spec_helper.rb.tt +35 -0
- data/lib/api_generator/client/config.rb +57 -0
- data/lib/api_generator/client/connection.rb +149 -0
- data/lib/api_generator/commands/generate.rb +56 -0
- data/lib/api_generator/helpers/utils.rb +56 -0
- data/lib/api_generator/middleware/error_code_middleware.rb +43 -0
- data/lib/api_generator/middleware/error_handler_middleware.rb +26 -0
- data/lib/api_generator/middleware/handle_unsuccessful_request_middleware.rb +36 -0
- data/lib/api_generator/middleware/http_errors.rb +21 -0
- data/lib/api_generator/middleware/raise_error_base.rb +11 -0
- data/lib/api_generator/middleware/raise_error_dsl.rb +36 -0
- data/lib/api_generator/middleware.rb +4 -0
- data/lib/api_generator/models/base.rb +15 -0
- data/lib/api_generator/models/data.rb +16 -0
- data/lib/api_generator/pagination/dsl.rb +32 -0
- data/lib/api_generator/pagination/limit_offset_relation.rb +24 -0
- data/lib/api_generator/pagination/page_relation.rb +21 -0
- data/lib/api_generator/pagination/relation.rb +114 -0
- data/lib/api_generator/pagination.rb +2 -0
- data/lib/api_generator/railtie.rb +24 -0
- data/lib/api_generator/services/base_create.rb +56 -0
- data/lib/api_generator/services/create_api.rb +87 -0
- data/lib/api_generator/services/create_model.rb +69 -0
- data/lib/api_generator/services/create_scaffold.rb +53 -0
- data/lib/api_generator/services/gemfather.rb +81 -0
- data/lib/api_generator/version.rb +3 -0
- data/lib/api_generator.rb +18 -0
- data/templates/api/action.erb +10 -0
- data/templates/api/action_paginate.erb +10 -0
- data/templates/api/api_module.erb +11 -0
- data/templates/api/include_helpers.erb +9 -0
- data/templates/api/include_namespace.erb +1 -0
- data/templates/api/path.erb +1 -0
- data/templates/models/request.erb +11 -0
- data/templates/models/response.erb +11 -0
- data/templates/specs/api_spec.erb +15 -0
- data/templates/specs/request_spec.erb +12 -0
- data/templates/specs/response_spec.erb +12 -0
- metadata +229 -0
File without changes
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require '<%= app_name %>'
|
3
|
+
require 'dotenv/load'
|
4
|
+
require 'vcr'
|
5
|
+
|
6
|
+
VCR.configure do |config|
|
7
|
+
config.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
|
8
|
+
|
9
|
+
config.hook_into :faraday
|
10
|
+
|
11
|
+
config.before_record do |vcr|
|
12
|
+
vcr.response.body.force_encoding('UTF-8')
|
13
|
+
end
|
14
|
+
|
15
|
+
config.configure_rspec_metadata!
|
16
|
+
end
|
17
|
+
|
18
|
+
RSpec.configure do |config|
|
19
|
+
config.expect_with :rspec do |c|
|
20
|
+
c.syntax = :expect
|
21
|
+
end
|
22
|
+
|
23
|
+
config.order = :random
|
24
|
+
|
25
|
+
Kernel.srand config.seed
|
26
|
+
|
27
|
+
config.before do
|
28
|
+
<%= app_name_class %>::Client.configure do |configuration|
|
29
|
+
configuration.api_endpoint = 'http://<%= app_name %>.dev'
|
30
|
+
configuration.api_header = 'X-Source-Token'
|
31
|
+
configuration.api_token = 'TEST_TOKEN'
|
32
|
+
configuration.logger = Logger.new(Pathname.new(__dir__).join('..', 'log', 'test.log'))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module ApiGenerator
|
2
|
+
module Client
|
3
|
+
module Config
|
4
|
+
class ConfigurationError < StandardError; end
|
5
|
+
|
6
|
+
USER_AGENT = 'Ruby API Client'.freeze
|
7
|
+
OPEN_TIMEOUT = 3
|
8
|
+
READ_TIMEOUT = 3
|
9
|
+
MAX_RETRIES = 3
|
10
|
+
RETRY_INTERVAL = 1
|
11
|
+
RETRY_BACKOFF_FACTOR = 1
|
12
|
+
CONNECTION_ERROR = ApiGenerator::Middleware::HttpErrors::ConnectionError
|
13
|
+
|
14
|
+
# rubocop:disable Metrics/MethodLength
|
15
|
+
def self.extended(klass)
|
16
|
+
klass.extend Dry::Configurable
|
17
|
+
|
18
|
+
klass.class_eval do
|
19
|
+
setting :api_endpoint, reader: true
|
20
|
+
setting :user_agent, default: USER_AGENT, reader: true
|
21
|
+
|
22
|
+
setting :api_header, reader: true
|
23
|
+
setting :api_token, reader: true
|
24
|
+
|
25
|
+
setting :api_user, reader: true
|
26
|
+
setting :api_password, reader: true
|
27
|
+
|
28
|
+
setting :ssl_verify, reader: true
|
29
|
+
setting :ca_file, reader: true
|
30
|
+
|
31
|
+
setting :open_timeout, default: OPEN_TIMEOUT, reader: true
|
32
|
+
setting :read_timeout, default: READ_TIMEOUT, reader: true
|
33
|
+
setting :max_retries, default: MAX_RETRIES, reader: true
|
34
|
+
setting :retry_interval, default: RETRY_INTERVAL, reader: true
|
35
|
+
setting :retry_backoff_factor, default: RETRY_BACKOFF_FACTOR, reader: true
|
36
|
+
setting :logger, reader: true
|
37
|
+
|
38
|
+
setting :retriable_errors, default: [CONNECTION_ERROR], reader: true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
# rubocop:enable Metrics/MethodLength
|
42
|
+
|
43
|
+
def check_config
|
44
|
+
raise ConfigurationError, 'API endpoint is not configured.' if config.api_endpoint.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
def inherit_config!(other_config)
|
48
|
+
config.to_h.each_key { |setting| config[setting] = other_config[setting] }
|
49
|
+
config
|
50
|
+
end
|
51
|
+
|
52
|
+
def same_config?(other_config)
|
53
|
+
config.to_h == other_config.to_h
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module ApiGenerator
|
2
|
+
module Client
|
3
|
+
# rubocop:disable Metrics/ClassLength
|
4
|
+
class Connection < SimpleDelegator
|
5
|
+
extend Config
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def inherited(subclass)
|
9
|
+
subclass.extend Middleware::RaiseErrorDsl
|
10
|
+
|
11
|
+
error_namespace = begin
|
12
|
+
subclass.module_parent::HttpErrors
|
13
|
+
rescue NameError
|
14
|
+
ApiGenerator::Middleware::HttpErrors
|
15
|
+
end
|
16
|
+
|
17
|
+
subclass.setup_middleware(error_namespace)
|
18
|
+
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def setup_middleware(error_namespace)
|
23
|
+
unsuccessful_request_middleware = Class.new(
|
24
|
+
Middleware::HandleUnsuccessfulRequestMiddleware,
|
25
|
+
)
|
26
|
+
unsuccessful_request_middleware.error_namespace = error_namespace if error_namespace
|
27
|
+
const_set(:HandleUnsuccessfulRequestMiddleware, unsuccessful_request_middleware)
|
28
|
+
|
29
|
+
error_handler_middleware = Class.new(Middleware::ErrorHandlerMiddleware)
|
30
|
+
error_handler_middleware.error_namespace = error_namespace if error_namespace
|
31
|
+
const_set(:ErrorHandlerMiddleware, error_handler_middleware)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
ACCEPT_HEADER = 'application/json'.freeze
|
36
|
+
|
37
|
+
attr_accessor :customization_block
|
38
|
+
|
39
|
+
def initialize(**settings, &blk)
|
40
|
+
if instance_of?(Connection)
|
41
|
+
raise LoadError, 'This class is abstract and is not intended to be initialized directly'
|
42
|
+
end
|
43
|
+
|
44
|
+
self.customization_block = blk if blk
|
45
|
+
|
46
|
+
config = self.class.inherit_config!(self.class.config)
|
47
|
+
settings.each { |setting, value| config[setting] = value }
|
48
|
+
self.class.check_config
|
49
|
+
|
50
|
+
super(connection)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# rubocop:disable Metrics/MethodLength
|
56
|
+
def connection
|
57
|
+
@connection ||= Faraday.new(url: self.class.api_endpoint) do |connection|
|
58
|
+
setup_auth!(connection)
|
59
|
+
|
60
|
+
setup_failed_req_handling!(connection)
|
61
|
+
setup_timeouts!(connection)
|
62
|
+
setup_user_agent!(connection)
|
63
|
+
setup_logger!(connection)
|
64
|
+
setup_retries!(connection)
|
65
|
+
setup_error_handling!(connection)
|
66
|
+
setup_error_code_middleware!(connection)
|
67
|
+
setup_ssl(connection)
|
68
|
+
|
69
|
+
customize_connection!(connection)
|
70
|
+
|
71
|
+
# When set last - req/res parsed as json in middleware
|
72
|
+
setup_json!(connection)
|
73
|
+
|
74
|
+
connection.adapter(Faraday.default_adapter)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
# rubocop:enable Metrics/MethodLength
|
78
|
+
|
79
|
+
# rubocop:disable Metrics/AbcSize
|
80
|
+
def setup_auth!(connection)
|
81
|
+
if self.class.api_token && self.class.api_header
|
82
|
+
connection.headers[self.class.api_header] = self.class.api_token
|
83
|
+
elsif self.class.api_user && self.class.api_password
|
84
|
+
connection.use(
|
85
|
+
Faraday::Request::BasicAuthentication,
|
86
|
+
self.class.api_user,
|
87
|
+
self.class.api_password,
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
# rubocop:enable Metrics/AbcSize
|
92
|
+
|
93
|
+
def setup_json!(connection)
|
94
|
+
connection.headers[:accept] = ACCEPT_HEADER
|
95
|
+
connection.request(:json)
|
96
|
+
connection.response(:json)
|
97
|
+
end
|
98
|
+
|
99
|
+
def setup_timeouts!(connection)
|
100
|
+
connection.options.open_timeout = self.class.open_timeout
|
101
|
+
connection.options.timeout = self.class.read_timeout
|
102
|
+
end
|
103
|
+
|
104
|
+
def setup_user_agent!(connection)
|
105
|
+
connection.headers[:user_agent] = self.class.user_agent
|
106
|
+
end
|
107
|
+
|
108
|
+
def setup_logger!(connection)
|
109
|
+
connection.response(:logger, self.class.logger) if self.class.logger
|
110
|
+
end
|
111
|
+
|
112
|
+
def setup_ssl(connection)
|
113
|
+
connection.ssl.verify = self.class.ssl_verify
|
114
|
+
connection.ssl.ca_file = self.class.ca_file
|
115
|
+
end
|
116
|
+
|
117
|
+
def setup_retries!(connection)
|
118
|
+
return if self.class.max_retries <= 0
|
119
|
+
|
120
|
+
connection.request(
|
121
|
+
:retry,
|
122
|
+
max: self.class.max_retries,
|
123
|
+
interval: self.class.retry_interval,
|
124
|
+
backoff_factor: self.class.retry_backoff_factor,
|
125
|
+
exceptions: self.class.retriable_errors,
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
129
|
+
def setup_failed_req_handling!(connection)
|
130
|
+
connection.use(self.class::HandleUnsuccessfulRequestMiddleware)
|
131
|
+
end
|
132
|
+
|
133
|
+
def setup_error_handling!(connection)
|
134
|
+
connection.use(self.class::ErrorHandlerMiddleware)
|
135
|
+
end
|
136
|
+
|
137
|
+
def setup_error_code_middleware!(connection)
|
138
|
+
connection.use(self.class::ErrorCodeMiddleware)
|
139
|
+
end
|
140
|
+
|
141
|
+
def customize_connection!(connection)
|
142
|
+
return unless customization_block
|
143
|
+
|
144
|
+
customization_block.call(connection)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
# rubocop:enable Metrics/ClassLength
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ApiGenerator
|
2
|
+
module Commands
|
3
|
+
class Generate
|
4
|
+
# rubocop:disable Layout/LineLength
|
5
|
+
ARGS_REGEX = /(?<namespace>[\w\-_]+):(?<action>[\w\-_]+):(?<http_method>get|post|patch|put)/i.freeze
|
6
|
+
# rubocop:enable Layout/LineLength
|
7
|
+
|
8
|
+
def initialize(argv)
|
9
|
+
@argv = argv[0..1]
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
service_args = [*parse_parameters(@argv), paginate(@argv)]
|
14
|
+
|
15
|
+
shutdown unless service_args[0..2].all?(&:present?)
|
16
|
+
|
17
|
+
Services::CreateScaffold.new(*service_args).call
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def usage
|
23
|
+
<<~USAGE
|
24
|
+
Generate API scaffold (api_namespace, models, specs)
|
25
|
+
Usage: bin/generate NAMESPACE:ACTION:HTTP_METHOD [--paginate]
|
26
|
+
Examples:
|
27
|
+
bin/generate deals:search:post --paginate
|
28
|
+
bin/generate persons:create:post
|
29
|
+
bin/generate deals:update:put
|
30
|
+
USAGE
|
31
|
+
end
|
32
|
+
|
33
|
+
def shutdown
|
34
|
+
puts(usage) || exit
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_parameters(args)
|
38
|
+
params = args.find { |arg| arg =~ ARGS_REGEX }
|
39
|
+
|
40
|
+
shutdown unless params
|
41
|
+
|
42
|
+
match_data = params.match(ARGS_REGEX)
|
43
|
+
|
44
|
+
[
|
45
|
+
match_data['namespace'],
|
46
|
+
match_data['action'],
|
47
|
+
match_data['http_method'],
|
48
|
+
]
|
49
|
+
end
|
50
|
+
|
51
|
+
def paginate(args)
|
52
|
+
args.find { |arg| arg == '--paginate' }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ApiGenerator
|
2
|
+
module Helpers
|
3
|
+
module Utils
|
4
|
+
INDENTATION_REGEX = /^([ \t]*)(?=\S)/.freeze
|
5
|
+
WRITE_MODE = 'w'.freeze
|
6
|
+
|
7
|
+
def create_dir(dir)
|
8
|
+
FileUtils.mkdir_p(dir) && log(dir)
|
9
|
+
end
|
10
|
+
|
11
|
+
def log(path)
|
12
|
+
path.gsub!(root, '.') if path.match?(Regexp.new(root))
|
13
|
+
shell.say_status('create', path, :green)
|
14
|
+
end
|
15
|
+
|
16
|
+
def shell
|
17
|
+
Thor::Shell::Color.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def copy_template(template, destination, data_binding)
|
21
|
+
content = render(template, data_binding)
|
22
|
+
destination.gsub!(/\.erb$/, '.rb')
|
23
|
+
|
24
|
+
File.write(destination, content, mode: WRITE_MODE) && log(destination)
|
25
|
+
end
|
26
|
+
|
27
|
+
def render(path, data_binding)
|
28
|
+
ERB.new(File.read(path)).result(data_binding)
|
29
|
+
end
|
30
|
+
|
31
|
+
def inject_after_str(file, str, insert)
|
32
|
+
text = File.read(file)
|
33
|
+
result = inject_after(text, str, insert)
|
34
|
+
File.write(file, result, mode: WRITE_MODE)
|
35
|
+
end
|
36
|
+
|
37
|
+
def inject_after(text, str, insert)
|
38
|
+
lines = text.each_line.to_a
|
39
|
+
regex = Regexp.new(str)
|
40
|
+
match_line_idx = lines.index { |line| line =~ regex }
|
41
|
+
|
42
|
+
return unless match_line_idx
|
43
|
+
|
44
|
+
line = lines[match_line_idx]
|
45
|
+
indented_insert = insert.each_line.map { |insert_line| indent_as(line, insert_line) }
|
46
|
+
lines.insert(match_line_idx + 1, *indented_insert)
|
47
|
+
lines.map(&:chomp).join("\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
def indent_as(source, target)
|
51
|
+
indentation = INDENTATION_REGEX.match(source).captures.first.to_s
|
52
|
+
indentation + target
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module ApiGenerator
|
2
|
+
module Middleware
|
3
|
+
class ErrorCodeMiddleware < Faraday::Response::Middleware
|
4
|
+
def self.inherited(subclass)
|
5
|
+
class << subclass
|
6
|
+
attr_accessor :error_mapping, :error_namespace
|
7
|
+
|
8
|
+
def add_error(code, exception)
|
9
|
+
error_mapping[code] = exception
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
subclass.error_mapping ||= {}
|
14
|
+
subclass.error_namespace ||= ApiGenerator::Middleware::HttpErrors
|
15
|
+
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_complete(env)
|
20
|
+
error_class = error_class(env.status)
|
21
|
+
return unless error_class
|
22
|
+
|
23
|
+
raise(error_class.new(message(env), env.body))
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def message(env)
|
29
|
+
"Server returned #{env.status}: #{env.body}. Headers: #{env.response_headers.inspect}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def error_class(code)
|
33
|
+
self.class.error_mapping.fetch(code) do
|
34
|
+
if code >= 500
|
35
|
+
self.class.error_namespace::ServerError
|
36
|
+
elsif code >= 400
|
37
|
+
self.class.error_namespace::ClientError
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ApiGenerator
|
2
|
+
module Middleware
|
3
|
+
class ErrorHandlerMiddleware < Faraday::Middleware
|
4
|
+
def self.inherited(subclass)
|
5
|
+
class << subclass
|
6
|
+
attr_accessor :error_namespace
|
7
|
+
end
|
8
|
+
|
9
|
+
subclass.error_namespace ||= ApiGenerator::Middleware::HttpErrors
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
FARADAY_CONNECTION_ERRORS = [
|
14
|
+
Faraday::TimeoutError,
|
15
|
+
Faraday::ConnectionFailed,
|
16
|
+
Faraday::SSLError,
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
@app.call(env)
|
21
|
+
rescue *FARADAY_CONNECTION_ERRORS => e
|
22
|
+
raise self.class.error_namespace::ConnectionError, e
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ApiGenerator
|
2
|
+
module Middleware
|
3
|
+
class HandleUnsuccessfulRequestMiddleware < Faraday::Response::Middleware
|
4
|
+
def self.inherited(subclass)
|
5
|
+
class << subclass
|
6
|
+
attr_accessor :error_namespace
|
7
|
+
end
|
8
|
+
|
9
|
+
subclass.error_namespace ||= ApiGenerator::Middleware::HttpErrors
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_complete(env)
|
14
|
+
response_body = env.body
|
15
|
+
|
16
|
+
return unless response_body.is_a?(Hash) && response_body['success'] == false
|
17
|
+
|
18
|
+
raise(
|
19
|
+
self.class.error_namespace::UnsuccessfulRequestError.new(
|
20
|
+
message(env), env.body,
|
21
|
+
),
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def message(env)
|
28
|
+
<<~MSG
|
29
|
+
Server is unable to process the request: #{env.request_body}.
|
30
|
+
Headers: #{env.request_headers.inspect}
|
31
|
+
Server returned: #{env.body}. Headers: #{env.response_headers.inspect}
|
32
|
+
MSG
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ApiGenerator
|
2
|
+
module Middleware
|
3
|
+
module HttpErrors
|
4
|
+
class ConnectionError < StandardError; end
|
5
|
+
|
6
|
+
class BaseError < StandardError
|
7
|
+
attr_reader :body
|
8
|
+
|
9
|
+
def initialize(msg = nil, body = nil)
|
10
|
+
@body = body if body
|
11
|
+
super(msg)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ServerError < BaseError; end
|
16
|
+
class ClientError < BaseError; end
|
17
|
+
|
18
|
+
class UnsuccessfulRequestError < ClientError; end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ApiGenerator
|
2
|
+
module Middleware
|
3
|
+
module RaiseErrorDsl
|
4
|
+
def self.extended(klass)
|
5
|
+
klass.singleton_class.class_eval { attr_accessor :error_namespace }
|
6
|
+
klass.error_namespace ||= if klass.module_parent.const_defined?(:HttpErrors)
|
7
|
+
klass.module_parent::HttpErrors
|
8
|
+
else
|
9
|
+
ApiGenerator::Middleware::HttpErrors
|
10
|
+
end
|
11
|
+
error_code_middleware = Class.new(ErrorCodeMiddleware)
|
12
|
+
error_code_middleware.error_namespace = klass.error_namespace
|
13
|
+
klass.const_set(:ErrorCodeMiddleware, error_code_middleware)
|
14
|
+
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_status(code, error:)
|
19
|
+
self::ErrorCodeMiddleware.add_error(code, error_const(code, error))
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def error_const(code, error_name)
|
25
|
+
const_name = error_name.to_s.camelize
|
26
|
+
|
27
|
+
return error_namespace.const_get(const_name) if error_namespace.const_defined?(const_name)
|
28
|
+
|
29
|
+
error_namespace.const_set(
|
30
|
+
const_name,
|
31
|
+
Class.new(code >= 500 ? error_namespace::ServerError : error_namespace::ClientError),
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ApiGenerator
|
2
|
+
module Models
|
3
|
+
class Base < Hashie::Trash
|
4
|
+
include Hashie::Extensions::Dash::Coercion
|
5
|
+
include Hashie::Extensions::Dash::IndifferentAccess
|
6
|
+
include Hashie::Extensions::IgnoreUndeclared
|
7
|
+
|
8
|
+
def self.parse_time
|
9
|
+
->(time) { Time.parse(time) if time.present? }
|
10
|
+
end
|
11
|
+
|
12
|
+
private_class_method :parse_time
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ApiGenerator
|
2
|
+
module Models
|
3
|
+
class Data < Base
|
4
|
+
property :namespace
|
5
|
+
property :action
|
6
|
+
property :http_method
|
7
|
+
property :paginate
|
8
|
+
property :app_name
|
9
|
+
property :app_short_name
|
10
|
+
|
11
|
+
def for_template
|
12
|
+
binding
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ApiGenerator::Pagination
|
2
|
+
module Dsl
|
3
|
+
STRATEGIES = {
|
4
|
+
limit_offset: LimitOffsetRelation,
|
5
|
+
page: PageRelation,
|
6
|
+
}.freeze
|
7
|
+
|
8
|
+
# rubocop:disable Metrics/MethodLength
|
9
|
+
# @param [Array<Symbol] methods список методов для применения пагинации
|
10
|
+
# @param [Symbol, Class] with стратегия пагинации (например, :limit_offset, :page)
|
11
|
+
# @param data_key ключ, по которому можно получить массив данных в ответе
|
12
|
+
# @param [Proc] on_request
|
13
|
+
# коллбэк, вызываемый при запросе и принимающий как аргументы запрос и параметры
|
14
|
+
# @param [Hash] config дополнительная конфигурация для стратегии пагинации
|
15
|
+
def paginate(*methods, with:, data_key:, on_request:, **config)
|
16
|
+
relation_class = STRATEGIES.fetch(with, with)
|
17
|
+
|
18
|
+
prepend(
|
19
|
+
Module.new do
|
20
|
+
methods.each do |method|
|
21
|
+
define_method(method) do |*args|
|
22
|
+
relation_class.new(config.merge(data_key: data_key)) do |relation|
|
23
|
+
super(*args) { |req| on_request.call(req, **relation.request_options) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end,
|
28
|
+
)
|
29
|
+
end
|
30
|
+
# rubocop:enable Metrics/MethodLength
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class ApiGenerator::Pagination::LimitOffsetRelation < ApiGenerator::Pagination::Relation
|
2
|
+
def limit(limit)
|
3
|
+
expand(limit: limit)
|
4
|
+
end
|
5
|
+
|
6
|
+
def offset(offset)
|
7
|
+
expand(offset: offset)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def next_relation
|
13
|
+
limit = config[:limit]&.call(response) || size
|
14
|
+
offset = config[:offset]&.call(response) || options[:offset].to_i
|
15
|
+
|
16
|
+
expand(offset: offset + limit)
|
17
|
+
end
|
18
|
+
|
19
|
+
def last_page?
|
20
|
+
return size < config[:limit].call(response) if config[:limit]
|
21
|
+
|
22
|
+
data.empty?
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class ApiGenerator::Pagination::PageRelation < ApiGenerator::Pagination::Relation
|
2
|
+
def page(page)
|
3
|
+
expand(page: page)
|
4
|
+
end
|
5
|
+
|
6
|
+
def per(size)
|
7
|
+
expand(per_page: size)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def next_relation
|
13
|
+
expand(page: [options[:page].to_i, 1].max + 1)
|
14
|
+
end
|
15
|
+
|
16
|
+
def last_page?
|
17
|
+
return data.size < config[:per_page].call(response) if config[:per_page]
|
18
|
+
|
19
|
+
data.empty?
|
20
|
+
end
|
21
|
+
end
|