haveapi 0.3.2 → 0.4.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 +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
|