haveapi 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +12 -0
- data/Gemfile +10 -10
- data/README.md +19 -12
- data/Rakefile +23 -0
- data/doc/hooks.erb +35 -0
- data/doc/index.md +1 -0
- data/doc/json-schema.erb +369 -0
- data/doc/protocol.md +178 -38
- data/doc/protocol.plantuml +220 -0
- data/haveapi.gemspec +6 -10
- data/lib/haveapi/action.rb +35 -6
- data/lib/haveapi/api.rb +22 -5
- data/lib/haveapi/common.rb +7 -0
- data/lib/haveapi/exceptions.rb +7 -0
- data/lib/haveapi/hooks.rb +19 -8
- data/lib/haveapi/model_adapters/active_record.rb +58 -19
- data/lib/haveapi/output_formatter.rb +8 -5
- data/lib/haveapi/output_formatters/json.rb +6 -1
- data/lib/haveapi/params/param.rb +33 -39
- data/lib/haveapi/params/resource.rb +20 -0
- data/lib/haveapi/params.rb +26 -4
- data/lib/haveapi/public/doc/protocol.png +0 -0
- data/lib/haveapi/resource.rb +2 -7
- data/lib/haveapi/server.rb +87 -26
- data/lib/haveapi/tasks/hooks.rb +3 -0
- data/lib/haveapi/tasks/yard.rb +12 -0
- data/lib/haveapi/validator.rb +134 -0
- data/lib/haveapi/validator_chain.rb +99 -0
- data/lib/haveapi/validators/acceptance.rb +38 -0
- data/lib/haveapi/validators/confirmation.rb +46 -0
- data/lib/haveapi/validators/custom.rb +21 -0
- data/lib/haveapi/validators/exclusion.rb +38 -0
- data/lib/haveapi/validators/format.rb +42 -0
- data/lib/haveapi/validators/inclusion.rb +42 -0
- data/lib/haveapi/validators/length.rb +71 -0
- data/lib/haveapi/validators/numericality.rb +104 -0
- data/lib/haveapi/validators/presence.rb +40 -0
- data/lib/haveapi/version.rb +2 -1
- data/lib/haveapi/views/doc_sidebars/json-schema.erb +7 -0
- data/lib/haveapi/views/main_layout.erb +11 -0
- data/lib/haveapi/views/version_page.erb +26 -3
- data/lib/haveapi.rb +7 -4
- metadata +45 -66
data/lib/haveapi/server.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'redcarpet'
|
3
|
+
|
1
4
|
module HaveAPI
|
2
5
|
class Server
|
3
6
|
attr_reader :root, :routes, :module_name, :auth_chain, :versions, :default_version,
|
@@ -7,7 +10,11 @@ module HaveAPI
|
|
7
10
|
|
8
11
|
# Called after the user was authenticated (or not). The block is passed
|
9
12
|
# current user object or nil as an argument.
|
10
|
-
has_hook :post_authenticated
|
13
|
+
has_hook :post_authenticated,
|
14
|
+
desc: 'Called after the user was authenticated',
|
15
|
+
args: {
|
16
|
+
current_user: 'object returned by the authentication backend',
|
17
|
+
}
|
11
18
|
|
12
19
|
module ServerHelpers
|
13
20
|
def authenticate!(v)
|
@@ -51,7 +58,7 @@ module HaveAPI
|
|
51
58
|
def report_error(code, headers, msg)
|
52
59
|
@halted = true
|
53
60
|
content_type @formatter.content_type, charset: 'utf-8'
|
54
|
-
halt code, headers, @formatter.format(false, nil, msg)
|
61
|
+
halt code, headers, @formatter.format(false, nil, msg, version: false)
|
55
62
|
end
|
56
63
|
|
57
64
|
def root
|
@@ -90,7 +97,7 @@ module HaveAPI
|
|
90
97
|
@versions ||= []
|
91
98
|
|
92
99
|
if v == :all
|
93
|
-
@versions = HaveAPI.
|
100
|
+
@versions = HaveAPI.versions(@module_name)
|
94
101
|
elsif v.is_a?(Array)
|
95
102
|
@versions += v
|
96
103
|
@versions.uniq!
|
@@ -101,7 +108,7 @@ module HaveAPI
|
|
101
108
|
end
|
102
109
|
|
103
110
|
# Set default version of API.
|
104
|
-
def
|
111
|
+
def default_version=(v)
|
105
112
|
@default_version = v
|
106
113
|
end
|
107
114
|
|
@@ -152,8 +159,11 @@ module HaveAPI
|
|
152
159
|
@sinatra.get @root do
|
153
160
|
authenticated?(settings.api_server.default_version)
|
154
161
|
|
155
|
-
@api = settings.api_server.describe(Context.new(
|
156
|
-
|
162
|
+
@api = settings.api_server.describe(Context.new(
|
163
|
+
settings.api_server,
|
164
|
+
user: current_user,
|
165
|
+
params: params
|
166
|
+
))
|
157
167
|
|
158
168
|
content_type 'text/html'
|
159
169
|
erb :index, layout: :main_layout
|
@@ -166,16 +176,24 @@ module HaveAPI
|
|
166
176
|
|
167
177
|
case params[:describe]
|
168
178
|
when 'versions'
|
169
|
-
ret = {
|
170
|
-
|
179
|
+
ret = {
|
180
|
+
versions: settings.api_server.versions,
|
181
|
+
default: settings.api_server.default_version
|
182
|
+
}
|
171
183
|
|
172
184
|
when 'default'
|
173
|
-
ret = settings.api_server.describe_version(Context.new(
|
174
|
-
|
185
|
+
ret = settings.api_server.describe_version(Context.new(
|
186
|
+
settings.api_server,
|
187
|
+
version: settings.api_server.default_version,
|
188
|
+
user: current_user, params: params
|
189
|
+
))
|
175
190
|
|
176
191
|
else
|
177
|
-
ret = settings.api_server.describe(Context.new(
|
178
|
-
|
192
|
+
ret = settings.api_server.describe(Context.new(
|
193
|
+
settings.api_server,
|
194
|
+
user: current_user,
|
195
|
+
params: params
|
196
|
+
))
|
179
197
|
end
|
180
198
|
|
181
199
|
@formatter.format(true, ret)
|
@@ -195,6 +213,14 @@ module HaveAPI
|
|
195
213
|
GitHub::Markdown.render(File.new(settings.views + '/../../../README.md').read)
|
196
214
|
end
|
197
215
|
end
|
216
|
+
|
217
|
+
@sinatra.get "#{@root}doc/json-schema" do
|
218
|
+
content_type 'text/html'
|
219
|
+
erb :doc_layout, layout: :main_layout do
|
220
|
+
@content = erb :'../../../doc/json-schema'
|
221
|
+
@sidebar = erb :'doc_sidebars/json-schema'
|
222
|
+
end
|
223
|
+
end
|
198
224
|
|
199
225
|
@sinatra.get %r{#{@root}doc/([^\.]+)[\.md]?} do |f|
|
200
226
|
content_type 'text/html'
|
@@ -244,8 +270,12 @@ module HaveAPI
|
|
244
270
|
authenticated?(v)
|
245
271
|
|
246
272
|
@v = v
|
247
|
-
@help = settings.api_server.describe_version(Context.new(
|
248
|
-
|
273
|
+
@help = settings.api_server.describe_version(Context.new(
|
274
|
+
settings.api_server,
|
275
|
+
version: v,
|
276
|
+
user: current_user,
|
277
|
+
params: params
|
278
|
+
))
|
249
279
|
content_type 'text/html'
|
250
280
|
erb :doc_layout, layout: :main_layout do
|
251
281
|
@content = erb :version_page
|
@@ -257,13 +287,29 @@ module HaveAPI
|
|
257
287
|
access_control
|
258
288
|
authenticated?(v)
|
259
289
|
|
260
|
-
@formatter.format(true, settings.api_server.describe_version(Context.new(
|
261
|
-
|
290
|
+
@formatter.format(true, settings.api_server.describe_version(Context.new(
|
291
|
+
settings.api_server,
|
292
|
+
version: v,
|
293
|
+
user: current_user,
|
294
|
+
params: params
|
295
|
+
)))
|
262
296
|
end
|
263
297
|
|
264
298
|
HaveAPI.get_version_resources(@module_name, v).each do |resource|
|
265
299
|
mount_resource(prefix, v, resource, @routes[v][:resources])
|
266
300
|
end
|
301
|
+
|
302
|
+
validate_resources(@routes[v][:resources])
|
303
|
+
end
|
304
|
+
|
305
|
+
def validate_resources(resources)
|
306
|
+
resources.each_value do |r|
|
307
|
+
r[:actions].each_key do |a|
|
308
|
+
a.validate_build
|
309
|
+
end
|
310
|
+
|
311
|
+
validate_resources(r[:resources])
|
312
|
+
end
|
267
313
|
end
|
268
314
|
|
269
315
|
def mount_resource(prefix, v, resource, hash)
|
@@ -271,7 +317,10 @@ module HaveAPI
|
|
271
317
|
|
272
318
|
resource.routes(prefix).each do |route|
|
273
319
|
if route.is_a?(Hash)
|
274
|
-
hash[resource][:resources][route.keys.first] = mount_nested_resource(
|
320
|
+
hash[resource][:resources][route.keys.first] = mount_nested_resource(
|
321
|
+
v,
|
322
|
+
route.values.first
|
323
|
+
)
|
275
324
|
|
276
325
|
else
|
277
326
|
hash[resource][:actions][route.action] = route.url
|
@@ -315,10 +364,15 @@ module HaveAPI
|
|
315
364
|
report_error(400, {}, 'Bad JSON syntax')
|
316
365
|
end
|
317
366
|
|
318
|
-
action = route.action.new(request, v, params, body, Context.new(
|
319
|
-
|
320
|
-
|
321
|
-
|
367
|
+
action = route.action.new(request, v, params, body, Context.new(
|
368
|
+
settings.api_server,
|
369
|
+
version: v,
|
370
|
+
action: route.action,
|
371
|
+
url: route.url,
|
372
|
+
params: params,
|
373
|
+
user: current_user,
|
374
|
+
endpoint: true
|
375
|
+
))
|
322
376
|
|
323
377
|
unless action.authorized?(current_user)
|
324
378
|
report_error(403, {}, 'Access denied. Insufficient permissions.')
|
@@ -330,7 +384,8 @@ module HaveAPI
|
|
330
384
|
status,
|
331
385
|
status ? reply : nil,
|
332
386
|
!status ? reply : nil,
|
333
|
-
errors
|
387
|
+
errors,
|
388
|
+
version: false
|
334
389
|
)
|
335
390
|
end
|
336
391
|
|
@@ -343,10 +398,16 @@ module HaveAPI
|
|
343
398
|
authenticate!(v) if route.action.auth
|
344
399
|
|
345
400
|
begin
|
346
|
-
desc = route.action.describe(Context.new(
|
347
|
-
|
348
|
-
|
349
|
-
|
401
|
+
desc = route.action.describe(Context.new(
|
402
|
+
settings.api_server,
|
403
|
+
version: v,
|
404
|
+
action: route.action,
|
405
|
+
url: route.url,
|
406
|
+
args: args,
|
407
|
+
params: params,
|
408
|
+
user: current_user,
|
409
|
+
endpoint: true
|
410
|
+
))
|
350
411
|
|
351
412
|
unless desc
|
352
413
|
report_error(403, {}, 'Access denied. Insufficient permissions.')
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
# Validators are stored in this module.
|
3
|
+
module Validators ; end
|
4
|
+
|
5
|
+
# Base class for all validators.
|
6
|
+
#
|
7
|
+
# All validators can have a short and a full form. The short form is used
|
8
|
+
# when default configuration is sufficient. Custom settings can be set using
|
9
|
+
# the full form.
|
10
|
+
#
|
11
|
+
# The short form means the validator is configured as +<option> => <single value>+.
|
12
|
+
# The full form is +<option> => { hash with configuration options }+.
|
13
|
+
#
|
14
|
+
# It is up to each validator what exactly the short form means and what options
|
15
|
+
# can be set. Specify only those options that you wish to override. The only
|
16
|
+
# common option is +message+ - the error message sent to the client if the provided
|
17
|
+
# value did not pass the validator.
|
18
|
+
#
|
19
|
+
# The +message+ can contain +%{value}+, which is replaced by the actual value
|
20
|
+
# that did not pass the validator.
|
21
|
+
class Validator
|
22
|
+
class << self
|
23
|
+
# Set validator name used in API documentation.
|
24
|
+
def name(v = nil)
|
25
|
+
if v
|
26
|
+
@name = v
|
27
|
+
|
28
|
+
else
|
29
|
+
@name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Specify options this validator takes from the parameter definition.
|
34
|
+
def takes(*opts)
|
35
|
+
@takes = opts
|
36
|
+
end
|
37
|
+
|
38
|
+
# True if this validator uses any of options in hash +opts+.
|
39
|
+
def use?(opts)
|
40
|
+
!(opts.keys & @takes).empty?
|
41
|
+
end
|
42
|
+
|
43
|
+
# Use the validator on given set of options in hash +opts+. Used
|
44
|
+
# options are removed from +opts+.
|
45
|
+
def use(opts)
|
46
|
+
keys = opts.keys & @takes
|
47
|
+
|
48
|
+
fail 'too many keys' if keys.size > 1
|
49
|
+
new(keys.first, opts.delete(keys.first))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
attr_accessor :message, :params
|
54
|
+
|
55
|
+
def initialize(key, opts)
|
56
|
+
reconfigure(key, opts)
|
57
|
+
end
|
58
|
+
|
59
|
+
def reconfigure(key, opts)
|
60
|
+
@key = key
|
61
|
+
@opts = opts
|
62
|
+
setup
|
63
|
+
end
|
64
|
+
|
65
|
+
def useful?
|
66
|
+
@useful.nil? ? true : @useful
|
67
|
+
end
|
68
|
+
|
69
|
+
# Validators should be configured by the given options. This method may be
|
70
|
+
# called multiple times, if the validator is reconfigured after it was
|
71
|
+
# created.
|
72
|
+
def setup
|
73
|
+
raise NotImplementedError
|
74
|
+
end
|
75
|
+
|
76
|
+
# Return a hash documenting this validator.
|
77
|
+
def describe
|
78
|
+
raise NotImplementedError
|
79
|
+
end
|
80
|
+
|
81
|
+
# Return true if the value is valid.
|
82
|
+
def valid?(v)
|
83
|
+
raise NotImplementedError
|
84
|
+
end
|
85
|
+
|
86
|
+
# Calls method valid?, but before calling it sets instance variable
|
87
|
+
# +@params+. It contains of hash of all other parameters. The validator
|
88
|
+
# may use this information as it will.
|
89
|
+
def validate(v, params)
|
90
|
+
@params = params
|
91
|
+
ret = valid?(v)
|
92
|
+
@params = nil
|
93
|
+
ret
|
94
|
+
end
|
95
|
+
|
96
|
+
protected
|
97
|
+
# This method has three modes of function.
|
98
|
+
#
|
99
|
+
# 1. If +v+ is nil, it returns +@opts+. It is used if +@opts+ is not a hash
|
100
|
+
# but a single value - abbreviation if we're ok with default settings
|
101
|
+
# for given validator.
|
102
|
+
# 2. If +v+ is not nil and +@opts+ is not a hash, it returns +default+
|
103
|
+
# 3. If +v+ is not nil and +@opts+ is a hash and +@opts[v]+ is not nil, it is
|
104
|
+
# returned. Otherwise the +default+ is returned.
|
105
|
+
def take(v = nil, default = nil)
|
106
|
+
if v.nil?
|
107
|
+
@opts
|
108
|
+
|
109
|
+
else
|
110
|
+
return default unless @opts.is_a?(::Hash)
|
111
|
+
return @opts[v] unless @opts[v].nil?
|
112
|
+
default
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Declare validator as useless. Such validator does not do anything and can
|
117
|
+
# be removed from validator chain. Validator can become useless when it's configuration
|
118
|
+
# makes it so.
|
119
|
+
def useless
|
120
|
+
@useful = false
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns true if +@opts+ is not a hash.
|
124
|
+
def simple?
|
125
|
+
!@opts.is_a?(::Hash)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns the name of the option given to this validator. It may bear some
|
129
|
+
# meaning to the validator.
|
130
|
+
def opt
|
131
|
+
@key
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
# A chain of validators for one input parameter.
|
3
|
+
class ValidatorChain
|
4
|
+
def initialize(args)
|
5
|
+
@validators = []
|
6
|
+
@required = false
|
7
|
+
|
8
|
+
find_validators(args) do |validator|
|
9
|
+
obj = validator.use(args)
|
10
|
+
next unless obj.useful?
|
11
|
+
@required = true if obj.is_a?(Validators::Presence)
|
12
|
+
@validators << obj
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Adds validator that takes option +name+ with configuration in +opt+.
|
17
|
+
# If such validator already exists, it is reconfigured with newly provided
|
18
|
+
# +opt+.
|
19
|
+
#
|
20
|
+
# If +opt+ is +nil+, the validator is removed.
|
21
|
+
def add_or_replace(name, opt)
|
22
|
+
args = { name => opt }
|
23
|
+
|
24
|
+
unless v_class = find_validator(args)
|
25
|
+
fail "validator for '#{name}' not found"
|
26
|
+
end
|
27
|
+
|
28
|
+
exists = @validators.detect { |v| v.is_a?(v_class) }
|
29
|
+
obj = exists
|
30
|
+
|
31
|
+
if exists
|
32
|
+
if opt.nil?
|
33
|
+
@validators.delete(exists)
|
34
|
+
|
35
|
+
else
|
36
|
+
exists.reconfigure(name, opt)
|
37
|
+
@validators.delete(exists) unless exists.useful?
|
38
|
+
end
|
39
|
+
|
40
|
+
else
|
41
|
+
obj = v_class.use(args)
|
42
|
+
@validators << obj if obj.useful?
|
43
|
+
end
|
44
|
+
|
45
|
+
if v_class == Validators::Presence
|
46
|
+
@required = !opt.nil? && obj.useful? ? true : false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns true if validator Validators::Presence is used.
|
51
|
+
def required?
|
52
|
+
@required
|
53
|
+
end
|
54
|
+
|
55
|
+
def describe
|
56
|
+
ret = {}
|
57
|
+
@validators.each do |v|
|
58
|
+
ret[v.class.name] = v.describe
|
59
|
+
end
|
60
|
+
|
61
|
+
ret
|
62
|
+
end
|
63
|
+
|
64
|
+
# Validate +value+ using all configured validators. It returns
|
65
|
+
# either +true+ if the value passed all validators or an array
|
66
|
+
# of errors.
|
67
|
+
def validate(value, params)
|
68
|
+
ret = []
|
69
|
+
|
70
|
+
@validators.each do |validator|
|
71
|
+
next if validator.validate(value, params)
|
72
|
+
ret << validator.message % {
|
73
|
+
value: value
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
ret.empty? ? true : ret
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
def find_validator(args)
|
82
|
+
HaveAPI::Validators.constants.select do |v|
|
83
|
+
validator = HaveAPI::Validators.const_get(v)
|
84
|
+
|
85
|
+
return validator if validator.use?(args)
|
86
|
+
end
|
87
|
+
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def find_validators(args)
|
92
|
+
HaveAPI::Validators.constants.select do |v|
|
93
|
+
validator = HaveAPI::Validators.const_get(v)
|
94
|
+
|
95
|
+
yield(validator) if validator.use?(args)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
# Accepts a single configured value.
|
3
|
+
#
|
4
|
+
# Short form:
|
5
|
+
# string :param, accept: 'value'
|
6
|
+
#
|
7
|
+
# Full form:
|
8
|
+
# string :param, accept: {
|
9
|
+
# value: 'value',
|
10
|
+
# message: 'the error message'
|
11
|
+
# }
|
12
|
+
class Validators::Acceptance < Validator
|
13
|
+
name :accept
|
14
|
+
takes :accept
|
15
|
+
|
16
|
+
def setup
|
17
|
+
if simple?
|
18
|
+
@value = take
|
19
|
+
|
20
|
+
else
|
21
|
+
@value = take(:value)
|
22
|
+
end
|
23
|
+
|
24
|
+
@message = take(:message, "has to be #{@value}")
|
25
|
+
end
|
26
|
+
|
27
|
+
def describe
|
28
|
+
{
|
29
|
+
value: @value,
|
30
|
+
message: @message,
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def valid?(v)
|
35
|
+
v == @value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
# Checks that two parameters are equal or not equal.
|
3
|
+
#
|
4
|
+
# Short form:
|
5
|
+
# string :param, confirm: :other_parameter
|
6
|
+
#
|
7
|
+
# Full form:
|
8
|
+
# string :param, confirm: {
|
9
|
+
# param: :other_parameter,
|
10
|
+
# equal: true/false,
|
11
|
+
# message: 'the error message'
|
12
|
+
# }
|
13
|
+
#
|
14
|
+
# +equal+ defaults to +true+.
|
15
|
+
class Validators::Confirmation < Validator
|
16
|
+
name :confirm
|
17
|
+
takes :confirm
|
18
|
+
|
19
|
+
def setup
|
20
|
+
@param = simple? ? take : take(:param)
|
21
|
+
@equal = take(:equal, true)
|
22
|
+
@message = take(
|
23
|
+
:message,
|
24
|
+
@equal ? "must be the same as #{@param}"
|
25
|
+
: "must be different from #{@param}"
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def describe
|
30
|
+
{
|
31
|
+
equal: @equal ? true : false,
|
32
|
+
parameter: @param,
|
33
|
+
message: @message,
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def valid?(v)
|
38
|
+
if @equal
|
39
|
+
v == params[@param]
|
40
|
+
|
41
|
+
else
|
42
|
+
v != params[@param]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
# Custom validator. It has only a short form, taking the description
|
3
|
+
# of the validator. This validator passes every value. It is up to the
|
4
|
+
# developer to implement the validation in HaveAPI::Action.exec.
|
5
|
+
class Validators::Custom < Validator
|
6
|
+
name :custom
|
7
|
+
takes :validate
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@desc = take
|
11
|
+
end
|
12
|
+
|
13
|
+
def describe
|
14
|
+
@desc
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid?(v)
|
18
|
+
true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
# Checks that the value is not reserved.
|
3
|
+
#
|
4
|
+
# Short form:
|
5
|
+
# string :param, exclude: %i(one two three)
|
6
|
+
#
|
7
|
+
# Full form:
|
8
|
+
# string :param, exclude: {
|
9
|
+
# values: %i(one two three),
|
10
|
+
# message: 'the error message'
|
11
|
+
# }
|
12
|
+
#
|
13
|
+
# In this case, the value could be anything but +one+, +two+ or
|
14
|
+
# +three+.
|
15
|
+
class Validators::Exclusion < Validator
|
16
|
+
name :exclude
|
17
|
+
takes :exclude
|
18
|
+
|
19
|
+
def setup
|
20
|
+
@values = (simple? ? take : take(:values)).map! do |v|
|
21
|
+
v.is_a?(::Symbol) ? v.to_s : v
|
22
|
+
end
|
23
|
+
|
24
|
+
@message = take(:message, '%{value} cannot be used')
|
25
|
+
end
|
26
|
+
|
27
|
+
def describe
|
28
|
+
{
|
29
|
+
values: @values,
|
30
|
+
message: @message,
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def valid?(v)
|
35
|
+
!@values.include?(v)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
# Checks that the value is or is not in specified format.
|
3
|
+
#
|
4
|
+
# Short form:
|
5
|
+
# string :param, format: /^[a-z0-9]+$/
|
6
|
+
#
|
7
|
+
# Full form:
|
8
|
+
# string :param, format: {
|
9
|
+
# rx: /^[a-z0-9]+$/,
|
10
|
+
# match: true/false,
|
11
|
+
# message: 'the error message'
|
12
|
+
# }
|
13
|
+
class Validators::Format < Validator
|
14
|
+
name :format
|
15
|
+
takes :format
|
16
|
+
|
17
|
+
def setup
|
18
|
+
@rx = simple? ? take : take(:rx)
|
19
|
+
@match = take(:match, true)
|
20
|
+
@desc = take(:desc)
|
21
|
+
@message = take(:message, @desc || '%{value} is not in a valid format')
|
22
|
+
end
|
23
|
+
|
24
|
+
def describe
|
25
|
+
{
|
26
|
+
rx: @rx.source,
|
27
|
+
match: @match,
|
28
|
+
description: @desc,
|
29
|
+
message: @message,
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid?(v)
|
34
|
+
if @match
|
35
|
+
@rx.match(v) ? true : false
|
36
|
+
|
37
|
+
else
|
38
|
+
@rx.match(v) ? false : true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
# Checks that the value is from given set of allowed values.
|
3
|
+
#
|
4
|
+
# Short form:
|
5
|
+
# string :param, choices: %i(one two three)
|
6
|
+
#
|
7
|
+
# Full form:
|
8
|
+
# string :param, choices: {
|
9
|
+
# values: %i(one two three),
|
10
|
+
# message: 'the error message'
|
11
|
+
# }
|
12
|
+
#
|
13
|
+
# Option +choices+ is an alias to +include+.
|
14
|
+
class Validators::Inclusion < Validator
|
15
|
+
name :include
|
16
|
+
takes :choices, :include
|
17
|
+
|
18
|
+
def setup
|
19
|
+
@values = (simple? ? take : take(:values)).map! do |v|
|
20
|
+
v.is_a?(::Symbol) ? v.to_s : v
|
21
|
+
end
|
22
|
+
|
23
|
+
@message = take(:message, '%{value} cannot be used')
|
24
|
+
end
|
25
|
+
|
26
|
+
def describe
|
27
|
+
{
|
28
|
+
values: @values,
|
29
|
+
message: @message,
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid?(v)
|
34
|
+
if @values.is_a?(::Hash)
|
35
|
+
@values.has_key?(v)
|
36
|
+
|
37
|
+
else
|
38
|
+
@values.include?(v)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|