sinatra-param-validator 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/Gemfile +0 -7
  4. data/Gemfile.lock +28 -5
  5. data/README.md +109 -2
  6. data/lib/sinatra/param_validator/camelize.rb +12 -0
  7. data/lib/sinatra/param_validator/definitions.rb +24 -0
  8. data/lib/sinatra/param_validator/helpers.rb +23 -0
  9. data/lib/sinatra/param_validator/parameter/array.rb +25 -0
  10. data/lib/sinatra/param_validator/parameter/boolean.rb +26 -0
  11. data/lib/sinatra/param_validator/parameter/common.rb +98 -0
  12. data/lib/sinatra/param_validator/parameter/date.rb +24 -0
  13. data/lib/sinatra/param_validator/parameter/float.rb +23 -0
  14. data/lib/sinatra/param_validator/parameter/hash.rb +25 -0
  15. data/lib/sinatra/param_validator/parameter/integer.rb +23 -0
  16. data/lib/sinatra/param_validator/parameter/string.rb +27 -0
  17. data/lib/sinatra/param_validator/parameter/time.rb +24 -0
  18. data/lib/sinatra/param_validator/parameter.rb +28 -0
  19. data/lib/sinatra/param_validator/parser.rb +41 -0
  20. data/lib/sinatra/param_validator/rule/all_or_none_of.rb +31 -0
  21. data/lib/sinatra/param_validator/rule/any_of.rb +29 -0
  22. data/lib/sinatra/param_validator/rule/one_of.rb +30 -0
  23. data/lib/sinatra/param_validator/rule.rb +23 -0
  24. data/lib/sinatra/param_validator/validation_failed_error.rb +15 -0
  25. data/lib/sinatra/param_validator/validator/form.rb +37 -0
  26. data/lib/sinatra/param_validator/validator/url_param.rb +14 -0
  27. data/lib/sinatra/param_validator/validator.rb +31 -0
  28. data/lib/sinatra/param_validator/version.rb +1 -1
  29. data/lib/sinatra/param_validator.rb +23 -2
  30. data/sinatra-param-validator.gemspec +9 -3
  31. metadata +137 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c121f205eb49bdae18ff06dd346292bd8c9fc9018468002cf26836728a466449
4
- data.tar.gz: eceddebc3e9bcfeef60e69c6e190fc33e360b14458613e20951f7f8fc0b952a2
3
+ metadata.gz: dda1b5487b870b26f4b2ca5c6a575a57c91441ac319b78567295ab0b4517a8bc
4
+ data.tar.gz: 331f3cace8d87a35fa410ae28c3df15e07f851d276ad2a2173ce799572cfae04
5
5
  SHA512:
6
- metadata.gz: 5e629007dc0aa4cf0e6edf751992031d366b6eceea1c615c23b05ad0d05fe3681bfae9346c1139c9c4ad9b0f3c40cf41a815e9b95cf426a772aa4aedd4ecd999
7
- data.tar.gz: 89ba7f100e4314453d3fa0d1bee0f6237f56c970fa6580e1cd2a73451f05e9ae2645ff056c7c7eafadb4be531362bdfbc1f8911de48f38ea8eca4229e2df1664
6
+ metadata.gz: caceb492da8562135850ecf1c7a77ecd4fd850ab31d714ff1fa555c4316e63074aa7ed9f75e331336ddf30c1921a5d3cc2f895447d7ec76f3767b46448e6a7c4
7
+ data.tar.gz: 729686317687d72ff23ce570991a88e6731314320f3e702ba631c7452b75e117997fc30207c2cee7be52c1aa458424311666b734061bb7c3aefb6911b07a9dc2
data/.rubocop.yml CHANGED
@@ -18,3 +18,6 @@ Metrics/BlockLength:
18
18
  # Extend permitted line length
19
19
  Layout/LineLength:
20
20
  Max: 120
21
+
22
+ RSpec/NestedGroups:
23
+ Max: 4
data/Gemfile CHANGED
@@ -4,10 +4,3 @@ source 'https://rubygems.org'
4
4
 
5
5
  # Specify your gem's dependencies in sinatra-param_validator-validator.gemspec
6
6
  gemspec
7
-
8
- gem 'rake', '~> 13.0'
9
- gem 'rspec', '~> 3.0'
10
- gem 'rubocop', '~> 1.21'
11
- gem 'rubocop-performance', '~> 1.0'
12
- gem 'rubocop-rake', '~> 0.5'
13
- gem 'rubocop-rspec', '~> 2.0'
data/Gemfile.lock CHANGED
@@ -1,19 +1,27 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sinatra-param-validator (0.1.0)
4
+ sinatra-param-validator (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  ast (2.4.2)
10
10
  diff-lcs (1.5.0)
11
+ multi_json (1.15.0)
12
+ mustermann (1.1.1)
13
+ ruby2_keywords (~> 0.0.1)
11
14
  parallel (1.22.1)
12
15
  parser (3.1.2.0)
13
16
  ast (~> 2.4.1)
17
+ rack (2.2.3)
18
+ rack-protection (2.2.0)
19
+ rack
20
+ rack-test (1.1.0)
21
+ rack (>= 1.0, < 3)
14
22
  rainbow (3.1.1)
15
23
  rake (13.0.6)
16
- regexp_parser (2.3.1)
24
+ regexp_parser (2.4.0)
17
25
  rexml (3.2.5)
18
26
  rspec (3.11.0)
19
27
  rspec-core (~> 3.11.0)
@@ -28,7 +36,7 @@ GEM
28
36
  diff-lcs (>= 1.2.0, < 2.0)
29
37
  rspec-support (~> 3.11.0)
30
38
  rspec-support (3.11.0)
31
- rubocop (1.29.0)
39
+ rubocop (1.29.1)
32
40
  parallel (~> 1.10)
33
41
  parser (>= 3.1.0.0)
34
42
  rainbow (>= 2.2.2, < 4.0)
@@ -37,7 +45,7 @@ GEM
37
45
  rubocop-ast (>= 1.17.0, < 2.0)
38
46
  ruby-progressbar (~> 1.7)
39
47
  unicode-display_width (>= 1.4.0, < 3.0)
40
- rubocop-ast (1.17.0)
48
+ rubocop-ast (1.18.0)
41
49
  parser (>= 3.1.1.0)
42
50
  rubocop-performance (1.13.3)
43
51
  rubocop (>= 1.7.0, < 2.0)
@@ -47,18 +55,33 @@ GEM
47
55
  rubocop-rspec (2.10.0)
48
56
  rubocop (~> 1.19)
49
57
  ruby-progressbar (1.11.0)
58
+ ruby2_keywords (0.0.5)
59
+ sinatra (2.2.0)
60
+ mustermann (~> 1.0)
61
+ rack (~> 2.2)
62
+ rack-protection (= 2.2.0)
63
+ tilt (~> 2.0)
64
+ sinatra-contrib (2.2.0)
65
+ multi_json
66
+ mustermann (~> 1.0)
67
+ rack-protection (= 2.2.0)
68
+ sinatra (= 2.2.0)
69
+ tilt (~> 2.0)
70
+ tilt (2.0.10)
50
71
  unicode-display_width (2.1.0)
51
72
 
52
73
  PLATFORMS
53
74
  x86_64-linux
54
75
 
55
76
  DEPENDENCIES
77
+ rack-test (~> 1.1)
56
78
  rake (~> 13.0)
57
79
  rspec (~> 3.0)
58
- rubocop (~> 1.21)
80
+ rubocop (~> 1.0)
59
81
  rubocop-performance (~> 1.0)
60
82
  rubocop-rake (~> 0.5)
61
83
  rubocop-rspec (~> 2.0)
84
+ sinatra-contrib (~> 2.0)
62
85
  sinatra-param-validator!
63
86
 
64
87
  BUNDLED WITH
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # Sinatra::Param::Validator
2
2
 
3
+ Validate parameters in a Sinatra app.
3
4
 
4
5
  ## Installation
5
6
 
@@ -11,9 +12,115 @@ If bundler is not being used to manage dependencies, install the gem by executin
11
12
 
12
13
  $ gem install sinatra-param-validator
13
14
 
14
- ## Usage
15
+ ## Sample Usage
15
16
 
16
- TODO: Write usage instructions here
17
+ ```ruby
18
+ validator identifier: :user_id do
19
+ param :id, Integer, required: true
20
+ end
21
+
22
+ get '/user/:id', validate: :user_id do
23
+ # ...
24
+ end
25
+
26
+ validator identifier: :new_user do
27
+ param :name, String, required: true
28
+ param :age, Integer, required: true, min: 0
29
+ end
30
+
31
+ post '/new-user', validate: :new_user do
32
+ # ...
33
+ end
34
+ ```
35
+
36
+ ## Parameter Types
37
+
38
+ The following parameter types are built-in,
39
+ and values will be coerced to an object of that type.
40
+
41
+ * `Array`
42
+ * Accepts a comma-separated list of values, as well as an array
43
+ * e.g. `a,b,c`
44
+ * `Boolean`
45
+ * `false|f|no|n|0` or `true|t|yes|y|1`
46
+ * `Date`
47
+ * All formats accepted by `Date.parse`
48
+ * `Float`
49
+ * `Hash`
50
+ * Accepts a comma-separated list of colon-separated key-value pairs
51
+ * e.g. `a:1,b:2,c:3`
52
+ * `Integer`
53
+ * `String`
54
+ * `Time`
55
+ * All formats accepted by `Time.parse`
56
+
57
+ Types can be defined using class names or symbols:
58
+
59
+ ```ruby
60
+ param :name, String
61
+ param :tick_box, :boolean
62
+ ```
63
+
64
+ ## Parameter Validations
65
+
66
+ ```ruby
67
+ param :number, Integer, required: true, in: 0..100
68
+ ```
69
+
70
+ All parameters have the following validations available:
71
+
72
+ * `nillable`
73
+ * If this is set, all other validations are skipped if the value is nil
74
+ * `required`
75
+ * The parameter must be present and cannot be nil
76
+ * `in`
77
+ * The value is in the given array / range
78
+ * `is`
79
+ * Match a specific value
80
+
81
+ `Array`, `Hash` and `String` have the following validations:
82
+
83
+ * `min_length` / `max_length`
84
+
85
+ `Date`, `Time`, `Float` and `Integer` have the following validations:
86
+
87
+ * `min` / `max`
88
+
89
+ ## Rules
90
+
91
+ Rules work on multiple parameters:
92
+
93
+ ```ruby
94
+ rule :all_or_none_of, :a, :b
95
+ ```
96
+
97
+ * `all_or_none_of`
98
+ * `any_of`
99
+ * At least one of the given fields must be present
100
+ * `one_of`
101
+ * Only one of the given fields can be present
102
+
103
+ ## Validator Types
104
+
105
+ The default validator will raise `Sinatra::ParamValidator::ValidationFailedError` when validation fails.
106
+
107
+ There are two other provided validators, that handle failure differently:
108
+
109
+ * `url_param`
110
+ * will `halt 403`
111
+ * `form`
112
+ * if [sinatra-flash](https://github.com/SFEley/sinatra-flash) is available, it will flash the errors and `redirect back`
113
+ * will provide a JSON object with errors to an XHR request
114
+ * will `halt 400`
115
+
116
+ These validators can be set when a validator is defined:
117
+
118
+ ```ruby
119
+ validator type: :form, identifier: :new_user do
120
+ param :name, String, required: true
121
+ param :age, Integer, required: true, min: 0
122
+ end
123
+ ```
17
124
 
18
125
  ## Development
19
126
 
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module ParamValidator
5
+ # Helpers for validating parameters
6
+ module Camelize
7
+ def camelize(symbol)
8
+ symbol.to_s.split('_').map(&:capitalize).join
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module ParamValidator
5
+ # Store of valid definitions
6
+ class Definitions
7
+ def initialize
8
+ @definitions = {}
9
+ end
10
+
11
+ def add(identifier, validator)
12
+ raise "Validator already defined: '#{identifier}'" if @definitions.key? identifier
13
+
14
+ @definitions[identifier] = validator
15
+ end
16
+
17
+ def get(identifier)
18
+ raise "Unknown validator: '#{identifier}'" unless @definitions.key? identifier
19
+
20
+ @definitions[identifier]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module ParamValidator
5
+ # Helpers for validating parameters
6
+ module Helpers
7
+ def filter_params
8
+ params.each do |(param, value)|
9
+ params[param] = nil if value == ''
10
+ params[param] = [] if value == ['']
11
+ end
12
+ rescue StandardError => e
13
+ raise "Filter params failed: #{e}"
14
+ end
15
+
16
+ def validate(identifier, args = {})
17
+ validator = settings.validator_definitions.get(identifier)
18
+ validator.run(self, *args)
19
+ validator.handle_failure(self) unless validator.success?
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'common'
4
+
5
+ module Sinatra
6
+ module ParamValidator
7
+ class Parameter
8
+ # Validation for arrays
9
+ class Array
10
+ include Common
11
+ include CommonMinMaxLength
12
+
13
+ private
14
+
15
+ def coerce(value)
16
+ return nil if value.nil?
17
+ return value if value.is_a? ::Array
18
+ return value.split(',') if value.is_a? ::String
19
+
20
+ raise ArgumentError
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'common'
4
+
5
+ module Sinatra
6
+ module ParamValidator
7
+ class Parameter
8
+ # Validation for booleans
9
+ class Boolean
10
+ include Common
11
+
12
+ private
13
+
14
+ def coerce(value)
15
+ return nil if value.nil?
16
+
17
+ case value.to_s
18
+ when /^(false|f|no|n|0)$/i then false
19
+ when /^(true|t|yes|y|1)$/i then true
20
+ else raise ArgumentError
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module ParamValidator
5
+ class Parameter
6
+ # Common validation methods shared between parameters
7
+ module Common
8
+ attr_reader :coerced, :errors
9
+
10
+ def initialize(value, **options)
11
+ @errors = []
12
+ @coerced = coerce value
13
+ @options = options
14
+
15
+ validate_options
16
+ validate unless nil_and_ok?
17
+ rescue ArgumentError
18
+ @errors.push "'#{value}' is not a valid #{self.class}"
19
+ end
20
+
21
+ def valid?
22
+ @errors.empty?
23
+ end
24
+
25
+ def validate_options
26
+ @options.each { |key, _| raise "Unknown option '#{key}' for #{self.class}" unless respond_to? key }
27
+ end
28
+ private :validate_options
29
+
30
+ def validate
31
+ @options.each { |key, value| method(key).call(value) }
32
+ end
33
+ private :validate
34
+
35
+ def in(options)
36
+ @errors.push "Parameter must be within #{options}" unless in? options
37
+ end
38
+
39
+ def in?(options)
40
+ case options
41
+ when Range
42
+ options.include? @coerced
43
+ else
44
+ Array(options).include? @coerced
45
+ end
46
+ end
47
+ private :in?
48
+
49
+ def is(option_value)
50
+ @errors.push "Parameter must be #{option_value}" unless @coerced == option_value
51
+ end
52
+
53
+ def nillable(_)
54
+ # Does nothing. Allows other tests to ignore nil values if present in the options
55
+ end
56
+
57
+ def nil_and_ok?
58
+ @options.key?(:nillable) && @coerced.nil?
59
+ end
60
+ private :nil_and_ok?
61
+
62
+ def required(enabled)
63
+ @errors.push 'Parameter is required' if enabled && @coerced.nil?
64
+ end
65
+ end
66
+
67
+ # min/max tests
68
+ module CommonMinMax
69
+ def max(maximum)
70
+ return if @coerced.respond_to?(:<=) && @coerced <= maximum
71
+
72
+ @errors.push "Parameter cannot be greater than #{maximum}"
73
+ end
74
+
75
+ def min(minimum)
76
+ return if @coerced.respond_to?(:>=) && @coerced >= minimum
77
+
78
+ @errors.push "Parameter cannot be less than #{minimum}"
79
+ end
80
+ end
81
+
82
+ # min/max length tests
83
+ module CommonMinMaxLength
84
+ def max_length(length)
85
+ return if @coerced.respond_to?(:length) && @coerced.length <= length
86
+
87
+ @errors.push "Parameter cannot have length greater than #{length}"
88
+ end
89
+
90
+ def min_length(length)
91
+ return if @coerced.respond_to?(:length) && @coerced.length >= length
92
+
93
+ @errors.push "Parameter cannot have length less than #{length}"
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require_relative 'common'
5
+
6
+ module Sinatra
7
+ module ParamValidator
8
+ class Parameter
9
+ # Validation for dates
10
+ class Date
11
+ include Common
12
+ include CommonMinMax
13
+
14
+ private
15
+
16
+ def coerce(value)
17
+ return nil if value.nil?
18
+
19
+ ::Date.parse(value)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'common'
4
+
5
+ module Sinatra
6
+ module ParamValidator
7
+ class Parameter
8
+ # Validation for floats
9
+ class Float
10
+ include Common
11
+ include CommonMinMax
12
+
13
+ private
14
+
15
+ def coerce(value)
16
+ return nil if value.nil?
17
+
18
+ Float(value)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'common'
4
+
5
+ module Sinatra
6
+ module ParamValidator
7
+ class Parameter
8
+ # Validation for hashes
9
+ class Hash
10
+ include Common
11
+ include CommonMinMaxLength
12
+
13
+ private
14
+
15
+ def coerce(value)
16
+ return nil if value.nil?
17
+ return value if value.is_a? ::Hash
18
+ return value.split(',').to_h { |s| s.split(':') } if value.is_a? ::String
19
+
20
+ raise ArgumentError
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'common'
4
+
5
+ module Sinatra
6
+ module ParamValidator
7
+ class Parameter
8
+ # Validation for integers
9
+ class Integer
10
+ include Common
11
+ include CommonMinMax
12
+
13
+ private
14
+
15
+ def coerce(value)
16
+ return nil if value.nil?
17
+
18
+ Integer(value, 10)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'common'
4
+
5
+ module Sinatra
6
+ module ParamValidator
7
+ class Parameter
8
+ # Validation for strings
9
+ class String
10
+ include Common
11
+ include CommonMinMaxLength
12
+
13
+ def format(format_string)
14
+ @errors.push "Parameter must match the format #{format_string}" unless @coerced&.match?(format_string)
15
+ end
16
+
17
+ private
18
+
19
+ def coerce(value)
20
+ return nil if value.nil?
21
+
22
+ String(value)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require_relative 'common'
5
+
6
+ module Sinatra
7
+ module ParamValidator
8
+ class Parameter
9
+ # Validation for times
10
+ class Time
11
+ include Common
12
+ include CommonMinMax
13
+
14
+ private
15
+
16
+ def coerce(value)
17
+ return nil if value.nil?
18
+
19
+ ::Time.parse(value)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'camelize'
4
+ require_relative 'parameter/array'
5
+ require_relative 'parameter/boolean'
6
+ require_relative 'parameter/date'
7
+ require_relative 'parameter/float'
8
+ require_relative 'parameter/hash'
9
+ require_relative 'parameter/integer'
10
+ require_relative 'parameter/string'
11
+ require_relative 'parameter/time'
12
+
13
+ module Sinatra
14
+ module ParamValidator
15
+ # Load and validate a single parameter
16
+ class Parameter
17
+ class << self
18
+ include Camelize
19
+
20
+ def new(value, type, **args)
21
+ type = camelize(type) if type.is_a? Symbol
22
+ klass = Object.const_get "Sinatra::ParamValidator::Parameter::#{type}"
23
+ klass.new(value, **args)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ require_relative 'parameter'
6
+ require_relative 'rule'
7
+
8
+ module Sinatra
9
+ module ParamValidator
10
+ # Run the definition in the given scope
11
+ class Parser < SimpleDelegator
12
+ attr_reader :errors
13
+
14
+ def initialize(definition, context)
15
+ super(context)
16
+ @context = context
17
+ @errors = {}
18
+
19
+ instance_exec({}, &definition)
20
+ end
21
+
22
+ def param(key, type, **args)
23
+ parameter = Parameter.new(@context.params[key], type, **args)
24
+ @context.params[key] = parameter.coerced
25
+ @errors[key] = parameter.errors unless parameter.valid?
26
+ rescue NameError
27
+ raise 'Invalid parameter type'
28
+ end
29
+
30
+ def rule(name, *args, **kwargs)
31
+ rule = Rule.new(name, @context.params, *args, **kwargs)
32
+ unless rule.passes?
33
+ @errors[:rules] ||= []
34
+ @errors[:rules].push(rule.errors)
35
+ end
36
+ rescue NameError
37
+ raise 'Invalid rule type'
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module ParamValidator
5
+ class Rule
6
+ # Rule to enforce all given params, or none of them
7
+ class AllOrNoneOf
8
+ attr_reader :errors
9
+
10
+ def initialize(params, *fields, **_kwargs)
11
+ @errors = []
12
+ @params = params
13
+ @fields = fields
14
+
15
+ validate(fields)
16
+ end
17
+
18
+ def passes?
19
+ @errors.empty?
20
+ end
21
+
22
+ def validate(fields)
23
+ count = fields.count { |f| @params.key? f }
24
+ return if count.zero? || count == fields.count
25
+
26
+ @errors.push "All or none of [#{fields.join ', '}] must be provided"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module ParamValidator
5
+ class Rule
6
+ # Rule to enforce at least one of the given params exists
7
+ class AnyOf
8
+ attr_reader :errors
9
+
10
+ def initialize(params, *fields, **_kwargs)
11
+ @errors = []
12
+ @params = params
13
+ @fields = fields
14
+
15
+ validate(fields)
16
+ end
17
+
18
+ def passes?
19
+ @errors.empty?
20
+ end
21
+
22
+ def validate(fields)
23
+ count = fields.count { |f| @params.key? f }
24
+ @errors.push "One of [#{fields.join ', '}] must be provided" if count < 1
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module ParamValidator
5
+ class Rule
6
+ # Rule to enforce only one of the given params has been given
7
+ class OneOf
8
+ attr_reader :errors
9
+
10
+ def initialize(params, *fields, **_kwargs)
11
+ @errors = []
12
+ @params = params
13
+ @fields = fields
14
+
15
+ validate(fields)
16
+ end
17
+
18
+ def passes?
19
+ @errors.empty?
20
+ end
21
+
22
+ def validate(fields)
23
+ count = fields.count { |f| @params.key? f }
24
+ @errors.push "Only one of [#{fields.join ', '}] is allowed" if count > 1
25
+ @errors.push "One of [#{fields.join ', '}] must be provided" if count < 1
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'camelize'
4
+ require_relative 'rule/all_or_none_of'
5
+ require_relative 'rule/any_of'
6
+ require_relative 'rule/one_of'
7
+
8
+ module Sinatra
9
+ module ParamValidator
10
+ # Class to check a single rule
11
+ class Rule
12
+ class << self
13
+ include Camelize
14
+
15
+ def new(name, params, *args, **kwargs)
16
+ name = camelize(name) if name.is_a? Symbol
17
+ klass = Object.const_get "Sinatra::ParamValidator::Rule::#{name}"
18
+ klass.new(params, *args, **kwargs)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module ParamValidator
5
+ # Error raised when validation fails
6
+ class ValidationFailedError < StandardError
7
+ attr_reader :errors
8
+
9
+ def initialize(errors, msg = nil)
10
+ @errors = errors
11
+ super(msg)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module ParamValidator
5
+ class Validator
6
+ # A form validator
7
+ class Form < Validator
8
+ def handle_failure(context)
9
+ case context.request.preferred_type.to_s
10
+ when 'application/json' then return json_failure(context)
11
+ when 'text/html'
12
+ return flash_failure(context) if defined? Sinatra::Flash
13
+ end
14
+
15
+ context.halt 400
16
+ end
17
+
18
+ def run(context)
19
+ @original_params = context.params
20
+ super(context)
21
+ end
22
+
23
+ private
24
+
25
+ def json_failure(context)
26
+ context.halt 400, { error: 'Validation failed', fields: @errors }.to_json
27
+ end
28
+
29
+ def flash_failure(context)
30
+ context.flash[:params] = @original_params
31
+ context.flash[:form_errors] = @errors
32
+ context.redirect context.back
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module ParamValidator
5
+ class Validator
6
+ # A URL parameter; handle validation failure with
7
+ class UrlParam < Validator
8
+ def handle_failure(context)
9
+ context.halt 403
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'validation_failed_error'
4
+ require_relative 'validator/form'
5
+ require_relative 'validator/url_param'
6
+
7
+ module Sinatra
8
+ module ParamValidator
9
+ # Definition of a single validator
10
+ class Validator
11
+ attr_reader :errors
12
+
13
+ def initialize(&definition)
14
+ @definition = definition
15
+ @errors = {}
16
+ end
17
+
18
+ def handle_failure(_context)
19
+ raise ValidationFailedError, @errors
20
+ end
21
+
22
+ def run(context)
23
+ @errors = Parser.new(@definition, context).errors
24
+ end
25
+
26
+ def success?
27
+ @errors.empty?
28
+ end
29
+ end
30
+ end
31
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sinatra
4
4
  module ParamValidator
5
- VERSION = '0.1.0'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
@@ -1,10 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'param_validator/definitions'
4
+ require_relative 'param_validator/helpers'
5
+ require_relative 'param_validator/parser'
6
+ require_relative 'param_validator/validator'
3
7
  require_relative 'param_validator/version'
4
8
 
5
9
  module Sinatra
6
- # Validator for param
10
+ # Module to register in Sinatra app
7
11
  module ParamValidator
8
- # Your code goes here...
12
+ include Camelize
13
+
14
+ def validator(identifier:, type: nil, &definition)
15
+ class_name = 'Sinatra::ParamValidator::Validator'
16
+ class_name = "#{class_name}::#{camelize(type)}" unless type.nil?
17
+ settings.validator_definitions.add(identifier, Object.const_get(class_name).new(&definition))
18
+ end
19
+
20
+ def self.registered(app)
21
+ app.helpers Helpers
22
+ app.before { filter_params }
23
+ app.set(:validator_definitions, Definitions.new)
24
+ app.set(:validate) do |*identifiers|
25
+ condition do
26
+ identifiers.each { |identifier| validate identifier }
27
+ end
28
+ end
29
+ end
9
30
  end
10
31
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lib/sinatra/param_validator'
3
+ require_relative 'lib/sinatra/param_validator/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'sinatra-param-validator'
@@ -27,6 +27,12 @@ Gem::Specification.new do |spec|
27
27
  end
28
28
  spec.require_paths = ['lib']
29
29
 
30
- # Uncomment to register a new dependency of your gem
31
- # spec.add_dependency "example-gem", "~> 1.0"
30
+ spec.add_development_dependency 'rack-test', '~> 1.1'
31
+ spec.add_development_dependency 'rake', '~> 13.0'
32
+ spec.add_development_dependency 'rspec', '~> 3.0'
33
+ spec.add_development_dependency 'rubocop', '~> 1.0'
34
+ spec.add_development_dependency 'rubocop-performance', '~> 1.0'
35
+ spec.add_development_dependency 'rubocop-rake', '~> 0.5'
36
+ spec.add_development_dependency 'rubocop-rspec', '~> 2.0'
37
+ spec.add_development_dependency 'sinatra-contrib', '~> 2.0'
32
38
  end
metadata CHANGED
@@ -1,15 +1,127 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra-param-validator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rick Selby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-16 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-06-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack-test
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-performance
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.5'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.5'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sinatra-contrib
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.0'
13
125
  description:
14
126
  email:
15
127
  - rick@selby-family.co.uk
@@ -27,6 +139,28 @@ files:
27
139
  - README.md
28
140
  - Rakefile
29
141
  - lib/sinatra/param_validator.rb
142
+ - lib/sinatra/param_validator/camelize.rb
143
+ - lib/sinatra/param_validator/definitions.rb
144
+ - lib/sinatra/param_validator/helpers.rb
145
+ - lib/sinatra/param_validator/parameter.rb
146
+ - lib/sinatra/param_validator/parameter/array.rb
147
+ - lib/sinatra/param_validator/parameter/boolean.rb
148
+ - lib/sinatra/param_validator/parameter/common.rb
149
+ - lib/sinatra/param_validator/parameter/date.rb
150
+ - lib/sinatra/param_validator/parameter/float.rb
151
+ - lib/sinatra/param_validator/parameter/hash.rb
152
+ - lib/sinatra/param_validator/parameter/integer.rb
153
+ - lib/sinatra/param_validator/parameter/string.rb
154
+ - lib/sinatra/param_validator/parameter/time.rb
155
+ - lib/sinatra/param_validator/parser.rb
156
+ - lib/sinatra/param_validator/rule.rb
157
+ - lib/sinatra/param_validator/rule/all_or_none_of.rb
158
+ - lib/sinatra/param_validator/rule/any_of.rb
159
+ - lib/sinatra/param_validator/rule/one_of.rb
160
+ - lib/sinatra/param_validator/validation_failed_error.rb
161
+ - lib/sinatra/param_validator/validator.rb
162
+ - lib/sinatra/param_validator/validator/form.rb
163
+ - lib/sinatra/param_validator/validator/url_param.rb
30
164
  - lib/sinatra/param_validator/version.rb
31
165
  - sinatra-param-validator.gemspec
32
166
  homepage: https://github.com/rickselby/sinatra-param_validator-validator