rails-param-validation 0.1.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 +7 -0
- data/.gitignore +2 -0
- data/.gitlab-ci.yml +34 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +63 -0
- data/Rakefile +6 -0
- data/bin/.keep +0 -0
- data/docs/_config.yml +3 -0
- data/docs/annotations.md +62 -0
- data/docs/getting-started.md +32 -0
- data/docs/image/error-screenshot.png +0 -0
- data/docs/index.md +61 -0
- data/docs/main-idea.md +72 -0
- data/docs/openapi.md +39 -0
- data/docs/type-definition.md +178 -0
- data/lib/rails-param-validation/errors/missing_parameter_annotation.rb +9 -0
- data/lib/rails-param-validation/errors/no_matching_factory.rb +9 -0
- data/lib/rails-param-validation/errors/param_validation_failed_error.rb +12 -0
- data/lib/rails-param-validation/errors/type_not_found.rb +9 -0
- data/lib/rails-param-validation/rails/action_definition.rb +66 -0
- data/lib/rails-param-validation/rails/annotation_manager.rb +40 -0
- data/lib/rails-param-validation/rails/config.rb +48 -0
- data/lib/rails-param-validation/rails/extensions/annotation_extension.rb +95 -0
- data/lib/rails-param-validation/rails/extensions/custom_type_extension.rb +13 -0
- data/lib/rails-param-validation/rails/extensions/error.template.html.erb +86 -0
- data/lib/rails-param-validation/rails/extensions/validation_extension.rb +105 -0
- data/lib/rails-param-validation/rails/helper.rb +9 -0
- data/lib/rails-param-validation/rails/openapi/openapi.rb +128 -0
- data/lib/rails-param-validation/rails/openapi/routing_helper.rb +40 -0
- data/lib/rails-param-validation/rails/rails.rb +31 -0
- data/lib/rails-param-validation/rails/tasks/openapi.rake +32 -0
- data/lib/rails-param-validation/types/types.rb +100 -0
- data/lib/rails-param-validation/validator.rb +51 -0
- data/lib/rails-param-validation/validator_factory.rb +37 -0
- data/lib/rails-param-validation/validators/alternatives.rb +42 -0
- data/lib/rails-param-validation/validators/array.rb +49 -0
- data/lib/rails-param-validation/validators/boolean.rb +38 -0
- data/lib/rails-param-validation/validators/constant.rb +38 -0
- data/lib/rails-param-validation/validators/custom_type.rb +28 -0
- data/lib/rails-param-validation/validators/date.rb +39 -0
- data/lib/rails-param-validation/validators/datetime.rb +39 -0
- data/lib/rails-param-validation/validators/float.rb +39 -0
- data/lib/rails-param-validation/validators/hash.rb +52 -0
- data/lib/rails-param-validation/validators/integer.rb +39 -0
- data/lib/rails-param-validation/validators/object.rb +63 -0
- data/lib/rails-param-validation/validators/optional.rb +44 -0
- data/lib/rails-param-validation/validators/regex.rb +37 -0
- data/lib/rails-param-validation/validators/string.rb +31 -0
- data/lib/rails-param-validation/validators/uuid.rb +39 -0
- data/lib/rails-param-validation/version.rb +3 -0
- data/lib/rails-param-validation.rb +42 -0
- data/rails-param-validation.gemspec +33 -0
- metadata +100 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
module RailsParamValidation
|
2
|
+
|
3
|
+
module AnnotationTypes
|
4
|
+
class AnnotationT
|
5
|
+
attr_reader :inner_type
|
6
|
+
|
7
|
+
def initialize(inner_type)
|
8
|
+
@inner_type = inner_type
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ArrayT < AnnotationT
|
13
|
+
def initialize(inner_type)
|
14
|
+
super(inner_type)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class HashT < AnnotationT
|
19
|
+
attr_reader :key_type
|
20
|
+
|
21
|
+
def initialize(inner_type, key_type = String)
|
22
|
+
super(inner_type)
|
23
|
+
|
24
|
+
@key_type = key_type
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class OptionalT < AnnotationT
|
29
|
+
attr_reader :default
|
30
|
+
|
31
|
+
def initialize(inner_type, default)
|
32
|
+
super(inner_type)
|
33
|
+
|
34
|
+
@default = default
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class CustomT < AnnotationT
|
39
|
+
attr_reader :type
|
40
|
+
|
41
|
+
def initialize(type)
|
42
|
+
@type = type
|
43
|
+
end
|
44
|
+
|
45
|
+
def schema
|
46
|
+
CustomT.registry[@type]
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.register(type, schema)
|
50
|
+
registry[type] = schema
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.registered(type)
|
54
|
+
raise TypeNotFound.new(type) unless registry.key? type
|
55
|
+
registry[type]
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.types
|
59
|
+
registry.keys
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def self.registry
|
65
|
+
@@types ||= {}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
module Types
|
71
|
+
def ArrayType(inner_type)
|
72
|
+
AnnotationTypes::ArrayT.new(inner_type)
|
73
|
+
end
|
74
|
+
|
75
|
+
def HashType(inner_type, key_type = String)
|
76
|
+
AnnotationTypes::HashT.new(inner_type, key_type)
|
77
|
+
end
|
78
|
+
|
79
|
+
def Optional(inner_type, default)
|
80
|
+
AnnotationTypes::OptionalT.new(inner_type, default)
|
81
|
+
end
|
82
|
+
|
83
|
+
def Type(type)
|
84
|
+
AnnotationTypes::CustomT.new(type)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
begin
|
91
|
+
class Boolean; end unless Module.const_get("Boolean").is_a?(Class)
|
92
|
+
rescue NameError
|
93
|
+
class Boolean; end
|
94
|
+
end
|
95
|
+
|
96
|
+
begin
|
97
|
+
class Uuid; end unless Module.const_get("Uuid").is_a?(Class)
|
98
|
+
rescue NameError
|
99
|
+
class Uuid; end
|
100
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module RailsParamValidation
|
2
|
+
|
3
|
+
class MatchResult
|
4
|
+
attr_reader :errors, :matching, :value
|
5
|
+
|
6
|
+
def initialize(value, path = nil, error = nil)
|
7
|
+
@value = value
|
8
|
+
@errors = []
|
9
|
+
|
10
|
+
unless error.nil?
|
11
|
+
@errors.push(path: path, message: error)
|
12
|
+
end
|
13
|
+
|
14
|
+
@matching = error.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def matches?
|
18
|
+
@matching
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [MatchResult] other
|
22
|
+
# @return [MatchResult]
|
23
|
+
def merge!(other)
|
24
|
+
@matching = @matching && other.matching
|
25
|
+
@errors += other.errors
|
26
|
+
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def error_messages
|
31
|
+
@errors.map { |e| { path: e[:path].join('/'), message: e[:message] } }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Validator
|
36
|
+
attr_reader :schema
|
37
|
+
|
38
|
+
def initialize(schema)
|
39
|
+
@schema = schema
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [MatchData]
|
43
|
+
def matches?(path, structure)
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_openapi
|
47
|
+
raise NotImplementedError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative "./errors/no_matching_factory"
|
2
|
+
require_relative "./validator"
|
3
|
+
|
4
|
+
module RailsParamValidation
|
5
|
+
|
6
|
+
class ValidatorFactory
|
7
|
+
|
8
|
+
# @param [ValidatorFactory] factory
|
9
|
+
def self.register(factory)
|
10
|
+
factories.push factory
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Array<ValidatorFactory>]
|
14
|
+
def self.factories
|
15
|
+
@@factories ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Validator]
|
19
|
+
def self.create(schema)
|
20
|
+
factory = factories.detect { |f| f.supports? schema }
|
21
|
+
|
22
|
+
if factory.nil?
|
23
|
+
raise NoMatchingFactory.new(schema)
|
24
|
+
end
|
25
|
+
|
26
|
+
factory.create schema
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Boolean]
|
30
|
+
def supports?(schema)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return Validator
|
34
|
+
def create(schema)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RailsParamValidation
|
2
|
+
|
3
|
+
class AlternativesValidator < Validator
|
4
|
+
# @param [Array] schema
|
5
|
+
def initialize(schema)
|
6
|
+
super schema
|
7
|
+
|
8
|
+
@inner_validators = schema.map { |value| ValidatorFactory.create(value) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches?(path, data)
|
12
|
+
result = MatchResult.new nil, path, "The value did not match any of the alternatives"
|
13
|
+
|
14
|
+
@inner_validators.each_with_index do |validator, idx|
|
15
|
+
match = validator.matches?(path + ["[#{idx}]"], data)
|
16
|
+
|
17
|
+
if match.matches?
|
18
|
+
return match
|
19
|
+
else
|
20
|
+
result.merge! match
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
result
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_openapi
|
28
|
+
{ oneOf: @inner_validators.map(&:to_openapi) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class AlternativesValidatorFactory < ValidatorFactory
|
33
|
+
def supports?(schema)
|
34
|
+
schema.is_a? Array
|
35
|
+
end
|
36
|
+
|
37
|
+
def create(schema)
|
38
|
+
AlternativesValidator.new schema
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module RailsParamValidation
|
2
|
+
|
3
|
+
class ArrayValidator < Validator
|
4
|
+
# @param [ArrayT] schema
|
5
|
+
def initialize(schema)
|
6
|
+
super schema
|
7
|
+
|
8
|
+
@inner_validator = ValidatorFactory.create schema.inner_type
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches?(path, data)
|
12
|
+
# Don't proceed if it is not an array at all
|
13
|
+
unless data.is_a? Array
|
14
|
+
return MatchResult.new nil, path, "Expected an array"
|
15
|
+
end
|
16
|
+
|
17
|
+
value = []
|
18
|
+
result = MatchResult.new nil
|
19
|
+
|
20
|
+
# Verify each entry
|
21
|
+
data.each_with_index do |entry, index|
|
22
|
+
match = @inner_validator.matches?(path + [index.to_s], entry)
|
23
|
+
|
24
|
+
if match.matches?
|
25
|
+
value.push match.value
|
26
|
+
else
|
27
|
+
result.merge! match
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
result.matches? ? MatchResult.new(value) : result
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_openapi
|
35
|
+
{ type: :array, items: @inner_validator.to_openapi }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class ArrayValidatorFactory < ValidatorFactory
|
40
|
+
def supports?(schema)
|
41
|
+
schema.is_a? AnnotationTypes::ArrayT
|
42
|
+
end
|
43
|
+
|
44
|
+
def create(schema)
|
45
|
+
ArrayValidator.new schema
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RailsParamValidation
|
2
|
+
|
3
|
+
class BooleanValidator < Validator
|
4
|
+
def initialize(schema)
|
5
|
+
super schema
|
6
|
+
end
|
7
|
+
|
8
|
+
def matches?(path, data)
|
9
|
+
if data.is_a?(TrueClass) || data.is_a?(FalseClass)
|
10
|
+
return MatchResult.new data
|
11
|
+
end
|
12
|
+
|
13
|
+
case data
|
14
|
+
when "true"
|
15
|
+
return MatchResult.new true
|
16
|
+
when "false"
|
17
|
+
return MatchResult.new false
|
18
|
+
else
|
19
|
+
return MatchResult.new(nil, path, "Expected a boolean (true, false)")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_openapi
|
24
|
+
{ type: :boolean }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class BooleanValidatorFactory < ValidatorFactory
|
29
|
+
def supports?(schema)
|
30
|
+
schema == Boolean
|
31
|
+
end
|
32
|
+
|
33
|
+
def create(schema)
|
34
|
+
BooleanValidator.new schema
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RailsParamValidation
|
2
|
+
|
3
|
+
class ConstantValidator < Validator
|
4
|
+
def initialize(schema)
|
5
|
+
super schema
|
6
|
+
|
7
|
+
@constant = schema
|
8
|
+
end
|
9
|
+
|
10
|
+
def matches?(path, data)
|
11
|
+
|
12
|
+
if data.to_s == @constant.to_s
|
13
|
+
if @constant.is_a?(String)
|
14
|
+
MatchResult.new @constant.clone
|
15
|
+
else
|
16
|
+
MatchResult.new @constant
|
17
|
+
end
|
18
|
+
else
|
19
|
+
MatchResult.new nil, path, "Expected value #{@constant.to_s}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_openapi
|
24
|
+
ValidatorFactory.create(schema.class).to_openapi.merge(enum: [schema])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class ConstantValidatorFactory < ValidatorFactory
|
29
|
+
def supports?(schema)
|
30
|
+
![String, Symbol, Numeric, TrueClass, FalseClass].detect { |klass| schema.is_a? klass }.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def create(schema)
|
34
|
+
ConstantValidator.new schema
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module RailsParamValidation
|
2
|
+
|
3
|
+
class CustomTypeValidator < Validator
|
4
|
+
def initialize(type)
|
5
|
+
super type
|
6
|
+
@validator = ValidatorFactory.create type.schema
|
7
|
+
end
|
8
|
+
|
9
|
+
def matches?(path, data)
|
10
|
+
@validator.matches? path, data
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_openapi
|
14
|
+
{ '$ref': "#/components/schemas/#{schema.type}" }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class CustomTypeValidatorFactory < ValidatorFactory
|
19
|
+
def supports?(schema)
|
20
|
+
schema.is_a? AnnotationTypes::CustomT
|
21
|
+
end
|
22
|
+
|
23
|
+
def create(schema)
|
24
|
+
CustomTypeValidator.new schema
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RailsParamValidation
|
2
|
+
|
3
|
+
class DateValidator < Validator
|
4
|
+
def initialize(schema)
|
5
|
+
super schema
|
6
|
+
end
|
7
|
+
|
8
|
+
def matches?(path, data)
|
9
|
+
if data.is_a?(DateTime)
|
10
|
+
MatchResult.new data.to_date
|
11
|
+
elsif data.is_a?(Time)
|
12
|
+
MatchResult.new data.to_date
|
13
|
+
elsif data.is_a?(String)
|
14
|
+
begin
|
15
|
+
MatchResult.new(data.to_date || raise(ArgumentError))
|
16
|
+
rescue ArgumentError
|
17
|
+
MatchResult.new nil, path, "Expected a date"
|
18
|
+
end
|
19
|
+
else
|
20
|
+
MatchResult.new nil, path, "Expected a date"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_openapi
|
25
|
+
{ type: :string, format: 'date' }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class DateValidatorFactory < ValidatorFactory
|
30
|
+
def supports?(schema)
|
31
|
+
schema == Date
|
32
|
+
end
|
33
|
+
|
34
|
+
def create(schema)
|
35
|
+
DateValidator.new schema
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RailsParamValidation
|
2
|
+
|
3
|
+
class DateTimeValidator < Validator
|
4
|
+
def initialize(schema)
|
5
|
+
super schema
|
6
|
+
end
|
7
|
+
|
8
|
+
def matches?(path, data)
|
9
|
+
if data.is_a?(DateTime)
|
10
|
+
MatchResult.new data
|
11
|
+
elsif data.is_a?(Time)
|
12
|
+
MatchResult.new data.to_datetime
|
13
|
+
elsif data.is_a?(String)
|
14
|
+
begin
|
15
|
+
MatchResult.new(data.to_datetime || raise(ArgumentError))
|
16
|
+
rescue ArgumentError
|
17
|
+
MatchResult.new nil, path, "Expected a date-time"
|
18
|
+
end
|
19
|
+
else
|
20
|
+
MatchResult.new nil, path, "Expected a date-time"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_openapi
|
25
|
+
{ type: :string, format: 'date-time' }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class DateTimeValidatorFactory < ValidatorFactory
|
30
|
+
def supports?(schema)
|
31
|
+
schema == DateTime
|
32
|
+
end
|
33
|
+
|
34
|
+
def create(schema)
|
35
|
+
DateTimeValidator.new schema
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RailsParamValidation
|
2
|
+
|
3
|
+
class FloatValidator < Validator
|
4
|
+
def initialize(schema)
|
5
|
+
super schema
|
6
|
+
end
|
7
|
+
|
8
|
+
def matches?(path, data)
|
9
|
+
if data.is_a? Numeric
|
10
|
+
return MatchResult.new data.to_f
|
11
|
+
end
|
12
|
+
|
13
|
+
unless data.is_a? String
|
14
|
+
return MatchResult.new(nil, path, "Expected a float")
|
15
|
+
end
|
16
|
+
|
17
|
+
begin
|
18
|
+
return MatchResult.new(Float(data))
|
19
|
+
rescue ArgumentError
|
20
|
+
return MatchResult.new(nil, path, "Expected a float")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_openapi
|
25
|
+
{ type: :number, format: :double }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class FloatValidatorFactory < ValidatorFactory
|
30
|
+
def supports?(schema)
|
31
|
+
schema == Float
|
32
|
+
end
|
33
|
+
|
34
|
+
def create(schema)
|
35
|
+
FloatValidator.new schema
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module RailsParamValidation
|
2
|
+
|
3
|
+
class HashValidator < Validator
|
4
|
+
# @param [HashT] schema
|
5
|
+
def initialize(schema)
|
6
|
+
super schema
|
7
|
+
|
8
|
+
@value_validator = ValidatorFactory.create schema.inner_type
|
9
|
+
@key_validator = ValidatorFactory.create schema.key_type
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches?(path, data)
|
13
|
+
# Don't proceed if it is not an array at all
|
14
|
+
unless data.is_a? Hash
|
15
|
+
return MatchResult.new nil, path, "Expected a hash"
|
16
|
+
end
|
17
|
+
|
18
|
+
value = {}
|
19
|
+
result = MatchResult.new nil
|
20
|
+
|
21
|
+
# Verify each entry
|
22
|
+
data.each do |key, entry|
|
23
|
+
match_key = @value_validator.matches?(path + ["#{key}[key]"], key)
|
24
|
+
match_value = @value_validator.matches?(path + [key], entry)
|
25
|
+
|
26
|
+
if match_value.matches? && match_key.matches?
|
27
|
+
value[match_key.value] = match_value.value
|
28
|
+
else
|
29
|
+
result.merge! match_key unless match_key.matches?
|
30
|
+
result.merge! match_value unless match_value.matches?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
result.matches? ? MatchResult.new(value) : result
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_openapi
|
38
|
+
{ type: :object, additionalProperties: @value_validator.to_openapi }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class HashValidatorFactory < ValidatorFactory
|
43
|
+
def supports?(schema)
|
44
|
+
schema.is_a? AnnotationTypes::HashT
|
45
|
+
end
|
46
|
+
|
47
|
+
def create(schema)
|
48
|
+
HashValidator.new schema
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RailsParamValidation
|
2
|
+
|
3
|
+
class IntegerValidator < Validator
|
4
|
+
def initialize(schema)
|
5
|
+
super schema
|
6
|
+
end
|
7
|
+
|
8
|
+
def matches?(path, data)
|
9
|
+
if data.is_a? Integer
|
10
|
+
return MatchResult.new data
|
11
|
+
end
|
12
|
+
|
13
|
+
unless data.is_a? String
|
14
|
+
return MatchResult.new(nil, path, "Expected an integer")
|
15
|
+
end
|
16
|
+
|
17
|
+
begin
|
18
|
+
return MatchResult.new(Integer(data))
|
19
|
+
rescue ArgumentError
|
20
|
+
return MatchResult.new(nil, path, "Expected an integer")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_openapi
|
25
|
+
{ type: :integer }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class IntegerValidatorFactory < ValidatorFactory
|
30
|
+
def supports?(schema)
|
31
|
+
schema == Integer
|
32
|
+
end
|
33
|
+
|
34
|
+
def create(schema)
|
35
|
+
IntegerValidator.new schema
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module RailsParamValidation
|
2
|
+
|
3
|
+
class ObjectValidator < Validator
|
4
|
+
# @param [Hash] schema
|
5
|
+
def initialize(schema)
|
6
|
+
super schema
|
7
|
+
|
8
|
+
@inner_validators = schema.map { |key, value| [key, ValidatorFactory.create(value)] }.to_h
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches?(path, data)
|
12
|
+
# Don't proceed if it is not hash at all
|
13
|
+
unless data.is_a? Hash
|
14
|
+
return MatchResult.new nil, path, "Expected an object"
|
15
|
+
end
|
16
|
+
|
17
|
+
value = {}
|
18
|
+
result = MatchResult.new nil
|
19
|
+
|
20
|
+
# Verify each entry
|
21
|
+
@inner_validators.each do |property, validator|
|
22
|
+
match = validator.matches?(path + [property], data.key?(property.to_s) ? data[property.to_s] : data[property.to_sym])
|
23
|
+
|
24
|
+
if match.matches?
|
25
|
+
value[property] = match.value
|
26
|
+
else
|
27
|
+
result.merge! match
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
additional_properties = data.keys.reject { |k| @inner_validators.key?(k.to_s) || @inner_validators.key?(k.to_sym) }
|
32
|
+
additional_properties.each do |property|
|
33
|
+
result.merge! MatchResult.new(nil, path + [property], "Unknown property")
|
34
|
+
end
|
35
|
+
|
36
|
+
result.matches? ? MatchResult.new(value) : result
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_openapi
|
40
|
+
openapi = { type: :object, properties: {} }
|
41
|
+
required = []
|
42
|
+
schema.each do |key, _|
|
43
|
+
validator = @inner_validators[key]
|
44
|
+
openapi[:properties][key] = validator.to_openapi
|
45
|
+
required.push key unless validator.is_a? OptionalValidator
|
46
|
+
end
|
47
|
+
|
48
|
+
openapi[:required] = required if required.any?
|
49
|
+
openapi
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class ObjectValidatorFactory < ValidatorFactory
|
54
|
+
def supports?(schema)
|
55
|
+
schema.is_a? Hash
|
56
|
+
end
|
57
|
+
|
58
|
+
def create(schema)
|
59
|
+
ObjectValidator.new schema
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RailsParamValidation
|
2
|
+
|
3
|
+
class OptionalValidator < Validator
|
4
|
+
def initialize(schema)
|
5
|
+
super schema
|
6
|
+
|
7
|
+
@inner_validator = ValidatorFactory.create schema.inner_type
|
8
|
+
@default = schema.default
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches?(path, data)
|
12
|
+
if data.nil?
|
13
|
+
if @default.is_a? Proc
|
14
|
+
MatchResult.new @default.call
|
15
|
+
else
|
16
|
+
MatchResult.new @default
|
17
|
+
end
|
18
|
+
else
|
19
|
+
@inner_validator.matches? path, data
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_openapi
|
24
|
+
child = @inner_validator.to_openapi
|
25
|
+
child[:nullable] = true
|
26
|
+
if child.key? :enum
|
27
|
+
child[:enum].push nil
|
28
|
+
end
|
29
|
+
|
30
|
+
child
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class OptionalValidatorFactory < ValidatorFactory
|
35
|
+
def supports?(schema)
|
36
|
+
schema.is_a? AnnotationTypes::OptionalT
|
37
|
+
end
|
38
|
+
|
39
|
+
def create(schema)
|
40
|
+
OptionalValidator.new schema
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|