saphyr 0.4.0.beta

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 48f8284121fe854fd431ed4afa7a656206c17d4d1ddcb21ad6540db9ece53f15
4
+ data.tar.gz: 454d04e4c206d9bf827690a656d8d60573ebebfb6a625af409e4b1be560d038e
5
+ SHA512:
6
+ metadata.gz: 16b265ab45002d654a716b994c6df15b48eafdd4617d3e1cc6e968905a5bd7172458c63c37213e3e2c1051a041b1cd1da9b97d108aba586b6b92137e5d7b8798
7
+ data.tar.gz: 0305d518e223973cb92c269030cb356d30d7c609e8abf86899cc6ab017164e853258bf67989a76c8e39521c6b804ced86254e42131afd851c0629022c7711a4b
data/CHANGELOG ADDED
@@ -0,0 +1,7 @@
1
+ No changelog update during all the beta development phase.
2
+
3
+ -----------------------------------------------------------------
4
+ Version: 0.1.0.beta - 2023-08-30
5
+ -----------------------------------------------------------------
6
+
7
+ - Initial release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Olivier Delbos
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,58 @@
1
+ module Saphyr
2
+ module Asserts
3
+
4
+ module BaseAssert
5
+ def assert_boolean value
6
+ value.is_a? TrueClass or value.is_a? FalseClass
7
+ end
8
+
9
+ def assert_class klass, value, errors, error_code=Fields::FieldBase::ERR_TYPE
10
+ klass = [klass] unless klass.is_a? Array
11
+ test = false
12
+ klass.each do |k|
13
+ if value.is_a? k; test = true; break; end
14
+ end
15
+ unless test
16
+ errors << {
17
+ type: err(error_code),
18
+ data: {
19
+ type: klass.to_s,
20
+ got: value.class.name,
21
+ }
22
+ }
23
+ end
24
+ test
25
+ end
26
+
27
+ def assert_eq opt_value, value, errors, error_code=Fields::FieldBase::ERR_EQ
28
+ return nil if opt_value.nil?
29
+ unless value == opt_value
30
+ errors << {
31
+ type: err(error_code),
32
+ data: {
33
+ _val: value,
34
+ eq: opt_value,
35
+ }
36
+ }
37
+ return false
38
+ end
39
+ true
40
+ end
41
+
42
+ def assert_in opt_values, value, errors, error_code=Fields::FieldBase::ERR_IN
43
+ return nil if opt_values.nil?
44
+ unless opt_values.include? value
45
+ errors << {
46
+ type: err(error_code),
47
+ data: {
48
+ _val: value,
49
+ in: opt_values,
50
+ }
51
+ }
52
+ return false
53
+ end
54
+ true
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,28 @@
1
+ module Saphyr
2
+ module Asserts
3
+
4
+ module ErrorConstants
5
+ # Base
6
+ ERR_NOT_NULLABLE = 'not-nullable'
7
+ ERR_BAD_FORMAT = 'bad-format'
8
+ ERR_TYPE = 'type'
9
+ ERR_IN = 'in'
10
+ ERR_EQ = 'eq'
11
+ # Size
12
+ ERR_SIZE_EQ = 'size-eq'
13
+ ERR_SIZE_LEN = 'size-len'
14
+ ERR_SIZE_MIN = 'size-min'
15
+ ERR_SIZE_MAX = 'size-max'
16
+ # Numeric
17
+ ERR_GT = 'gt'
18
+ ERR_GTE = 'gte'
19
+ ERR_LT = 'lt'
20
+ ERR_LTE = 'lte'
21
+ # String
22
+ ERR_LEN = 'len'
23
+ ERR_MIN = 'min'
24
+ ERR_MAX = 'max'
25
+ ERR_REGEXP = 'regexp'
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,66 @@
1
+ module Saphyr
2
+ module Asserts
3
+
4
+ module NumericAssert
5
+ def assert_numeric_gt opt_value, value, errors, error_code=Fields::FieldBase::ERR_GT
6
+ return nil if opt_value.nil?
7
+ if value <= opt_value
8
+ errors << {
9
+ type: err(error_code),
10
+ data: {
11
+ _val: value,
12
+ gt: opt_value,
13
+ }
14
+ }
15
+ return false
16
+ end
17
+ true
18
+ end
19
+
20
+ def assert_numeric_gte opt_value, value, errors, error_code=Fields::FieldBase::ERR_GTE
21
+ return nil if opt_value.nil?
22
+ if value < opt_value
23
+ errors << {
24
+ type: err(error_code),
25
+ data: {
26
+ _val: value,
27
+ gte: opt_value,
28
+ }
29
+ }
30
+ return false
31
+ end
32
+ true
33
+ end
34
+
35
+ def assert_numeric_lt opt_value, value, errors, error_code=Fields::FieldBase::ERR_LT
36
+ return nil if opt_value.nil?
37
+ if value >= opt_value
38
+ errors << {
39
+ type: err(error_code),
40
+ data: {
41
+ _val: value,
42
+ lt: opt_value,
43
+ }
44
+ }
45
+ return false
46
+ end
47
+ true
48
+ end
49
+
50
+ def assert_numeric_lte opt_value, value, errors, error_code=Fields::FieldBase::ERR_LTE
51
+ return nil if opt_value.nil?
52
+ if value > opt_value
53
+ errors << {
54
+ type: err(error_code),
55
+ data: {
56
+ _val: value,
57
+ lte: opt_value,
58
+ }
59
+ }
60
+ return false
61
+ end
62
+ true
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,54 @@
1
+ module Saphyr
2
+ module Asserts
3
+
4
+ module SizeAssert
5
+ def assert_size_len opt_value, value, errors, error_code=Fields::FieldBase::ERR_SIZE_LEN
6
+ return nil if opt_value.nil?
7
+ unless value.size == opt_value
8
+ errors << {
9
+ type: err(error_code),
10
+ data: {
11
+ _val: value,
12
+ len: opt_value,
13
+ got: value.size,
14
+ }
15
+ }
16
+ return false
17
+ end
18
+ true
19
+ end
20
+
21
+ def assert_size_min opt_value, value, errors, error_code=Fields::FieldBase::ERR_SIZE_MIN
22
+ return nil if opt_value.nil?
23
+ if value.size < opt_value
24
+ errors << {
25
+ type: err(error_code),
26
+ data: {
27
+ _val: value,
28
+ min: opt_value,
29
+ got: value.size,
30
+ }
31
+ }
32
+ return false
33
+ end
34
+ true
35
+ end
36
+
37
+ def assert_size_max opt_value, value, errors, error_code=Fields::FieldBase::ERR_SIZE_MAX
38
+ return nil if opt_value.nil?
39
+ if value.size > opt_value
40
+ errors << {
41
+ type: err(error_code),
42
+ data: {
43
+ _val: value,
44
+ max: opt_value,
45
+ got: value.size,
46
+ }
47
+ }
48
+ return false
49
+ end
50
+ true
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,21 @@
1
+ module Saphyr
2
+ module Asserts
3
+
4
+ module StringAssert
5
+ def assert_string_regexp opt_value, value, errors, error_code=Fields::FieldBase::ERR_REGEXP
6
+ return nil if opt_value.nil?
7
+ unless value =~ opt_value
8
+ errors << {
9
+ type: err(error_code),
10
+ data: {
11
+ _val: value,
12
+ regexp: opt_value,
13
+ }
14
+ }
15
+ return false
16
+ end
17
+ true
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Saphyr
4
+ # Group all modules related to atomic assertions.
5
+ #
6
+ # @api private
7
+ module Asserts
8
+ end
9
+ end
10
+
11
+ require_relative './asserts/error_constants'
12
+ require_relative './asserts/base_assert'
13
+ require_relative './asserts/size_assert'
14
+ require_relative './asserts/numeric_assert'
15
+ require_relative './asserts/string_assert'
@@ -0,0 +1,220 @@
1
+ require 'json'
2
+
3
+ module Saphyr
4
+
5
+ # The engine used to handle the validation processus.
6
+ # @api private
7
+ class Engine
8
+
9
+ # Class used to encapsule the validation engine context.
10
+ # @api private
11
+ class Context
12
+ attr_reader :validators, :schema, :data, :fragment, :path, :errors
13
+
14
+ def initialize(validators, schema, data, fragment, path, errors=[])
15
+ @validators = validators
16
+ @schema = schema
17
+ @data = data
18
+ @fragment = fragment
19
+ @path = path
20
+ @errors = errors
21
+ end
22
+
23
+ # Create a new context derived from the current one.
24
+ # @param schema [Saphyr::Schema] The schema associated with the new context.
25
+ # @param fragment [Hash] The data fragment.
26
+ # @param path [String] The path of the new context.
27
+ def derive(schema, fragment, path)
28
+ Context.new @validators, schema, @data, fragment, path, @errors
29
+ end
30
+
31
+ # Get the current field path from the root of the document.
32
+ # @return [String]
33
+ def get_path(name)
34
+ return @path + name if @path == '//'
35
+ return @path if name == ''
36
+ @path + '.' + name
37
+ end
38
+
39
+ # Find a schema given his name.
40
+ # Lookup first into the local validators if there isn't any schema found then
41
+ # lookup into the global schemas.
42
+ # @param name [Symbol] The name of the schema.
43
+ # @return [Saphyr::Schema]
44
+ # @rmaise [Saphyr::Error] if no schema was found.
45
+ def find_schema(name)
46
+ @validators.each do |validator|
47
+ schema = validator.find_schema name
48
+ return schema unless schema.nil?
49
+ end
50
+ schema = Saphyr.global_schema name
51
+ raise Saphyr::Error.new "Cannot find schema name: #{name}" if schema.nil?
52
+ schema
53
+ end
54
+
55
+ # Get data needed for validation
56
+ # @return The full data or a fragment of data is it exists.
57
+ def data_to_validate()
58
+ return @data if @fragment.nil?
59
+ @fragment
60
+ end
61
+ end
62
+
63
+ # -----
64
+
65
+ def initialize(ctx)
66
+ @ctx = ctx
67
+ end
68
+
69
+ def validate
70
+ array_validation if @ctx.schema.root_array?
71
+ object_validation if @ctx.schema.root_object?
72
+ end
73
+
74
+ private
75
+
76
+ def array_validation()
77
+ fields = @ctx.schema.fields
78
+ unless fields.size == 1
79
+ raise Saphyr::Error.new 'When root is :array, only :_root_ field must exists'
80
+ end
81
+
82
+ field = fields['_root_']
83
+ if field.nil?
84
+ raise Saphyr::Error.new 'When root is :array, must define :_root_ field'
85
+ end
86
+
87
+ errors = field.validate @ctx, '', @ctx.data_to_validate
88
+ unless errors.size == 0
89
+ @ctx.errors << {
90
+ path: '//',
91
+ errors: errors,
92
+ }
93
+ end
94
+ end
95
+
96
+ def object_validation()
97
+ # Computte all conditional fields
98
+ allowed_cond_fields = []
99
+ forbidden_cond_fields = []
100
+ @ctx.schema.conditionals.each do |data|
101
+ condition, schema = data
102
+ if @ctx.validators.last.send condition
103
+ allowed_cond_fields += schema.fields.keys
104
+ else
105
+ forbidden_cond_fields += schema.fields.keys
106
+ end
107
+ end
108
+
109
+ do_validation @ctx.schema, allowed_cond_fields, forbidden_cond_fields, []
110
+
111
+ fields = @ctx.schema.fields.keys
112
+ @ctx.schema.conditionals.each do |data|
113
+ condition, schema = data
114
+
115
+ if @ctx.validators.last.send condition
116
+ do_validation schema, allowed_cond_fields, forbidden_cond_fields, fields
117
+ end
118
+ end
119
+ end
120
+
121
+ def do_validation(schema, allowed_cond_fields, forbidden_cond_fields, exclude_fields)
122
+ data = @ctx.data_to_validate
123
+
124
+ # Apply casting
125
+ schema.casts.each_pair do |field, method|
126
+ data[field] = @ctx.validators.last.send method, data[field]
127
+ end
128
+
129
+ fields = schema.fields
130
+ field_keys = fields.keys
131
+ data_keys = data.keys
132
+
133
+ field_names = field_keys.union data_keys
134
+ field_names.each do |name|
135
+ field_path = @ctx.get_path name
136
+ field = fields[name]
137
+
138
+ if field.nil?
139
+ next unless @ctx.schema.strict?
140
+ next if allowed_cond_fields.include? name
141
+ next if exclude_fields.include? name
142
+
143
+ # If there is already an error for this field we skip adding a new one
144
+ # Bacause if there are many conditional schema, each one will add
145
+ # the field errors.
146
+ k = @ctx.errors.select { |err| err[:path] == field_path }
147
+ next if k.size != 0
148
+
149
+ if forbidden_cond_fields.include? name
150
+ @ctx.errors << {
151
+ path: field_path,
152
+ errors: [
153
+ {
154
+ type: 'conditional:not-allowed',
155
+ data: { field: name }
156
+ },
157
+ ],
158
+ }
159
+ else
160
+ @ctx.errors << {
161
+ path: field_path,
162
+ errors: [
163
+ {
164
+ type: 'strict_mode:missing_in_schema',
165
+ data: { field: name }
166
+ },
167
+ ],
168
+ }
169
+ end
170
+ next
171
+ end
172
+
173
+ unless data.key? name
174
+ next unless @ctx.schema.strict?
175
+ if field.required?
176
+ @ctx.errors << {
177
+ path: field_path,
178
+ errors: [
179
+ {
180
+ type: 'strict_mode:missing_in_data',
181
+ data: {
182
+ field: name,
183
+ }
184
+ }
185
+ ],
186
+ }
187
+ end
188
+ next
189
+ end
190
+
191
+ value = data[name]
192
+
193
+ if value.nil?
194
+ unless field.nullable?
195
+ @ctx.errors << {
196
+ path: field_path,
197
+ errors: [
198
+ {
199
+ type: Saphyr::Fields::FieldBase::ERR_NOT_NULLABLE,
200
+ data: {
201
+ field: name,
202
+ }
203
+ }
204
+ ],
205
+ }
206
+ end
207
+ next
208
+ end
209
+
210
+ errors = field.validate @ctx, name, value
211
+ unless errors.size == 0
212
+ @ctx.errors << {
213
+ path: field_path,
214
+ errors: errors,
215
+ }
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,80 @@
1
+ module Saphyr
2
+ module Fields
3
+
4
+ class ArrayField < FieldBase
5
+ PREFIX = 'array'
6
+
7
+ AUTHORIZED_OPTIONS = [:len, :min, :max, :of_type, :of_schema, :opts]
8
+
9
+ REQUIRED_ONE_OF_OPTIONS = [:of_type, :of_schema]
10
+
11
+ EXCLUSIVE_OPTIONS = [
12
+ [ :len, [:min, :max] ],
13
+ [ :of_schema, [:opts] ],
14
+ ]
15
+
16
+ # Cannot have: min == max, use +:len+ instead
17
+ NOT_EQUALS_OPTIONS = [
18
+ [:min, :max],
19
+ ]
20
+
21
+ # Cannot have: min > max
22
+ NOT_SUP_OPTIONS = [
23
+ [:min, :max],
24
+ ]
25
+
26
+ def initialize(opts={})
27
+ super
28
+
29
+ # TODO : Check that :of_type exists
30
+
31
+ # TODO : Check that :of_schema exists
32
+ end
33
+
34
+ private
35
+
36
+ def do_validate(ctx, name, value, errors)
37
+ return unless assert_class Array, value, errors
38
+ assert_size_len @opts[:len], value, errors
39
+ assert_size_min @opts[:min], value, errors
40
+ assert_size_max @opts[:max], value, errors
41
+
42
+ of_type = @opts[:of_type]
43
+ unless of_type.nil?
44
+ opts = {}
45
+ field_type_opts = @opts[:opts]
46
+ opts = field_type_opts unless field_type_opts.nil?
47
+
48
+ # Raise exception if field type is not found
49
+ field_type = Saphyr.config.instanciate_field_type of_type, opts
50
+ value.each_with_index do |current, index|
51
+ field_path = make_path ctx, name, index
52
+ errors = field_type.validate ctx, name, current
53
+ if errors.size != 0
54
+ ctx.errors << {
55
+ path: field_path,
56
+ errors: errors,
57
+ }
58
+ end
59
+ end
60
+ end
61
+
62
+ of_schema = @opts[:of_schema]
63
+ unless of_schema.nil?
64
+ value.each_with_index do |current, index|
65
+ field_path = make_path ctx, name, index
66
+ schema = ctx.find_schema of_schema # Raise exceptions if not found
67
+ new_ctx = ctx.derive schema, current, field_path
68
+ engine = Saphyr::Engine.new new_ctx
69
+ engine.validate
70
+ end
71
+ end
72
+ end
73
+
74
+ def make_path(ctx, name, index)
75
+ return ctx.get_path "[#{index}]" if name == ''
76
+ ctx.get_path "#{name}.[#{index}]"
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,20 @@
1
+ module Saphyr
2
+ module Fields
3
+
4
+ # The +boolean+ field type
5
+ #
6
+ # Allowed options are: +:eq+.
7
+ class BooleanField < FieldBase
8
+ PREFIX = 'boolean'
9
+
10
+ AUTHORIZED_OPTIONS = [:eq]
11
+
12
+ private
13
+
14
+ def do_validate(ctx, name, value, errors)
15
+ return unless assert_class [TrueClass, FalseClass], value, errors
16
+ assert_eq @opts[:eq], value, errors
17
+ end
18
+ end
19
+ end
20
+ end