typero 0.4.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,132 @@
1
+ module Typero
2
+ class Schema
3
+ SCHEMAS = {}
4
+
5
+ # accepts dsl block to
6
+ def initialize &block
7
+ raise "Params not defined" unless block_given?
8
+ @schema = Params.new &block
9
+ end
10
+
11
+ # validates any instance object with hash variable interface
12
+ # it also coarces values
13
+ def validate object
14
+ @object = object
15
+ @errors = {}
16
+
17
+ @schema.rules.each do |field, opts|
18
+ # set value to default if value is blank and default given
19
+ @object[field] = opts[:default] if opts[:default] && @object[field].blank?
20
+
21
+ # get field value
22
+ value = @object[field]
23
+
24
+ if opts[:array]
25
+ unless value.is_a?(Array)
26
+ opts[:delimiter] ||= /\s*,\s*/
27
+ value = value.to_s.split(opts[:delimiter])
28
+ end
29
+
30
+ value = value
31
+ .map { |el| check_filed_value field, el, opts }
32
+ .map { |el| el.to_s == '' ? nil : el }
33
+ .compact
34
+
35
+ value = Set.new(value).to_a unless opts[:duplicates]
36
+
37
+ opts[:max_count] ||= 100
38
+ add_error(field, 'Max number of array elements is %d, you have %d' % [opts[:max_count], value.length], opts) if value.length > opts[:max_count]
39
+
40
+ add_required_error field, value.first, opts
41
+ else
42
+ value = check_filed_value field, value, opts
43
+ add_required_error field, value, opts
44
+ end
45
+
46
+ # if value is not list of allowed values, raise error
47
+ if opts[:allowed] && !opts[:values].include?(value)
48
+ add_error field, 'Value "%s" is not allowed' % value, opts
49
+ end
50
+
51
+ # present empty string values as nil
52
+ @object[field] = value.to_s.sub(/\s+/, '') == '' ? nil : value
53
+ end
54
+
55
+ if @errors.keys.length > 0 && block_given?
56
+ @errors.each { |k, v| yield(k, v) }
57
+ end
58
+
59
+ @errors
60
+ end
61
+
62
+ def valid? object
63
+ errors = validate object
64
+ errors.keys.length == 0
65
+ end
66
+
67
+ # returns field, db_type, db_opts
68
+ def db_schema
69
+ out = @schema.rules.inject([]) do |total, (field, opts)|
70
+ # get db filed schema
71
+ type, opts = Typero::Type.load(opts[:type]).new(nil, opts).db_field
72
+
73
+ # add array true to field it ont defined in schema
74
+ schema_opts = @schema.rules[field]
75
+ opts[:array] = true if schema_opts[:array]
76
+
77
+ total << [type, field, opts]
78
+ end
79
+
80
+ out += @schema.db_rules
81
+
82
+ out
83
+ end
84
+
85
+ # iterate trough all the ruels via block interface
86
+ # schema.rules do |field, opts|
87
+ # schema.rules(:url) do |field, opts|
88
+ def rules filter = nil, &block
89
+ return @schema.rules unless filter
90
+ out = @schema.rules
91
+ out = out.select { |k, v| v[:type].to_s == filter.to_s || v[:array_type].to_s == filter.to_s } if filter
92
+ return out unless block_given?
93
+
94
+ out.each { |k, v| yield k, v }
95
+ end
96
+ alias :to_h :rules
97
+
98
+ private
99
+
100
+ # adds error to array or prefixes with field name
101
+ def add_error field, msg, opts
102
+ if @errors[field]
103
+ @errors[field] += ", %s" % msg
104
+ else
105
+ if msg && msg[0, 1].downcase == msg[0, 1]
106
+ field_name = opts[:name] || field.to_s.sub(/_id$/, "").capitalize
107
+ msg = "%s %s" % [field_name, msg]
108
+ end
109
+
110
+ @errors[field] = msg
111
+ end
112
+ end
113
+
114
+ def safe_type type
115
+ type.to_s.gsub(/[^\w]/, "").classify
116
+ end
117
+
118
+ def add_required_error field, value, opts
119
+ return unless opts[:required] && value.nil?
120
+ msg = opts[:required].class == TrueClass ? "is required" : opts[:required]
121
+ add_error field, msg, opts
122
+ end
123
+
124
+ def check_filed_value field, value, opts
125
+ klass = "Typero::%sType" % safe_type(opts[:type])
126
+ check = klass.constantize.new value, opts
127
+ check.get
128
+ rescue TypeError => e
129
+ add_error field, e.message, opts
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,110 @@
1
+ # Master class
2
+
3
+ module Typero
4
+ class Type
5
+ ERRORS = {
6
+ en: {
7
+ # errors shared between various types
8
+ min_length_error: 'min lenght is %s, you have %s',
9
+ max_length_error: 'max lenght is %s, you have %s',
10
+ min_value_error: 'min is %s, got %s',
11
+ max_value_error: 'max is %s, got %s',
12
+ unallowed_characters_error: 'is having unallowed characters',
13
+ not_in_range: 'Value in not in allowed range (%s)'
14
+ }
15
+ }
16
+
17
+ # default shared allowed opts keys
18
+ OPTS_KEYS = [:type, :required, :array, :max_count, :default, :name, :meta, :model]
19
+ OPTS = {}
20
+
21
+ attr_reader :opts
22
+
23
+ class << self
24
+ def load name
25
+ klass = 'Typero::%sType' % name.to_s.gsub(/[^\w]/,'').classify
26
+
27
+ if const_defined? klass
28
+ klass.constantize
29
+ else
30
+ raise ArgumentError, 'Typero type "%s" is not defined (%s)' % [name, klass]
31
+ end
32
+ end
33
+
34
+ def error locale, key, message
35
+ locale = locale.to_sym
36
+ ERRORS[locale] ||= {}
37
+ ERRORS[locale][key.to_sym] = message
38
+ end
39
+
40
+ def opts key, desc
41
+ OPTS_KEYS.push key unless OPTS_KEYS.include?(key)
42
+
43
+ OPTS[self] ||= {}
44
+ OPTS[self][key] = desc
45
+ end
46
+ end
47
+
48
+ ###
49
+
50
+ def initialize value, opts={}, &block
51
+ value = value.sub(/^\s+/, '').sub(/\s+$/, '') if value.is_a?(String)
52
+
53
+ @value = value
54
+ @opts = opts
55
+ @block = block
56
+ end
57
+
58
+ def value &block
59
+ if block_given?
60
+ @value = block.call @value
61
+ else
62
+ @value
63
+ end
64
+ end
65
+
66
+ def get
67
+ if value.nil?
68
+ opts[:default].nil? ? default : opts[:default]
69
+ else
70
+ set
71
+ error_for(:not_in_range, opts[:values].join(', ')) if opts[:values] && !opts[:values].include?(@value)
72
+ value
73
+ end
74
+ end
75
+
76
+ def default
77
+ nil
78
+ end
79
+
80
+ def db_field
81
+ out = db_schema
82
+ out[1] ||= {}
83
+ out[1][:default] ||= opts[:default] unless opts[:default].nil?
84
+ out[1][:null] = false if !opts[:array] && opts[:required]
85
+ out
86
+ end
87
+
88
+ private
89
+
90
+ # get error from option or the default one
91
+ def error_for name, *args
92
+ locale =
93
+ if defined?(Lux)
94
+ Lux.current.locale.to_s
95
+ elsif defined?(I18n)
96
+ I18n.locale
97
+ end
98
+
99
+ locale = :en if locale.to_s == ''
100
+ pointer = ERRORS[locale.to_sym] || ERRORS[:en]
101
+ error = @opts.dig(:meta, locale, name) || @opts.dig(:meta, name) || pointer[name]
102
+ error = error % args if args.first
103
+
104
+ raise 'Type error :%s not defined' % name unless error
105
+ raise TypeError.new(error)
106
+ end
107
+ end
108
+ end
109
+
110
+
@@ -0,0 +1,26 @@
1
+ class Typero::BooleanType < Typero::Type
2
+ error :en, :unsupported_boolean, 'Unsupported boolean param value: %s'
3
+
4
+ def set
5
+ value do |_|
6
+ bool = _.to_s
7
+
8
+ if value == ''
9
+ false
10
+ elsif %w(true 1 on).include?(bool)
11
+ true
12
+ elsif %w(false 0 off).include?(bool)
13
+ false
14
+ else
15
+ error_for :unsupported_boolean, bool
16
+ end
17
+ end
18
+ end
19
+
20
+ def db_schema
21
+ [:boolean, {
22
+ default: opts[:default] || false
23
+ }]
24
+ end
25
+ end
26
+
@@ -0,0 +1,21 @@
1
+ # you should not use this filed for currency calculations
2
+ # use integer and covert in code
3
+ # Example: use cents and divide by 100 for $
4
+
5
+ require_relative './float_type'
6
+
7
+ class Typero::CurrencyType < Typero::FloatType
8
+
9
+ def set
10
+ value { |data| data.to_f.round(2) }
11
+ end
12
+
13
+ def db_schema
14
+ [:decimal, {
15
+ precision: 8,
16
+ scale: 2
17
+ }]
18
+ end
19
+
20
+ end
21
+
@@ -0,0 +1,32 @@
1
+ class Typero::DateType < Typero::Type
2
+ error :en, :min_date, 'Minimal allowed date is %s'
3
+ error :en, :max_date, 'Maximal allowed date is %s'
4
+
5
+ def set
6
+ value { |data| DateTime.parse(data) }
7
+ value { |data| DateTime.new(data.year, data.month, data.day) }
8
+
9
+ check_date_min_max
10
+ end
11
+
12
+ def db_schema
13
+ [:date, {}]
14
+ end
15
+
16
+ private
17
+
18
+ def check_date_min_max
19
+ if min = opts[:min]
20
+ min = DateTime.parse(min)
21
+ error_for(:min_date, min) % min if min > value
22
+ end
23
+
24
+ if max = opts[:max]
25
+ max = DateTime.parse(max)
26
+ error_for(:max_date, max) % max if value > max
27
+ end
28
+
29
+ value
30
+ end
31
+ end
32
+
@@ -0,0 +1,14 @@
1
+ require_relative 'date_type'
2
+
3
+ class Typero::DatetimeType < Typero::DateType
4
+ def set
5
+ value { |data| DateTime.parse(data) }
6
+
7
+ check_date_min_max
8
+ end
9
+
10
+ def db_schema
11
+ [:datetime]
12
+ end
13
+ end
14
+
@@ -0,0 +1,20 @@
1
+ class Typero::EmailType < Typero::Type
2
+ error :en, :not_8_chars_error, 'is not having at least 8 characters'
3
+ error :en, :missing_monkey_error, 'is missing @'
4
+
5
+ def set
6
+ value do |email|
7
+ email.downcase.gsub(/\s+/,'+')
8
+ end
9
+
10
+ error_for(:not_8_chars_error) unless value.to_s.length > 7
11
+ error_for(:missing_monkey_error) unless value.include?('@')
12
+ end
13
+
14
+ def db_schema
15
+ [:string, {
16
+ limit: @opts[:max] || 120
17
+ }]
18
+ end
19
+ end
20
+
@@ -0,0 +1,24 @@
1
+ class Typero::FloatType < Typero::Type
2
+ opts :min, 'Minimum value'
3
+ opts :max, 'Maximun value'
4
+ opts :round, 'Round to (decimal spaces)'
5
+
6
+ def set
7
+ @value =
8
+ if opts[:round]
9
+ value.to_f.round(opts[:round])
10
+ else
11
+ value.to_f
12
+ end
13
+
14
+ error_for(:min_length_error, opts[:min], value) if opts[:min] && value < opts[:min]
15
+ error_for(:max_length_error, opts[:max], value) if opts[:max] && value > opts[:max]
16
+ end
17
+
18
+ def db_schema
19
+ opts = {}
20
+ opts[:null] = false if opts[:required]
21
+ [:float, opts]
22
+ end
23
+ end
24
+
@@ -0,0 +1,25 @@
1
+ class Typero::HashType < Typero::Type
2
+ error :en, :not_hash_type_error, 'value is not hash type'
3
+
4
+ def set
5
+ error_for(:not_hash_type_error) unless value.is_a?(Hash)
6
+
7
+ if opts[:allow]
8
+ for key in value.keys
9
+ value.delete(key) unless opts[:allow].include?(key)
10
+ end
11
+ end
12
+ end
13
+
14
+ def default
15
+ {}
16
+ end
17
+
18
+ def db_schema
19
+ [:jsonb, {
20
+ null: false,
21
+ default: '{}'
22
+ }]
23
+ end
24
+ end
25
+
@@ -0,0 +1,22 @@
1
+ class Typero::ImageType < Typero::Type
2
+ FORMATS = %w[jpg jpeg gif png svg webp]
3
+
4
+ error :en, :image_not_starting_error, 'URL is not starting with http'
5
+ error :en, :image_not_image_format, 'URL is not ending with %s' % FORMATS.join(', ')
6
+
7
+ opts :strict, 'Force image to have known extension (%s)' % FORMATS.join(', ')
8
+
9
+ def set
10
+ error_for(:image_not_starting_error) unless value =~ /^https?:\/\/./
11
+
12
+ if opts[:strict]
13
+ ext = value.split('.').last.downcase
14
+ error_for(:image_not_image_format) unless FORMATS.include?(ext)
15
+ end
16
+ end
17
+
18
+ def db_schema
19
+ [:string]
20
+ end
21
+ end
22
+
@@ -0,0 +1,16 @@
1
+ class Typero::IntegerType < Typero::Type
2
+ opts :min, 'Minimum value'
3
+ opts :max, 'Maximun value'
4
+
5
+ def set
6
+ value(&:to_i)
7
+
8
+ error_for(:min_value_error, opts[:min], value) if opts[:min] && value < opts[:min]
9
+ error_for(:max_value_error, opts[:max], value) if opts[:max] && value > opts[:max]
10
+ end
11
+
12
+ def db_schema
13
+ [:integer, {}]
14
+ end
15
+ end
16
+