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