rails-param-validation 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|