zleb 0.1.6
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/.gitignore +11 -0
- data/.rake_tasks~ +7 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +131 -0
- data/LICENSE.txt +21 -0
- data/README.md +40 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/build.sh +1 -0
- data/config/errors.yml +218 -0
- data/lib/zleb/controller.rb +33 -0
- data/lib/zleb/plugins/api_route.rb +21 -0
- data/lib/zleb/plugins/basic_restful_action.rb +212 -0
- data/lib/zleb/plugins/dry_schema_enhance.rb +44 -0
- data/lib/zleb/plugins/http_auth.rb +70 -0
- data/lib/zleb/plugins/method_route.rb +59 -0
- data/lib/zleb/plugins/params_check.rb +312 -0
- data/lib/zleb/plugins/params_snake_keys.rb +33 -0
- data/lib/zleb/plugins/render_static.rb +142 -0
- data/lib/zleb/plugins/schema_compiler.rb +293 -0
- data/lib/zleb/plugins/uni_logger.rb +18 -0
- data/lib/zleb/symbolize_helper.rb +19 -0
- data/lib/zleb/version.rb +3 -0
- data/lib/zleb.rb +133 -0
- data/push.sh +3 -0
- data/zleb.gemspec +43 -0
- metadata +248 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
module BasicRestfulAction
|
|
7
|
+
extend Dry::Container::Mixin
|
|
8
|
+
|
|
9
|
+
def self.configure(config)
|
|
10
|
+
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.load_dependencies(app, _opts = OPTS)
|
|
14
|
+
app.plugin :params_check
|
|
15
|
+
app.plugin :symbolized_params
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# module included in the Roda class
|
|
19
|
+
module ClassMethods
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Config
|
|
25
|
+
def initialize()
|
|
26
|
+
@config = {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def [](name)
|
|
30
|
+
@config[name]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def method_missing(meth, *args, &block)
|
|
34
|
+
if meth =~ /(\w+)=$/
|
|
35
|
+
@config[$1.to_sym] = args[0]
|
|
36
|
+
else
|
|
37
|
+
if block
|
|
38
|
+
@config[meth] = block
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# baisc_restful_action(:db_config, DBConfig) do |config|
|
|
45
|
+
# config.create_field_checker do
|
|
46
|
+
# required(:type).filled(:string)
|
|
47
|
+
# end
|
|
48
|
+
# config.unique_check_field_names = [:name]
|
|
49
|
+
# config.more_create_properties = {user: id}
|
|
50
|
+
|
|
51
|
+
# config.modify_field_checker do
|
|
52
|
+
# required(:type).filled(:string)
|
|
53
|
+
# end
|
|
54
|
+
|
|
55
|
+
# config.more_query_properties = {user: id}
|
|
56
|
+
# end
|
|
57
|
+
|
|
58
|
+
module InstanceMethods
|
|
59
|
+
def baisc_restful_action(name, model_class)
|
|
60
|
+
r = request
|
|
61
|
+
config = Config.new
|
|
62
|
+
yield config
|
|
63
|
+
|
|
64
|
+
begin
|
|
65
|
+
r.is do
|
|
66
|
+
#create
|
|
67
|
+
r.post do
|
|
68
|
+
desc "#{name}_create"
|
|
69
|
+
input_params = params
|
|
70
|
+
if config[:create_field_checker]
|
|
71
|
+
params_check :"#{name}_create" do
|
|
72
|
+
params(&config[:create_field_checker])
|
|
73
|
+
end
|
|
74
|
+
input_params = checked_params
|
|
75
|
+
end
|
|
76
|
+
resource_field_filter_define(config)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
resource_unique_check(name, config, input_params, model_class)
|
|
80
|
+
|
|
81
|
+
resouce_properties = input_params
|
|
82
|
+
if config[:more_create_properties]
|
|
83
|
+
resouce_properties = resouce_properties.merge(config[:more_create_properties])
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
resouce = model_class.create(resouce_properties)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
#get all
|
|
90
|
+
r.get do
|
|
91
|
+
desc "#{name}_list"
|
|
92
|
+
|
|
93
|
+
condition = params
|
|
94
|
+
if config[:more_query_properties]
|
|
95
|
+
condition = condition.merge(config[:more_query_properties])
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
resource_field_filter_define(config, :array)
|
|
99
|
+
|
|
100
|
+
resources = model_class.where(condition).order_by(created_at: -1)
|
|
101
|
+
|
|
102
|
+
result = []
|
|
103
|
+
resources.each do |item|
|
|
104
|
+
result << item.to_h
|
|
105
|
+
end
|
|
106
|
+
result
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
r.is String do |id|
|
|
111
|
+
|
|
112
|
+
unless is_interface_info_request?
|
|
113
|
+
resource = model_class.find(id)
|
|
114
|
+
checker = config[:resource_permission_checker]
|
|
115
|
+
if checker
|
|
116
|
+
unless checker.call(resource)
|
|
117
|
+
response.status = 403
|
|
118
|
+
return {error: "not allowed"}
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# delete one
|
|
124
|
+
r.delete do
|
|
125
|
+
desc "#{name}_delete"
|
|
126
|
+
output_filter do
|
|
127
|
+
required(:success).filled(:string)
|
|
128
|
+
required(:id).filled(:string)
|
|
129
|
+
end
|
|
130
|
+
resource.destroy
|
|
131
|
+
{ success: "deleted", id: id }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# get one
|
|
135
|
+
r.get do
|
|
136
|
+
desc "#{name}_get"
|
|
137
|
+
resource_field_filter_define(config)
|
|
138
|
+
resource
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
#modified one
|
|
142
|
+
r.patch do
|
|
143
|
+
desc "#{name}_modify"
|
|
144
|
+
|
|
145
|
+
input_params = params
|
|
146
|
+
if config[:modify_field_checker]
|
|
147
|
+
params_check :"#{name}_modify" do
|
|
148
|
+
params(&config[:modify_field_checker])
|
|
149
|
+
end
|
|
150
|
+
input_params = checked_params
|
|
151
|
+
end
|
|
152
|
+
resource_field_filter_define(config)
|
|
153
|
+
|
|
154
|
+
resource_unique_check(name, config, resource.to_h.merge(input_params), model_class, [id])
|
|
155
|
+
resouce_properties = input_params
|
|
156
|
+
if config[:more_update_properties]
|
|
157
|
+
resouce_properties = resouce_properties.merge(config[:more_update_properties])
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
resource.update_attributes(resouce_properties)
|
|
161
|
+
resource.save
|
|
162
|
+
|
|
163
|
+
resource
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
rescue Mongoid::Errors::DocumentNotFound => doc_not_found
|
|
167
|
+
response.status = 400
|
|
168
|
+
{ error: "#{name} not existed" }
|
|
169
|
+
rescue Mongo::Error::OperationFailure => op_error
|
|
170
|
+
response.status = 400
|
|
171
|
+
{ error: op_error.message }
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
def resource_unique_check(resource_class_name, config, input_params, model_class, exclude_items = [])
|
|
178
|
+
if config[:unique_check_field_names]
|
|
179
|
+
unique_condition = {}
|
|
180
|
+
config[:unique_check_field_names].each do |data_name|
|
|
181
|
+
if data_name.class == Hash
|
|
182
|
+
data_name.each do |n, v|
|
|
183
|
+
unique_condition[n] = v
|
|
184
|
+
end
|
|
185
|
+
else
|
|
186
|
+
unique_condition[data_name] = input_params[data_name]
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
if model_class.not.in(id: exclude_items).where(unique_condition).first
|
|
191
|
+
response.status = 400
|
|
192
|
+
response['Content-Type'] = 'application/json'
|
|
193
|
+
response.write({ error: "#{resource_class_name} already existed: #{unique_condition}", conflict_field: unique_condition}.to_json)
|
|
194
|
+
throw :halt, response.finish
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def resource_field_filter_define(config, type = :hash)
|
|
200
|
+
if config[:resource_field_filter]
|
|
201
|
+
output_filter(type, &config[:resource_field_filter])
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
register_plugin(:basic_restful_action, BasicRestfulAction)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require "dry/schema"
|
|
2
|
+
|
|
3
|
+
# module Dry
|
|
4
|
+
# module Schema
|
|
5
|
+
# module Macros
|
|
6
|
+
# class Key < DSL
|
|
7
|
+
# def desc(*args)
|
|
8
|
+
# #just solution for call error
|
|
9
|
+
# end
|
|
10
|
+
# end
|
|
11
|
+
# end
|
|
12
|
+
# end
|
|
13
|
+
# end
|
|
14
|
+
|
|
15
|
+
module Dry
|
|
16
|
+
module Schema
|
|
17
|
+
module Messages
|
|
18
|
+
class Abstract
|
|
19
|
+
# Abstract class for message backends
|
|
20
|
+
#
|
|
21
|
+
# @api public
|
|
22
|
+
def get_message_for_key(predicate, options)
|
|
23
|
+
options = { locale: default_locale, **options }
|
|
24
|
+
opts = options.reject { |k, | config.lookup_options.include?(k) }
|
|
25
|
+
path = lookup_paths(predicate, options).detect { |key| key?(key, opts) }
|
|
26
|
+
|
|
27
|
+
return unless path
|
|
28
|
+
|
|
29
|
+
result = get(path, opts)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Dry::Schema::Macros::DSL
|
|
38
|
+
def default(value)
|
|
39
|
+
schema_dsl.before(:rule_applier) do |result|
|
|
40
|
+
result.update(name => value) if result.output && !result[name]
|
|
41
|
+
end
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# from gem 'roda-http-auth'
|
|
2
|
+
# plugin :http_auth, authenticator: ->(user, pass) { [user, pass] == %w[foo bar] },
|
|
3
|
+
# realm: 'Restricted Area', # default
|
|
4
|
+
# schemes: %w[basic] # default
|
|
5
|
+
|
|
6
|
+
module Roda::RodaPlugins
|
|
7
|
+
module HttpAuth
|
|
8
|
+
DEFAULTS = {
|
|
9
|
+
realm: "Restricted Area",
|
|
10
|
+
unauthorized_headers: ->(opts) do
|
|
11
|
+
{ 'WWW-Authenticate' => ('Basic realm="%s"' % opts[:realm]) }
|
|
12
|
+
end,
|
|
13
|
+
unauthorized: ->(r) {},
|
|
14
|
+
schemes: %w[basic]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def self.configure(app, opts={})
|
|
18
|
+
plugin_opts = (app.opts[:http_auth] ||= DEFAULTS)
|
|
19
|
+
app.opts[:http_auth] = plugin_opts.merge(opts)
|
|
20
|
+
app.opts[:http_auth].freeze
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module InstanceMethods
|
|
24
|
+
def http_auth(opts={}, &authenticator)
|
|
25
|
+
auth_opts = request.roda_class.opts[:http_auth].merge(opts)
|
|
26
|
+
authenticator ||= auth_opts[:authenticator]
|
|
27
|
+
|
|
28
|
+
raise "Must provide an authenticator block" if authenticator.nil?
|
|
29
|
+
|
|
30
|
+
auth = Rack::Auth::Basic::Request.new(env)
|
|
31
|
+
|
|
32
|
+
unless auth.provided? && auth_opts[:schemes].include?(auth.scheme)
|
|
33
|
+
unauthorized(auth_opts)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
credentials = if auth.basic?
|
|
37
|
+
auth.credentials
|
|
38
|
+
elsif auth.scheme == 'bearer'
|
|
39
|
+
[env['HTTP_AUTHORIZATION'].split(' ', 2).last]
|
|
40
|
+
else
|
|
41
|
+
http_auth = env['HTTP_AUTHORIZATION'].split(' ', 2)
|
|
42
|
+
.last
|
|
43
|
+
|
|
44
|
+
creds = !http_auth.include?('=') ? http_auth :
|
|
45
|
+
Rack::Auth::Digest::Params.parse(http_auth)
|
|
46
|
+
|
|
47
|
+
[auth.scheme, creds]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if authenticator.call(*credentials)
|
|
51
|
+
env['REMOTE_USER'] = auth.username
|
|
52
|
+
else
|
|
53
|
+
unauthorized(auth_opts)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def unauthorized(opts)
|
|
60
|
+
response.status = 401
|
|
61
|
+
response.headers.merge!(opts[:unauthorized_headers].call(opts))
|
|
62
|
+
|
|
63
|
+
request.block_result(instance_exec(request, &opts[:unauthorized]))
|
|
64
|
+
request.halt response.finish
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
register_plugin(:http_auth, HttpAuth)
|
|
70
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require 'semantic_logger'
|
|
2
|
+
|
|
3
|
+
class Roda
|
|
4
|
+
module RodaPlugins
|
|
5
|
+
module MethodRoute
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def self.load_dependencies(app, _opts = OPTS)
|
|
9
|
+
app.plugin :hash_routes
|
|
10
|
+
app.plugin :json_parser
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module InstanceMethods
|
|
14
|
+
include SemanticLogger::Loggable
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module RequestMethods
|
|
19
|
+
def method_to(mod)
|
|
20
|
+
r_path = remaining_path
|
|
21
|
+
method_name = nil
|
|
22
|
+
result = nil
|
|
23
|
+
if r_path != ""
|
|
24
|
+
segments = r_path.split("/")
|
|
25
|
+
segments.shift
|
|
26
|
+
first_seg = segments.shift
|
|
27
|
+
first_seg[0] = first_seg[0].upcase
|
|
28
|
+
begin
|
|
29
|
+
klass = mod.const_get(first_seg, false)
|
|
30
|
+
rescue NameError => e
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
segments.unshift(request_method.downcase)
|
|
34
|
+
method_name = segments.join("_").to_sym
|
|
35
|
+
|
|
36
|
+
processer = klass.new({request: self, response: response, headers: headers, params: params, scope: scope})
|
|
37
|
+
if processer.respond_to?(:init)
|
|
38
|
+
processer.init
|
|
39
|
+
end
|
|
40
|
+
if processer.respond_to?(method_name)
|
|
41
|
+
result = processer.send(method_name)
|
|
42
|
+
else
|
|
43
|
+
return
|
|
44
|
+
end
|
|
45
|
+
block_result(result)
|
|
46
|
+
halt
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
module ClassMethods
|
|
53
|
+
include SemanticLogger::Loggable
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
register_plugin(:method_route, MethodRoute)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
require 'dry-container'
|
|
2
|
+
require 'dry-validation'
|
|
3
|
+
require_relative 'schema_compiler'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Hash
|
|
7
|
+
def select_items(*keys)
|
|
8
|
+
result = {}
|
|
9
|
+
keys.each do |k|
|
|
10
|
+
if has_key?(k)
|
|
11
|
+
result[k] = self[k]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
result
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def reject_items(*keys)
|
|
18
|
+
result = self.clone
|
|
19
|
+
keys.each do |k|
|
|
20
|
+
result.delete(k)
|
|
21
|
+
end
|
|
22
|
+
result
|
|
23
|
+
end
|
|
24
|
+
def deep_merge(second)
|
|
25
|
+
merger = proc { |_, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
|
|
26
|
+
merge(second.to_h, &merger)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Roda
|
|
33
|
+
module RodaPlugins
|
|
34
|
+
module ParamsCheck
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
module RequestCheckerContainer
|
|
38
|
+
extend Dry::Container::Mixin
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
module ResponseEntityFilterContainer
|
|
42
|
+
extend Dry::Container::Mixin
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.configure(config)
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.load_dependencies(app, _opts = OPTS)
|
|
50
|
+
app.plugin :request_headers
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# build-in types
|
|
55
|
+
# Types::Nominal::Any
|
|
56
|
+
# Types::Nominal::Nil
|
|
57
|
+
# Types::Nominal::Symbol
|
|
58
|
+
# Types::Nominal::Class
|
|
59
|
+
# Types::Nominal::True
|
|
60
|
+
# Types::Nominal::False
|
|
61
|
+
# Types::Nominal::Bool
|
|
62
|
+
# Types::Nominal::Integer
|
|
63
|
+
# Types::Nominal::Float
|
|
64
|
+
# Types::Nominal::Decimal
|
|
65
|
+
# Types::Nominal::String
|
|
66
|
+
# Types::Nominal::Date
|
|
67
|
+
# Types::Nominal::DateTime
|
|
68
|
+
# Types::Nominal::Time
|
|
69
|
+
# Types::Nominal::Array
|
|
70
|
+
# Types::Nominal::Hash
|
|
71
|
+
|
|
72
|
+
#添加类型
|
|
73
|
+
module Types
|
|
74
|
+
include Dry::Types()
|
|
75
|
+
|
|
76
|
+
StrippedString = Types::String.constructor(&:strip)
|
|
77
|
+
end
|
|
78
|
+
TypeContainer = Dry::Schema::TypeContainer.new
|
|
79
|
+
TypeContainer.register('params.stripped_string', Types::StrippedString)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
#定义contract 来校验
|
|
85
|
+
class ApplicationContract < Dry::Validation::Contract
|
|
86
|
+
config.messages.default_locale = :cn
|
|
87
|
+
config.messages.load_paths << File.join(__dir__, "..", "..", "..", 'config', 'errors.yml')
|
|
88
|
+
|
|
89
|
+
def self.params(&block)
|
|
90
|
+
add_config_block = Proc.new do
|
|
91
|
+
config.types = TypeContainer
|
|
92
|
+
self.instance_eval(&block)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
param_schema = Dry::Schema.Params(&add_config_block)
|
|
96
|
+
extended_info = DrySchemaExtend::SchemaExtendEdInfo.define(&block)
|
|
97
|
+
|
|
98
|
+
schema_info = DrySchemaExtend::SchemaInfoCompiler.new(param_schema)
|
|
99
|
+
schema_info.call
|
|
100
|
+
schema_info = schema_info.to_h
|
|
101
|
+
|
|
102
|
+
@schema_info = extended_info.get_desc.deep_merge(schema_info[:children])
|
|
103
|
+
|
|
104
|
+
super(&add_config_block)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.schema_info
|
|
108
|
+
@schema_info
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
module ClassMethods
|
|
114
|
+
|
|
115
|
+
def def_checker(&block)
|
|
116
|
+
block
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def def_pub_checker(name, &block)
|
|
120
|
+
if !(RequestCheckerContainer.key?(name))
|
|
121
|
+
contract = Class.new(ApplicationContract, &block)
|
|
122
|
+
RequestCheckerContainer.register(name, contract.new)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
OutputEncloseName = :output
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
module InstanceMethods
|
|
131
|
+
|
|
132
|
+
def params_check(name=nil, &block)
|
|
133
|
+
if name == nil
|
|
134
|
+
name = "#{request.request_method} #{request.path}"
|
|
135
|
+
end
|
|
136
|
+
if !(RequestCheckerContainer.key?(name))
|
|
137
|
+
contract = Class.new(ApplicationContract, &block)
|
|
138
|
+
begin
|
|
139
|
+
RequestCheckerContainer.register(name, contract.new)
|
|
140
|
+
rescue Dry::Container::Error => e
|
|
141
|
+
puts e.message
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
@input_checkers ||= []
|
|
145
|
+
checker = RequestCheckerContainer.resolve(name)
|
|
146
|
+
@input_checkers << checker
|
|
147
|
+
|
|
148
|
+
interface_info_flag = request.headers["Interface-Info"]
|
|
149
|
+
if interface_info_flag
|
|
150
|
+
input_params_schema_info = {}
|
|
151
|
+
@input_checkers.each do |ch|
|
|
152
|
+
input_params_schema_info = input_params_schema_info.deep_merge(ch.class.schema_info)
|
|
153
|
+
end
|
|
154
|
+
interface_info[:input_schema] = input_params_schema_info
|
|
155
|
+
|
|
156
|
+
return
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
result = checker.(request.params)
|
|
160
|
+
if (result.failure?)
|
|
161
|
+
response.status = 400
|
|
162
|
+
response['Content-Type'] = 'application/json'
|
|
163
|
+
response.write({error: result.errors.to_h }.to_json)
|
|
164
|
+
throw :halt, response.finish
|
|
165
|
+
end
|
|
166
|
+
@_checked_params = result
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def process(&block)
|
|
170
|
+
if is_interface_info_request?
|
|
171
|
+
interface_info
|
|
172
|
+
else
|
|
173
|
+
self.instance_exec &block
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def is_interface_info_request?
|
|
179
|
+
request.headers["Interface-Info"] != nil
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def output_filter(type = :hash, safe = true, name = nil, &block)
|
|
183
|
+
request.output_entity_filter_safe = safe
|
|
184
|
+
if name == nil
|
|
185
|
+
name = "#{request.request_method} #{request.matched_path}"
|
|
186
|
+
end
|
|
187
|
+
if type == :array
|
|
188
|
+
contract_block = Proc.new do
|
|
189
|
+
params do
|
|
190
|
+
required(OutputEncloseName).array(:hash, &block)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
else
|
|
194
|
+
contract_block = Proc.new do
|
|
195
|
+
params do
|
|
196
|
+
required(OutputEncloseName).hash(&block)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
if !(ResponseEntityFilterContainer.key?(name))
|
|
201
|
+
contract = Class.new(ApplicationContract, &contract_block)
|
|
202
|
+
ResponseEntityFilterContainer.register(name, contract.new)
|
|
203
|
+
end
|
|
204
|
+
filter = ResponseEntityFilterContainer.resolve(name)
|
|
205
|
+
|
|
206
|
+
interface_info_flag = request.headers["Interface-Info"]
|
|
207
|
+
if interface_info_flag
|
|
208
|
+
output_entity_schema_info = filter.class.schema_info
|
|
209
|
+
interface_info[:output_schema] = output_entity_schema_info
|
|
210
|
+
|
|
211
|
+
response.status = 200
|
|
212
|
+
response['Content-Type'] = 'application/json'
|
|
213
|
+
|
|
214
|
+
res_data = nil
|
|
215
|
+
if interface_info_flag == 'output'
|
|
216
|
+
res_data = {output_schema: output_entity_schema_info}
|
|
217
|
+
elsif interface_info_flag == 'input'
|
|
218
|
+
res_data = {input_schema: @interface_info[:input_schema]}
|
|
219
|
+
elsif interface_info_flag == 'mock'
|
|
220
|
+
res_data = mock_data_extract(output_entity_schema_info)[OutputEncloseName]
|
|
221
|
+
elsif interface_info_flag == 'all'
|
|
222
|
+
res_data = @interface_info
|
|
223
|
+
else
|
|
224
|
+
res_data = {}
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
response.write(res_data.to_json)
|
|
228
|
+
throw :halt, response.finish
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
@output_entity_filter = filter
|
|
232
|
+
request.output_entity_filter = filter
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def desc(description)
|
|
236
|
+
@interface_info = {desc: description}
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def def_checker(&block)
|
|
240
|
+
block
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def checked_params
|
|
244
|
+
@_checked_params.to_h
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
private
|
|
248
|
+
|
|
249
|
+
def interface_info
|
|
250
|
+
@interface_info ||= {}
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def mock_data_extract(schema_data)
|
|
254
|
+
result = {}
|
|
255
|
+
schema_data.each do |name, value|
|
|
256
|
+
if value[:member]
|
|
257
|
+
result[name] = [mock_data_extract(value[:member][:children])]
|
|
258
|
+
elsif value[:children]
|
|
259
|
+
result[name] = mock_data_extract(value[:children])
|
|
260
|
+
else
|
|
261
|
+
if value[:desc]
|
|
262
|
+
result[name] = value[:desc][:value]
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
result
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
module RequestMethods
|
|
271
|
+
|
|
272
|
+
def output_entity_filter=(output_entity_filter)
|
|
273
|
+
@output_entity_filter = output_entity_filter
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def output_entity_filter_safe=(safe)
|
|
277
|
+
@output_entity_filter_safe = safe
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
private
|
|
281
|
+
# If the result is an instance of one of the json_result_classes,
|
|
282
|
+
# convert the result to json and return it as the body, using the
|
|
283
|
+
# application/json content-type.
|
|
284
|
+
def block_result_body(result)
|
|
285
|
+
|
|
286
|
+
if @output_entity_filter and (response.status == nil or response.status < 300)
|
|
287
|
+
filter_data = @output_entity_filter.({OutputEncloseName => result})
|
|
288
|
+
if (filter_data.failure? and @output_entity_filter_safe == true)
|
|
289
|
+
response.status = 417
|
|
290
|
+
response['Content-Type'] = 'application/json'
|
|
291
|
+
response.write({error: filter_data.errors.to_h}.to_json)
|
|
292
|
+
throw :halt, response.finish
|
|
293
|
+
else
|
|
294
|
+
if filter_data.failure?
|
|
295
|
+
result = result
|
|
296
|
+
else
|
|
297
|
+
result = filter_data[OutputEncloseName]
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
super(result)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
register_plugin(:params_check, ParamsCheck)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
|