typero 0.4.0 → 0.9.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.
@@ -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
+