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.
@@ -0,0 +1,235 @@
1
+ module Saphyr
2
+ module Fields
3
+
4
+ class FieldBase
5
+ # Prefix use to format error code
6
+ # @note You must Override this class constants in your field type class
7
+ PREFIX = 'base'
8
+
9
+ include Saphyr::Asserts::ErrorConstants
10
+ include Saphyr::Asserts::BaseAssert
11
+ include Saphyr::Asserts::SizeAssert
12
+ include Saphyr::Asserts::NumericAssert
13
+ include Saphyr::Asserts::StringAssert
14
+
15
+ # A hash containing the options of the field.
16
+ attr_reader :opts
17
+
18
+ # Every field type has the +:required+ and +:nullable+ options.
19
+ # @api private
20
+ DEFAULT_OPT_VALUES = {required: true, nullable: false}.freeze
21
+ # @api private
22
+ DEFAULT_OPTS = DEFAULT_OPT_VALUES.keys.freeze
23
+
24
+ # NOTE:
25
+ # Override following constants to manage options
26
+
27
+ # List of authorized options.
28
+ # @note Override this class constant if you want to use this feature.
29
+ AUTHORIZED_OPTIONS = []
30
+
31
+ # List of required options.
32
+ # @note Override this class constant if you want to use this feature.
33
+ REQUIRED_OPTIONS = []
34
+
35
+ # Require one and only of the listed options.
36
+ # @note Override this class constant if you want to use this feature.
37
+ REQUIRED_ONE_OF_OPTIONS = []
38
+
39
+ # Definition of exclusive options
40
+ # @note Override this class constant if you want to use this feature.
41
+ EXCLUSIVE_OPTIONS = []
42
+
43
+ # List of options where value must not be equals to another option.
44
+ # (ex: min == max)
45
+ # @note Override this class constant if you want to use this feature.
46
+ NOT_EQUALS_OPTIONS = []
47
+
48
+ # List of options where value must not be superior to another option.
49
+ # (ex: lt > gt)
50
+ # @note Override this class constant if you want to use this feature.
51
+ NOT_SUP_OPTIONS = []
52
+
53
+ # List of options where value must not be superior or equals to another option.
54
+ # (ex: lt >= gt)
55
+ # @note Override this class constant if you want to use this feature.
56
+ NOT_SUP_OR_EQUALS_OPTIONS = []
57
+
58
+
59
+ def initialize(opts={})
60
+ if opts.key? :required
61
+ unless assert_boolean opts[:required]
62
+ raise Saphyr::Error.new "Option ':required' must be a Boolean"
63
+ end
64
+ end
65
+ if opts.key? :nullable
66
+ unless assert_boolean opts[:nullable]
67
+ raise Saphyr::Error.new "Option ':nullable' must be a Boolean"
68
+ end
69
+ end
70
+
71
+ unless authorized_options.size == 0
72
+ opts.keys.each do |opt|
73
+ next if opt == :required or opt == :nullable
74
+ unless authorized_options.include? opt
75
+ raise Saphyr::Error.new "Unauthorized option: #{opt}"
76
+ end
77
+ end
78
+ end
79
+
80
+ required_options.each do |opt|
81
+ unless opts.include? opt
82
+ raise Saphyr::Error.new "Missing required option: '#{opt}'"
83
+ end
84
+ end
85
+
86
+ unless required_one_of_options.size == 0
87
+ status, selected = false, nil
88
+ required_one_of_options.each do |opt|
89
+ if opts.include? opt
90
+ if status
91
+ raise Saphyr::Error.new "You can't provide both options at same time: '#{selected}' and '#{opt}'"
92
+ end
93
+ status = true
94
+ selected = opt
95
+ end
96
+ end
97
+ unless status
98
+ raise Saphyr::Error.new "You must provide one of the following options: '#{required_one_of_options.to_s}'"
99
+ end
100
+ end
101
+
102
+ exclusive_options.each do |data|
103
+ opt, excluded = data
104
+ if opts.include? opt
105
+ if excluded.first == :_all_
106
+ excluded = authorized_options - [opt]
107
+ end
108
+ unless opts.keys.intersection(excluded).size == 0
109
+ raise Saphyr::Error.new "You can't use #{excluded.to_s} options, if you use #{opt.to_s} options, if you use : :#{opt}"
110
+ end
111
+ end
112
+ end
113
+
114
+ not_equals_options.each do |data|
115
+ opt1, opt2 = data
116
+ if opts.include? opt1 and opts.include? opt2
117
+ if opts[opt1] == opts[opt2]
118
+ raise Saphyr::Error.new "Option '#{opt1} cannot be > to '#{opt2}'"
119
+ end
120
+ end
121
+ end
122
+
123
+ not_sup_options.each do |data|
124
+ opt1, opt2 = data
125
+ if opts.include? opt1 and opts.include? opt2
126
+ if opts[opt1] > opts[opt2]
127
+ raise Saphyr::Error.new "Option '#{opt1} cannot be > to '#{opt2}'"
128
+ end
129
+ end
130
+ end
131
+
132
+ not_sup_or_equals_options.each do |data|
133
+ opt1, opt2 = data
134
+ if opts.include? opt1 and opts.include? opt2
135
+ if opts[opt1] >= opts[opt2]
136
+ raise Saphyr::Error.new "Option '#{opt1} cannot be >= to '#{opt2}'"
137
+ end
138
+ end
139
+ end
140
+
141
+ @opts = DEFAULT_OPT_VALUES.merge opts
142
+ end
143
+
144
+ # -----
145
+
146
+ # Get the +PREFIX+ setting.
147
+ # @return [String] The prefix
148
+ def prefix
149
+ self.class::PREFIX
150
+ end
151
+
152
+ # -----
153
+
154
+ # Get the +AUTHORIZED_OPTIONS+ options
155
+ # @return [Array]
156
+ def authorized_options
157
+ self.class::AUTHORIZED_OPTIONS
158
+ end
159
+
160
+ # Get the +REQUIRED_OPTIONS+ options
161
+ # @return [Array]
162
+ def required_options
163
+ self.class::REQUIRED_OPTIONS
164
+ end
165
+
166
+ # Get the +REQUIRED_ONE_OF_OPTIONS+ options
167
+ # @return [Array]
168
+ def required_one_of_options
169
+ self.class::REQUIRED_ONE_OF_OPTIONS
170
+ end
171
+
172
+ # Get the +EXCLUSIVE_OPTIONS+ options
173
+ # @return [Array]
174
+ def exclusive_options
175
+ self.class::EXCLUSIVE_OPTIONS
176
+ end
177
+
178
+ # Get the +NOT_EQUALS_OPTIONS+ options
179
+ # @return [Array]
180
+ def not_equals_options
181
+ self.class::NOT_EQUALS_OPTIONS
182
+ end
183
+
184
+ # Get the +NOT_SUP_OPTIONS+ options
185
+ # @return [Array]
186
+ def not_sup_options
187
+ self.class::NOT_SUP_OPTIONS
188
+ end
189
+
190
+ # Get the +NOT_SUP_OR_EQUALS_OPTIONS+ options
191
+ # @return [Array]
192
+ def not_sup_or_equals_options
193
+ self.class::NOT_SUP_OR_EQUALS_OPTIONS
194
+ end
195
+
196
+ # -----
197
+
198
+ # Format the error code with the field prefix.
199
+ # @param code [String] The error code.
200
+ # @return [String] The formatted error code.
201
+ def err(code)
202
+ prefix + ':' + code
203
+ end
204
+
205
+ # Is the field required?
206
+ def required?
207
+ @opts[:required]
208
+ end
209
+
210
+ # Is the field nullable?
211
+ def nullable?
212
+ @opts[:nullable]
213
+ end
214
+
215
+ # -----
216
+
217
+ # Check if the field value is valid.
218
+ # @param ctx [Saphyr::Engine::Context] The engine context.
219
+ # @param name [String] The field name.
220
+ # @param value [String] The field value.
221
+ def validate(ctx, name, value)
222
+ # NOTE: Nullable is handle by the engine.
223
+ errors = []
224
+ do_validate ctx, name, value, errors
225
+ errors
226
+ end
227
+
228
+ private
229
+
230
+ def do_validate(ctx, name, value, errors)
231
+ raise Saphyr::Error.new 'Not implemented!'
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,49 @@
1
+ module Saphyr
2
+ module Fields
3
+
4
+ # The +float+ field type
5
+ #
6
+ # Allowed options are: +:eq, :gt, :gte, :lt, :lte, :in+.
7
+ class FloatField < FieldBase
8
+ PREFIX = 'float'
9
+
10
+ AUTHORIZED_OPTIONS = [:eq, :gt, :gte, :lt, :lte, :in]
11
+
12
+ EXCLUSIVE_OPTIONS = [
13
+ [ :eq, [:_all_] ],
14
+ [ :in, [:_all_] ],
15
+ [ :gt, [:gte] ],
16
+ [ :lt, [:lte] ],
17
+ ]
18
+
19
+ # Cannot have: lte == gte, use +:eq+ instead
20
+ NOT_EQUALS_OPTIONS = [
21
+ [:lte, :gte],
22
+ ]
23
+
24
+ # Cannot have: gte > lte
25
+ NOT_SUP_OPTIONS = [
26
+ [:gte, :lte],
27
+ ]
28
+
29
+ # Cannot have: gt >= lt ... and so on
30
+ NOT_SUP_OR_EQUALS_OPTIONS = [
31
+ [:gt, :lt],
32
+ [:gt, :lte],
33
+ [:gte, :lt],
34
+ ]
35
+
36
+ private
37
+
38
+ def do_validate(ctx, name, value, errors)
39
+ return unless assert_class Float, value, errors
40
+ assert_eq @opts[:eq], value, errors
41
+ assert_numeric_gt @opts[:gt], value, errors
42
+ assert_numeric_gte @opts[:gte], value, errors
43
+ assert_numeric_lt @opts[:lt], value, errors
44
+ assert_numeric_lte @opts[:lte], value, errors
45
+ assert_in @opts[:in], value, errors
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,49 @@
1
+ module Saphyr
2
+ module Fields
3
+
4
+ # The +integer+ field type
5
+ #
6
+ # Allowed options are: +:eq, :gt, :gte, :lt, :lte, :in+.
7
+ class IntegerField < FieldBase
8
+ PREFIX = 'integer'
9
+
10
+ AUTHORIZED_OPTIONS = [:eq, :gt, :gte, :lt, :lte, :in]
11
+
12
+ EXCLUSIVE_OPTIONS = [
13
+ [ :eq, [:_all_] ],
14
+ [ :in, [:_all_] ],
15
+ [ :gt, [:gte] ],
16
+ [ :lt, [:lte] ],
17
+ ]
18
+
19
+ # Cannot have: lte == gte, use +:eq+ instead
20
+ NOT_EQUALS_OPTIONS = [
21
+ [:lte, :gte],
22
+ ]
23
+
24
+ # Cannot have: gte > lte
25
+ NOT_SUP_OPTIONS = [
26
+ [:gte, :lte],
27
+ ]
28
+
29
+ # Cannot have: gt >= lt ... and so on
30
+ NOT_SUP_OR_EQUALS_OPTIONS = [
31
+ [:gt, :lt],
32
+ [:gt, :lte],
33
+ [:gte, :lt],
34
+ ]
35
+
36
+ private
37
+
38
+ def do_validate(ctx, name, value, errors)
39
+ return unless assert_class Integer, value, errors
40
+ assert_eq @opts[:eq], value, errors
41
+ assert_numeric_gt @opts[:gt], value, errors
42
+ assert_numeric_gte @opts[:gte], value, errors
43
+ assert_numeric_lt @opts[:lt], value, errors
44
+ assert_numeric_lte @opts[:lte], value, errors
45
+ assert_in @opts[:in], value, errors
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,26 @@
1
+ module Saphyr
2
+ module Fields
3
+
4
+ class SchemaField < FieldBase
5
+ PREFIX = 'schema'
6
+
7
+ AUTHORIZED_OPTIONS = [:name]
8
+
9
+ REQUIRED_OPTIONS = [:name]
10
+
11
+ private
12
+
13
+ def do_validate(ctx, name, value, errors)
14
+ # Find the schema
15
+ schema_name = @opts[:name]
16
+ schema = ctx.find_schema schema_name # Raise exceeption if not found
17
+
18
+ # Create derived engine to handle schema and data fragment.
19
+ field_path = ctx.get_path name
20
+ new_ctx = ctx.derive schema, value, field_path
21
+ engine = Saphyr::Engine.new new_ctx
22
+ engine.validate
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ module Saphyr
2
+ module Fields
3
+
4
+ # The +string+ field type
5
+ #
6
+ # Allowed options are: +:eq, :len, :min, :max, :in, :regexp+.
7
+ class StringField < FieldBase
8
+ PREFIX = 'string'
9
+
10
+ AUTHORIZED_OPTIONS = [:eq, :len, :min, :max, :in, :regexp]
11
+
12
+ EXCLUSIVE_OPTIONS = [
13
+ [ :eq, [:_all_] ],
14
+ [ :len, [:eq, :min, :max, :in] ],
15
+ [ :in, [:_all_] ],
16
+ ]
17
+
18
+ # Cannot have: min == max, use +:len+ instead
19
+ NOT_EQUALS_OPTIONS = [
20
+ [:min, :max],
21
+ ]
22
+
23
+ # Cannot have: min > max
24
+ NOT_SUP_OPTIONS = [
25
+ [:min, :max],
26
+ ]
27
+
28
+ private
29
+
30
+ def do_validate(ctx, name, value, errors)
31
+ return unless assert_class String, value, errors
32
+ assert_eq @opts[:eq], value, errors
33
+ assert_size_len @opts[:len], value, errors
34
+ assert_size_min @opts[:min], value, errors
35
+ assert_size_max @opts[:max], value, errors
36
+ assert_in @opts[:in], value, errors
37
+ assert_string_regexp @opts[:regexp], value, errors
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,13 @@
1
+ module Saphyr
2
+ module Fields
3
+ require_relative './fields/field_base'
4
+
5
+ require_relative './fields/array_field'
6
+ require_relative './fields/schema_field'
7
+
8
+ require_relative './fields/string_field'
9
+ require_relative './fields/integer_field'
10
+ require_relative './fields/float_field'
11
+ require_relative './fields/boolean_field'
12
+ end
13
+ end
@@ -0,0 +1,106 @@
1
+ module Saphyr
2
+ module Helpers
3
+ class Format
4
+ class << self
5
+ def errors_to_text errors
6
+ errors.each do |error|
7
+ type = error[:type]
8
+ puts "path: #{error[:path]}"
9
+ errs = error[:errors]
10
+ errs.each do |err|
11
+ puts " - type: #{err[:type]}"
12
+ puts " - data: #{err[:data].to_s}"
13
+ puts " - msg: #{make_msg err}"
14
+ end
15
+ puts ''
16
+ end
17
+ end
18
+
19
+ def make_msg(err)
20
+ type = err[:type]
21
+ data = err[:data]
22
+
23
+ # ------------------------------------
24
+ # BASE
25
+ # ------------------------------------
26
+ # eq
27
+ if type.end_with? 'eq'
28
+ return "Expecting value to be equals to: #{data[:eq]}, got: #{data[:_val]}"
29
+ end
30
+ # class
31
+ if type.end_with? 'type'
32
+ return "Expecting type '#{data[:type]}', got: #{data[:got]}"
33
+ end
34
+ # in
35
+ if type.end_with? 'in'
36
+ return "Expecting value to be in: #{data[:in].to_s}, got: #{data[:_val]}"
37
+ end
38
+
39
+ # ------------------------------------
40
+ # Numeric
41
+ # ------------------------------------
42
+ # gt
43
+ if type.end_with? 'gt'
44
+ return "Expecting value to be > #{data[:gt]}, got: #{data[:_val]}"
45
+ end
46
+ # gte
47
+ if type.end_with? 'gte'
48
+ return "Expecting value to be >= #{data[:gte]}, got: #{data[:_val]}"
49
+ end
50
+ # lt
51
+ if type.end_with? 'lt'
52
+ return "Expecting value to be < #{data[:lt]}, got: #{data[:_val]}"
53
+ end
54
+ # lte
55
+ if type.end_with? 'lte'
56
+ return "Expecting value to be <= #{data[:lte]}, got: #{data[:_val]}"
57
+ end
58
+
59
+ # ------------------------------------
60
+ # Size
61
+ # ------------------------------------
62
+ # len
63
+ if type.end_with? 'len'
64
+ return "Expecting size to be equals to: #{data[:len]}, got: #{data[:_val].size}"
65
+ end
66
+ # min
67
+ if type.end_with? 'min'
68
+ return "Expecting size to be >= #{data[:min]}, got: #{data[:_val].size}"
69
+ end
70
+ # max
71
+ if type.end_with? 'max'
72
+ return "Expecting size to be <= #{data[:max]}, got: #{data[:_val].size}"
73
+ end
74
+
75
+ # ------------------------------------
76
+ # String
77
+ # ------------------------------------
78
+ # regexp
79
+ if type.end_with? 'regexp'
80
+ return "Value failed to match regexp: #{data[:regexp].to_s}, got: #{data[:_val]}"
81
+ end
82
+
83
+ # ------------------------------------
84
+ # Strict mode
85
+ # ------------------------------------
86
+ if type.end_with? 'missing_in_data'
87
+ return 'Missing fields in data'
88
+ end
89
+
90
+ if type.end_with? 'missing_in_schema'
91
+ return 'Missing fields in schema'
92
+ end
93
+
94
+ # ------------------------------------
95
+ # Not Nullable
96
+ # ------------------------------------
97
+ if type == 'not-nullable'
98
+ return 'Not nullable'
99
+ end
100
+
101
+ 'unknown'
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,7 @@
1
+ module Saphyr
2
+ module Helpers
3
+
4
+ require_relative './helpers/format'
5
+
6
+ end
7
+ end
@@ -0,0 +1,73 @@
1
+ module Saphyr
2
+
3
+ # This class is used to encapsulate schema definition.
4
+ #
5
+ class Schema
6
+ attr_reader :strict, :root, :fields, :schemas, :conditionals, :casts
7
+
8
+ def initialize()
9
+ @strict, @root = true, :object # Default values
10
+ @fields, @schemas, @conditionals, @casts = {}, {}, [], {}
11
+ end
12
+
13
+ # ---------------------------------------------------- DSL
14
+ def strict(value)
15
+ @strict = value # TODO: Validate bollean
16
+ end
17
+
18
+ def root(value)
19
+ @root = value # TODO : Validate symbol -> :object or :array
20
+ end
21
+
22
+ def field(name, type, **opts)
23
+ # TODO : What if field 'name' already exists?
24
+ name_s = name.to_s # Convert Symbol to string
25
+ # Raise exceptions if field type is not found
26
+ @fields[name_s] = Saphyr.config.instanciate_field_type type, opts
27
+ end
28
+
29
+ def schema(name, &block)
30
+ schema = Saphyr::Schema.new
31
+ schema.instance_eval &block
32
+ # TODO : What if schema 'name' already exists?
33
+ @schemas[name] = schema
34
+ end
35
+
36
+ def conditional(cond, &block)
37
+ cond = cond.to_sym if cond.is_a? String
38
+ if not cond.is_a?(Proc) and not cond.is_a?(Symbol)
39
+ raise Saphyr::Error.new "Bad condition, must a Proc or Symbol"
40
+ end
41
+
42
+ schema = Saphyr::Schema.new
43
+ schema.instance_eval &block
44
+ @conditionals << [cond, schema]
45
+ end
46
+
47
+ def cast(field, method)
48
+ method = method.to_s if method.is_a? Symbol
49
+ if not method.is_a?(Proc) and not method.is_a?(String)
50
+ raise Saphyr::Error.new "Bad method, must a Proc or Symbol | String"
51
+ end
52
+ @casts.store field.to_s, method
53
+ end
54
+ # -------------------------------------------------- / DSL
55
+
56
+ # Find local schema definition
57
+ def find_schema(name)
58
+ @schemas[name]
59
+ end
60
+
61
+ def strict?
62
+ @strict
63
+ end
64
+
65
+ def root_array?
66
+ @root == :array
67
+ end
68
+
69
+ def root_object?
70
+ @root == :object
71
+ end
72
+ end
73
+ end