sinatra-param-validator 0.1.0 → 0.2.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.
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