typero 0.3.4 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eaa8278b3b0dde68e1c8f2ff0ebed2715cc21caee65e4eace4d2dafd69b96bbc
4
- data.tar.gz: fddf1feee206bb971713ed9df6819cfef93abb457b1ed141ecf1d43bde74d462
3
+ metadata.gz: ba656a56b8d2e5d3d977a2973f6de881eefd3257397a4a4ebe908a7abaed70a7
4
+ data.tar.gz: 8374f340b6af235c274ceaa9aace33f0c462b5a203d667a199fa519c1f56f53e
5
5
  SHA512:
6
- metadata.gz: 0ad494feec708f0886a9396b8311433ea0eb2310ace9ba761c9e1ae8d7c19995bac61dd938beb6a201666f16c3a09b5788e1b0ba499fc9c742ebed878aa919ab
7
- data.tar.gz: 6db00aae4495818d03f5dabb09eb1f19d43bfcecbd05c0528b8dc136f818643731f0e4f7635aa4ffecbb8b7a59d90af30da94ca2a9114ed7f10369192ab149af
6
+ metadata.gz: ba14e29595a5d17c3c3d102f49d1234a82e7ea23a7062a4a61c6dc56770d9d4922f1be7ae01663c41382efb6c6c51ed3fb5dfd4c188315ec1a5bf3f43eb699fc
7
+ data.tar.gz: c2d8b322fac4048067b809bf7944c8ca27bb787d4b590a68eec62169a768a65ddbb089359da73f550a32b59a0405b95629ae4932bf7a3dfb9d5426e2e26d3e7d
data/.version CHANGED
@@ -1 +1 @@
1
- 0.3.4
1
+ 0.8.0
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequel::Plugins::TyperoAttributes
4
+ module ClassMethods
5
+ def typero
6
+ Typero.new self
7
+ end
8
+ end
9
+
10
+ module InstanceMethods
11
+ # calling typero! on any object will validate all fields
12
+ def typero! field_name=nil
13
+ return unless Typero.defined?(self.class)
14
+
15
+ typero = self.class.typero
16
+
17
+ typero.validate(self) do |name, err|
18
+ errors.add(name, err) unless (errors.on(name) || []).include?(err)
19
+ end
20
+
21
+ # this are rules unique to database, so we check them here
22
+ typero.rules.each do |field, rule|
23
+ self[field] ||= {} if rule[:type] == 'hash'
24
+
25
+ # check uniqe fields
26
+ if rule[:unique]
27
+ id = self[:id] || 0
28
+ value = self[field]
29
+
30
+ # we only check if field is changed
31
+ if value.present? && column_changed?(field) && self.class.xwhere('LOWER(%s)=LOWER(?) and id<>?' % field, value, id).first
32
+ error = rule[:unique].class == TrueClass ? %[Value '"#{value}"' for #{field} allready exists] : rule[:unique]
33
+ errors.add(field, error) unless (errors.on(field) || []).include?(error)
34
+ end
35
+ end
36
+
37
+ # check protected fields
38
+ if rule[:protected] && self[:id]
39
+ if column_changed?(field)
40
+ error = rule[:protected].class == TrueClass ? "value once defined can't be overwritten." : rule[:protected]
41
+ errors.add(field, error) unless (errors.on(field) || []).include?(error)
42
+ end
43
+ end
44
+ end
45
+
46
+ # check single field if single field given
47
+ if field_name
48
+ raise ArgumentError.new 'Field :%s not found in %s' % [field_name, self] unless self[field_name]
49
+ return unless errors.on(field_name)
50
+
51
+ errors.on(field_name).join(', ')
52
+ end
53
+
54
+ true
55
+ end
56
+
57
+ def validate
58
+ typero!
59
+ super
60
+ end
61
+ end
62
+
63
+ module DatasetMethods
64
+
65
+ end
66
+ end
67
+
68
+ Sequel::Model.plugin :typero_attributes
69
+
@@ -1,163 +1,12 @@
1
- # rules = Typero.new do
2
- # set :name, String, req: true
3
- # set :email, :email, req: true
4
- # set :skills, [:email], min: 2
5
- # end
6
- #
7
- # errors = rules.validate @object
8
- # rules.valid?
9
- # rules.validate(@object) { |errors| ... }
10
-
11
- class Typero
12
- attr_reader :rules
13
-
14
- VERSION = File.read File.expand_path '../.version', File.dirname(__FILE__)
15
-
16
- class << self
17
- # validate single value in type
18
- def validate value, type, opts={}
19
- field = type.to_s.tableize.singularize.to_sym
20
-
21
- # we need to have pointer to hash, so value can be changed (coerced) if needed
22
- h = { field => value }
23
-
24
- rule = new
25
- rule.set field, type, opts
26
-
27
- if error = rule.validate(h)[field]
28
- block_given? ? yield(error) : raise(TypeError.new(error))
29
- end
30
-
31
- h[field]
32
- end
33
-
34
- # Typero.set(:label, 'Foo bar') -> "foo-bar"
35
- def set type, value, opts={}
36
- klass = 'Typero::%sType' % type.to_s.gsub(/[^\w]/,'').classify
37
- check = klass.constantize.new value, opts
38
- check.set
39
- end
40
-
41
- def cache
42
- @@cache ||= {}
43
- end
44
- end
45
-
46
- ###
47
-
48
- # accepts dsl block to
49
- def initialize hash={}, &block
50
- @rules = {}
51
- hash.each { |k, v| set(k, v) }
52
- instance_exec &block if block
53
- end
54
-
55
- # convert
56
- # integer :age
57
- # set :age, type: :integer
58
- # email :email
59
- # set :email, [:emails]
60
- # email [:emails]
61
- def method_missing name, *args, &block
62
- field = args.shift
63
-
64
- if field.class == Array
65
- field = field.first
66
- name = [name]
67
- end
68
-
69
- set field, type=name, *args
70
- end
71
-
72
- # coerce opts values
73
- def parse_option opts
74
- opts[:type] ||= 'string'
75
- opts[:req] = opts.delete(:required) unless opts[:required].nil?
76
-
77
- if opts[:type].is_a?(Array)
78
- opts[:array_type] = opts[:type][0] if opts[:type][0]
79
- opts[:type] = 'array'
80
- end
81
-
82
- opts[:type] = opts[:type].to_s.downcase
83
-
84
- allowed_names = [:req, :uniq, :protected, :type, :min, :max, :array_type, :default, :downcase, :desc]
85
- opts.keys.each do |key|
86
- raise ArgumentError.new('%s is not allowed as typero option' % key) unless allowed_names.index(key)
87
- end
88
-
89
- opts
90
- end
91
-
92
- # used in dsl to define value
93
- def set field, type=String, opts={}
94
- klass = '::Typero::%sType' % type.to_s.gsub(/[^\w]/,'').classify
95
- klass.constantize
96
-
97
- opts = type.is_a?(Hash) ? type : opts.merge(type: type)
98
-
99
- @rules[field] = parse_option opts
100
- end
101
-
102
- def safe_type type
103
- type.to_s.gsub(/[^\w]/,'').classify
104
- end
105
-
106
- # adds error to array or prefixes with field name
107
- def add_error field, msg
108
- if @errors[field]
109
- @errors[field] += ', %s' % msg
110
- else
111
- field_name = field.to_s.sub(/_id$/,'').humanize
112
- @errors[field] = '%s %s' % [field_name, msg]
113
- end
114
- end
115
-
116
- # validates any instance object or object with hash variable interface
117
- # it also coarces values
118
- def validate instance
119
- @errors = {}
120
-
121
- @rules.each do |field, opts|
122
- # set value to default if value is blank and default given
123
- instance[field] = opts[:default] if opts[:default] && instance[field].blank?
124
-
125
- # get field value
126
- value = instance[field]
127
-
128
- if value.present?
129
- klass = 'Typero::%sType' % safe_type(opts[:type])
130
- check = klass.constantize.new value, opts
131
- check.value = check.default if check.value.nil?
132
-
133
- unless check.value.nil?
134
- begin
135
- check.set
136
- check.validate
137
- instance[field] = check.value
138
- rescue TypeError => e
139
- add_error field, e.message
140
- end
141
- end
142
- elsif opts[:req]
143
- msg = opts[:req].class == TrueClass ? 'is required' : opts[:req]
144
- add_error field, msg
145
- end
146
- end
147
-
148
- if @errors.keys.length > 0 && block_given?
149
- @errors.each { |k,v| yield(k, v) }
150
- end
151
-
152
- @errors
153
- end
154
-
155
- def valid? instance
156
- errors = validate instance
157
- errors.keys.length == 0
158
- end
1
+ # base libs
2
+ require_relative 'typero/typero'
3
+ require_relative 'typero/params'
4
+ require_relative 'typero/type/type'
5
+
6
+ # checker types
7
+ Dir['%s/typero/type/types/*.rb' % __dir__].each do |file|
8
+ require file
159
9
  end
160
10
 
161
- require_relative 'typero/type'
162
-
163
- Dir['%s/typero/type/*.rb' % File.dirname(__FILE__)].each { |file| require file }
11
+ # load Sequel adapter is Sequel is available
12
+ require_relative './adapters/sequel' if defined?(Sequel)
@@ -0,0 +1,84 @@
1
+ # Base class for schema validation
2
+
3
+ class Typero
4
+ class Params
5
+ ALLOWED = %i(name min max default allowed delimiter max_count req required type array meta desc description duplicates unique)
6
+
7
+ attr_reader :rules, :db_rules
8
+
9
+ def initialize &block
10
+ @db_rules = []
11
+ @rules = {}
12
+ instance_exec &block
13
+ end
14
+
15
+ private
16
+
17
+ # used in dsl to define schema field options
18
+ def set field, *args
19
+ raise "Field name not given (Typero)" unless field
20
+
21
+ if args.first.is_a?(Hash)
22
+ opts = args.first || {}
23
+ else
24
+ opts = args[1] || {}
25
+ opts[:type] ||= args[0]
26
+ end
27
+
28
+ opts[:type] ||= :string
29
+ opts[:required] = true unless opts[:required].is_a?(FalseClass) || opts[:req].is_a?(FalseClass)
30
+
31
+ field = field.to_s
32
+
33
+ # name? - opional name
34
+ if field.include?('?')
35
+ field = field.sub('?', '')
36
+ opts[:required] = false
37
+ end
38
+
39
+ # array that allows duplicates
40
+ if opts[:type].is_a?(Array)
41
+ opts[:type] = opts[:type].first
42
+ opts[:array] = true
43
+ end
44
+
45
+ # no duplicates array
46
+ if opts[:type].is_a?(Set)
47
+ opts[:type] = opts[:type].to_a.first
48
+ opts[:array] = true
49
+ end
50
+
51
+ opts[:type] ||= 'string'
52
+ opts[:type] = opts[:type].to_s.downcase
53
+
54
+ opts[:description] = opts.delete(:desc) unless opts[:desc].nil?
55
+
56
+ # chek alloed params, all optional should go in meta
57
+ result = opts.keys - ALLOWED
58
+ raise ArgumentError.new('Unallowed Type params found: %s, allowed: %s' % [result.join(', '), ALLOWED]) if result.length > 0
59
+
60
+ field = field.to_sym
61
+
62
+ db :add_index, field if opts.delete(:index)
63
+
64
+ klass = Typero::Type.load opts[:type]
65
+ @rules[field] = opts
66
+ end
67
+
68
+ # pass values for db_schema only
69
+ # db :timestamps
70
+ # db :add_index, :code -> t.add_index :code
71
+ def db *args
72
+ @db_rules.push args
73
+ end
74
+
75
+ # set :age, type: :integer -> integer :age
76
+ # email :email
77
+ #
78
+ # set :emails, Array[:email]
79
+ # email Array[:emails]
80
+ def method_missing field, *args, &block
81
+ set field, *args
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,82 @@
1
+ # Master class
2
+
3
+ class 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
+ }
14
+ }
15
+
16
+ OPTS = {}
17
+
18
+ attr_accessor :opts
19
+ attr_accessor :value
20
+
21
+ class << self
22
+ def load name
23
+ klass = 'Typero::%sType' % name.to_s.gsub(/[^\w]/,'').classify
24
+
25
+ if const_defined? klass
26
+ klass.constantize
27
+ else
28
+ raise ArgumentError, 'Typero type "%s" is not defined (%s)' % [name, klass]
29
+ end
30
+ end
31
+
32
+ def error locale, key, message
33
+ locale = locale.to_sym
34
+ ERRORS[locale] ||= {}
35
+ ERRORS[locale][key.to_sym] = message
36
+ end
37
+
38
+ def opts key, desc
39
+ OPTS[self] ||= {}
40
+ OPTS[self][key] = desc
41
+ end
42
+ end
43
+
44
+ ###
45
+
46
+ def initialize value, opts={}
47
+ @value = value
48
+ @opts = opts
49
+ end
50
+
51
+ # default validation for any type
52
+ def validate
53
+ true
54
+ end
55
+
56
+ def default
57
+ nil
58
+ end
59
+
60
+ private
61
+
62
+ # get error from option or the default one
63
+ def error_for name, *args
64
+ locale =
65
+ if defined?(Lux)
66
+ Lux.current.locale.to_s
67
+ elsif defined?(I18n)
68
+ I18n.locale
69
+ end
70
+
71
+ locale = :en if locale.to_s == ''
72
+ pointer = ERRORS[locale.to_sym] || ERRORS[:en]
73
+ error = @opts.dig(:meta, locale, name) || @opts.dig(:meta, name) || pointer[name]
74
+ error = error % args if args.first
75
+
76
+ raise 'Type error :%s not defined' % name unless error
77
+ raise TypeError.new(error)
78
+ end
79
+ end
80
+ end
81
+
82
+
@@ -6,5 +6,11 @@ class Typero::BooleanType < Typero::Type
6
6
  def set
7
7
  @value = [true, 1, '1', 'true', 'on'].include?(@value) ? true : false
8
8
  end
9
+
10
+ def db_field
11
+ opts = {}
12
+ opts[:default] = @opts[:default] || false
13
+ [:boolean, opts]
14
+ end
9
15
  end
10
16
 
@@ -0,0 +1,19 @@
1
+ # you should not use this filed for currency calculations
2
+
3
+ require_relative './float'
4
+
5
+ class Typero::CurrencyType < Typero::FloatType
6
+
7
+ def set
8
+ @value = @value.to_f.round(2)
9
+ end
10
+
11
+ def db_field
12
+ opts = {}
13
+ opts[:precision] = 8
14
+ opts[:scale] = 2
15
+ [:decimal, opts]
16
+ end
17
+
18
+ end
19
+
@@ -0,0 +1,10 @@
1
+ class Typero::DateType < Typero::Type
2
+ def set
3
+ @value = @value.to_s.downcase.gsub(/[^\d\-\.\s]/, '')
4
+ end
5
+
6
+ def db_field
7
+ [:date, {}]
8
+ end
9
+ end
10
+
@@ -0,0 +1,10 @@
1
+ class Typero::DatetimeType < Typero::Type
2
+ def set
3
+ @value = @value.to_s.downcase.gsub(/[^\d\-\.\s\:]/, '')
4
+ end
5
+
6
+ def db_field
7
+ [:datetime, {}]
8
+ end
9
+ end
10
+
@@ -0,0 +1,21 @@
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 = @value.downcase.gsub(/\s+/,'+')
7
+ end
8
+
9
+ def validate
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_field
15
+ opts = {}
16
+ opts[:limit] = @opts[:max] || 120
17
+ opts[:null] = false if @opts[:required]
18
+ [:string, opts]
19
+ end
20
+ end
21
+
@@ -0,0 +1,21 @@
1
+ class Typero::FloatType < Typero::Type
2
+ opts :min, 'Minimum value'
3
+ opts :max, 'Maximun value'
4
+
5
+ def set
6
+ @value = @value.to_f
7
+ end
8
+
9
+ def validate
10
+ error_for(:min_length_error, @opts[:min], @value) if @opts[:min] && value < @opts[:min]
11
+ error_for(:max_length_error, @opts[:max], @value) if @opts[:max] && value > @opts[:max]
12
+ end
13
+
14
+ def db_field
15
+ opts = {}
16
+ opts[:null] = false if @opts[:required]
17
+ [:float, opts]
18
+ end
19
+
20
+ end
21
+
@@ -0,0 +1,22 @@
1
+ class Typero::HashType < Typero::Type
2
+ error :en, :not_hash_type_error, 'value is not hash type'
3
+
4
+ def default
5
+ {}
6
+ end
7
+
8
+ def set
9
+ @value = @value.to_h
10
+ end
11
+
12
+ def validate
13
+ error_for(:not_hash_type_error) unless @value.is_a?(Hash)
14
+ end
15
+
16
+ def db_field
17
+ [:jsonb, {
18
+ null: false
19
+ }]
20
+ end
21
+ end
22
+
@@ -0,0 +1,28 @@
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
+ @value = 'https://%s' % @value unless @value.include?('://')
11
+ end
12
+
13
+ def validate
14
+ error_for(:image_not_starting_error) unless @value =~ /^https?:\/\/./
15
+
16
+ if opts[:strict]
17
+ ext = @value.split('.').last.downcase
18
+ error_for(:image_not_image_format) unless FORMATS.include?(ext)
19
+ end
20
+ end
21
+
22
+ def db_field
23
+ opts = {}
24
+ opts[:null] = false if @opts[:required]
25
+ [:string, opts]
26
+ end
27
+ end
28
+
@@ -0,0 +1,21 @@
1
+ class Typero::IntegerType < Typero::Type
2
+ opts :min, 'Minimum value'
3
+ opts :max, 'Maximun value'
4
+
5
+ def set
6
+ @value = @value.to_i
7
+ end
8
+
9
+ def validate
10
+ error_for(:min_value_error, @opts[:min], @value) if @opts[:min] && @value < @opts[:min]
11
+ error_for(:max_value_error, @opts[:max], @value) if @opts[:max] && @value > @opts[:max]
12
+ end
13
+
14
+ def db_field
15
+ opts = {}
16
+ opts[:null] = false if @opts[:required]
17
+ opts[:default] = @opts[:default]
18
+ [:integer, opts]
19
+ end
20
+ end
21
+
@@ -4,6 +4,12 @@ class Typero::LabelType < Typero::Type
4
4
  end
5
5
 
6
6
  def validate
7
- raise TypeError, "Label is having unallowed characters" unless @value =~ /^[\w\-]+$/
7
+ error_for(:unallowed_characters_error) unless @value =~ /^[\w\-]+$/
8
+ end
9
+
10
+ def db_field
11
+ opts = {}
12
+ opts[:limit] = 30
13
+ [:string, opts]
8
14
  end
9
15
  end
@@ -0,0 +1,42 @@
1
+ class Typero::OibType < Typero::Type
2
+ error :en, :not_an_oib_error, 'not in an OIB format'
3
+
4
+ def set
5
+ @value = check?(@value) ? @value.to_i : nil
6
+ end
7
+
8
+ def validate
9
+ error_for(:not_an_oib_error) unless check?(@value)
10
+ end
11
+
12
+ def db_field
13
+ opts = {}
14
+ opts[:null] = false if @opts[:required]
15
+ opts[:limit] = 11
16
+ [:string, opts]
17
+ end
18
+
19
+ private
20
+
21
+ # http://domagoj.eu/oib/
22
+ def check? oib
23
+ oib = oib.to_s
24
+
25
+ return false unless oib.match(/^[0-9]{11}$/)
26
+
27
+ control_sum = (0..9).inject(10) do |middle, position|
28
+ middle += oib.at(position).to_i
29
+ middle %= 10
30
+ middle = 10 if middle == 0
31
+ middle *= 2
32
+ middle %= 11
33
+ end
34
+
35
+ control_sum = 11 - control_sum
36
+ control_sum = 0 if control_sum == 10
37
+
38
+ return control_sum == oib.at(10).to_i
39
+ end
40
+
41
+ end
42
+
@@ -0,0 +1,29 @@
1
+ # for postgres - select st_asewkt(lon_lat) || st_astext(lon_lat)
2
+ # point = @object.class.xselect("ST_AsText(#{field}) as #{field}").where(id: @object.id).first[field.to_sym]
3
+
4
+ class Typero::PointType < Typero::Type
5
+ def set
6
+ if @value.present?
7
+ if @value.include?('/@')
8
+ point = @value.split('/@', 2).last.split(',')
9
+ @value = [point[0], point[1]].join(',')
10
+ end
11
+
12
+ if !@value.include?('POINT') && @value.include?(',')
13
+ point = @value.sub(/,\s*/, ' ')
14
+ @value = 'SRID=4326;POINT(%s)' % point
15
+ end
16
+ end
17
+ end
18
+
19
+ def validate
20
+ if @value && @value.include?(',') && !@value =~ /^SRID=4326;POINT\(/
21
+ error_for(:unallowed_characters_error)
22
+ end
23
+ end
24
+
25
+ def db_field
26
+ [:geography, {}]
27
+ end
28
+ end
29
+
@@ -0,0 +1,23 @@
1
+ class Typero::StringType < Typero::Type
2
+ opts :min, 'Minimun string length'
3
+ opts :max, 'Maximun string length'
4
+
5
+ def set
6
+ @value = @value.to_s unless @value.is_a?(String)
7
+ @value = @value.downcase if @opts[:downcase]
8
+ end
9
+
10
+ def validate
11
+ error_for(:min_length_error, @opts[:min], @value.length) if @opts[:min] && @value.length < @opts[:min]
12
+ error_for(:max_length_error, @opts[:max], @value.length) if @opts[:max] && @value.length > @opts[:max]
13
+ end
14
+
15
+ def db_field
16
+ opts = {}
17
+ opts[:limit] = @opts[:max] || 255
18
+ opts[:null] = false if @opts[:required]
19
+ opts[:default] = @opts[:default]
20
+ [:string, opts]
21
+ end
22
+ end
23
+
@@ -0,0 +1,12 @@
1
+ require_relative 'string'
2
+
3
+ class Typero::TextType < Typero::StringType
4
+ opts :min, 'Minimun string length'
5
+ opts :max, 'Maximun string length'
6
+
7
+ def db_field
8
+ opts = {}
9
+ opts[:null] = false if @opts[:required]
10
+ [:text, opts]
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ class Typero::UrlType < Typero::Type
2
+ error :en, :url_not_starting_error, 'URL is not starting with http'
3
+
4
+ def set
5
+ @value = 'http://%s' % @value unless @value.include?('://')
6
+ end
7
+
8
+ def validate
9
+ error_for(:url_not_starting_error) unless @value =~ /^https?:\/\/./
10
+ end
11
+
12
+ def db_field
13
+ opts = {}
14
+ opts[:null] = false if @opts[:required]
15
+ [:string, opts]
16
+ end
17
+ end
18
+
@@ -0,0 +1,177 @@
1
+ # rules = Typero.new do
2
+ # set :name, String, req: true
3
+ # set :email, :email, req: true
4
+ # set :skills, [:email], min: 2
5
+ # end
6
+ #
7
+ # or
8
+ #
9
+ # rules = Typero.new do
10
+ # string :name, req: true
11
+ # email :email, req: true
12
+ # email [:skills], min: 2
13
+ # end
14
+ #
15
+ # errors = rules.validate @object
16
+ # rules.valid?
17
+ # rules.validate(@object) { |errors| ... }
18
+
19
+ class Typero
20
+ VERSION = File.read File.expand_path "../../.version", File.dirname(__FILE__)
21
+
22
+ class << self
23
+ # check and coerce value
24
+ # Typero.set(:label, 'Foo bar') -> "foo-bar"
25
+ def set type, value, opts = {}, &block
26
+ check = Typero::Type.load(type).new value, opts
27
+ check.set
28
+ check.validate
29
+ check.value
30
+ rescue TypeError => error
31
+ if block
32
+ block.call error
33
+ false
34
+ else
35
+ raise error
36
+ end
37
+ end
38
+ end
39
+
40
+ ###
41
+
42
+ # accepts dsl block to
43
+ def initialize &block
44
+ raise "Params not defined" unless block_given?
45
+ @schema = Params.new &block
46
+ end
47
+
48
+ # validates any instance object with hash variable interface
49
+ # it also coarces values
50
+ def validate object
51
+ @object = object
52
+ @errors = {}
53
+
54
+ @schema.rules.each do |field, opts|
55
+ # set value to default if value is blank and default given
56
+ @object[field] = opts[:default] if opts[:default] && @object[field].blank?
57
+
58
+ # get field value
59
+ value = @object[field]
60
+
61
+ if opts[:array]
62
+ unless value.is_a?(Array)
63
+ opts[:delimiter] ||= /\s*,\s*/
64
+ value = value.to_s.split(opts[:delimiter])
65
+ end
66
+
67
+ value = value
68
+ .map { |el| check_filed_value field, el, opts }
69
+ .map { |el| el.to_s == '' ? nil : el }
70
+ .compact
71
+
72
+ value = Set.new(value).to_a unless opts[:duplicates]
73
+
74
+ opts[:max_count] ||= 100
75
+ 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]
76
+
77
+ check_required field, value.first, opts
78
+ else
79
+ value = check_filed_value field, value, opts
80
+ check_required field, value, opts
81
+ end
82
+
83
+ # if value is not list of allowed values, raise error
84
+ if opts[:allowed] && !opts[:values].include?(value)
85
+ add_error field, 'Value "%s" is not allowed' % value, opts
86
+ end
87
+
88
+ # present empty string values as nil
89
+ @object[field] = value.to_s.sub(/\s+/, '') == '' ? nil : value
90
+ end
91
+
92
+ if @errors.keys.length > 0 && block_given?
93
+ @errors.each { |k, v| yield(k, v) }
94
+ end
95
+
96
+ @errors
97
+ end
98
+
99
+ def valid? object
100
+ errors = validate object
101
+ errors.keys.length == 0
102
+ end
103
+
104
+ # returns field, db_type, db_opts
105
+ def db_schema
106
+ out = @schema.rules.inject([]) do |total, (field, opts)|
107
+ # get db filed schema
108
+ type, opts = Typero::Type.load(opts[:type]).new(nil, opts).db_field
109
+
110
+ # add array true to field it ont defined in schema
111
+ schema_opts = @schema.rules[field]
112
+ opts[:array] = true if schema_opts[:array]
113
+
114
+ total << [type, field, opts]
115
+ end
116
+
117
+ out += @schema.db_rules
118
+
119
+ out
120
+ end
121
+
122
+ # iterate trough all the ruels via block interface
123
+ # schema.rules do |field, opts|
124
+ # schema.rules(:url) do |field, opts|
125
+ def rules(filter = nil, &block)
126
+ return @schema.rules unless filter
127
+ out = @schema.rules
128
+ out = out.select { |k, v| v[:type].to_s == filter.to_s || v[:array_type].to_s == filter.to_s } if filter
129
+ return out unless block_given?
130
+
131
+ out.each { |k, v| yield k, v }
132
+ end
133
+
134
+ private
135
+
136
+ # adds error to array or prefixes with field name
137
+ def add_error field, msg, opts
138
+ if @errors[field]
139
+ @errors[field] += ", %s" % msg
140
+ else
141
+ if msg && msg[0, 1].downcase == msg[0, 1]
142
+ field_name = opts[:name] || field.to_s.sub(/_id$/, "").humanize
143
+ msg = "%s %s" % [field_name, msg]
144
+ end
145
+
146
+ @errors[field] = msg
147
+ end
148
+ end
149
+
150
+ def safe_type type
151
+ type.to_s.gsub(/[^\w]/, "").classify
152
+ end
153
+
154
+ def check_required field, value, opts
155
+ return if !opts[:required] || value
156
+ msg = opts[:required].class == TrueClass ? "is required" : opts[:required]
157
+ add_error field, msg, opts
158
+ end
159
+
160
+ def check_filed_value field, value, opts
161
+ return unless value
162
+
163
+ klass = "Typero::%sType" % safe_type(opts[:type])
164
+ check = klass.constantize.new value, opts
165
+ check.value = check.default if check.value.nil?
166
+
167
+ unless check.value.nil?
168
+ begin
169
+ check.set
170
+ check.validate
171
+ check.value
172
+ rescue TypeError => e
173
+ add_error field, e.message, opts
174
+ end
175
+ end
176
+ end
177
+ end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typero
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dino Reic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-06 00:00:00.000000000 Z
11
+ date: 2020-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fast_blank
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1'
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1'
26
+ version: '0'
27
27
  description: Simple and fast ruby type system. Enforce types as Array, Email, Boolean
28
28
  for ruby class instances
29
29
  email: reic.dino@gmail.com
@@ -32,18 +32,26 @@ extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
34
  - "./.version"
35
+ - "./lib/adapters/sequel.rb"
35
36
  - "./lib/typero.rb"
36
- - "./lib/typero/type.rb"
37
- - "./lib/typero/type/array.rb"
38
- - "./lib/typero/type/boolean.rb"
39
- - "./lib/typero/type/email.rb"
40
- - "./lib/typero/type/float.rb"
41
- - "./lib/typero/type/hash.rb"
42
- - "./lib/typero/type/integer.rb"
43
- - "./lib/typero/type/label.rb"
44
- - "./lib/typero/type/oib.rb"
45
- - "./lib/typero/type/string.rb"
46
- - "./lib/typero/type/url.rb"
37
+ - "./lib/typero/params.rb"
38
+ - "./lib/typero/type/type.rb"
39
+ - "./lib/typero/type/types/boolean.rb"
40
+ - "./lib/typero/type/types/currency.rb"
41
+ - "./lib/typero/type/types/date.rb"
42
+ - "./lib/typero/type/types/datetime.rb"
43
+ - "./lib/typero/type/types/email.rb"
44
+ - "./lib/typero/type/types/float.rb"
45
+ - "./lib/typero/type/types/hash.rb"
46
+ - "./lib/typero/type/types/image.rb"
47
+ - "./lib/typero/type/types/integer.rb"
48
+ - "./lib/typero/type/types/label.rb"
49
+ - "./lib/typero/type/types/oib.rb"
50
+ - "./lib/typero/type/types/point.rb"
51
+ - "./lib/typero/type/types/string.rb"
52
+ - "./lib/typero/type/types/text.rb"
53
+ - "./lib/typero/type/types/url.rb"
54
+ - "./lib/typero/typero.rb"
47
55
  homepage: https://github.com/dux/typero
48
56
  licenses:
49
57
  - MIT
@@ -63,8 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
71
  - !ruby/object:Gem::Version
64
72
  version: '0'
65
73
  requirements: []
66
- rubyforge_project:
67
- rubygems_version: 2.7.5
74
+ rubygems_version: 3.0.6
68
75
  signing_key:
69
76
  specification_version: 4
70
77
  summary: Ruby type system
@@ -1,28 +0,0 @@
1
- class Typero::Type
2
- attr_accessor :opts
3
- attr_accessor :value
4
-
5
- def initialize(value, opts={})
6
- @value = value
7
- @opts = opts
8
- end
9
-
10
- # default validation for any type
11
- def validate(what)
12
- true
13
- end
14
-
15
- def get
16
- @value
17
- end
18
-
19
- def set
20
- @value
21
- end
22
-
23
- def default
24
- nil
25
- end
26
- end
27
-
28
-
@@ -1,32 +0,0 @@
1
- class Typero::ArrayType < Typero::Type
2
- def default
3
- []
4
- end
5
-
6
- def set
7
- unless @value.class.to_s.index('Array')
8
- @value = @value.to_s.sub(/^\{/,'').sub(/\}$/,'').split(/\s*,\s*/)
9
- end
10
-
11
- @value.uniq!
12
- @value.compact!
13
-
14
- if type = @opts[:array_type]
15
- @value.map! { |el|
16
- Typero.validate(el, type) { |msg|
17
- raise TypeError.new "'%s' %s (value in list)" % [el, msg]
18
- }
19
- }
20
- end
21
-
22
- # this converts Sequel::Postgres::PGArray to Array and fixes many problems
23
- @value = @value.to_a if @value.class != Array
24
- end
25
-
26
- def validate
27
- raise TypeError, 'Min array lenght is %s elements' % @opts[:min] if @opts[:min] && @value.length < @opts[:min]
28
- raise TypeError, 'Max array lenght is %s elements' % @opts[:max] if @opts[:max] && @value.length > @opts[:max]
29
- true
30
- end
31
- end
32
-
@@ -1,14 +0,0 @@
1
- class Typero::EmailType < Typero::Type
2
-
3
- def set
4
- @value = @value.downcase.gsub(/\s+/,'+')
5
- end
6
-
7
- def validate
8
- raise TypeError, 'is not having at least 8 characters' unless @value.to_s.length > 7
9
- raise TypeError, 'is missing @' unless @value.include?('@')
10
- # raise TypeError, "[#{@value}] is in wrong format" unless @value =~ /^[\+\w\-\.]+\@[\w\-\.]+$/i
11
- end
12
-
13
- end
14
-
@@ -1,13 +0,0 @@
1
- class Typero::FloatType < Typero::Type
2
-
3
- def set
4
- @value = @value.to_f
5
- end
6
-
7
- def validate
8
- raise TypeError, "min lenght is #{@opts[:min]}" if @opts[:min] && value < @opts[:min]
9
- raise TypeError, "max lenght is #{@opts[:max]}" if @opts[:max] && value > @opts[:max]
10
- end
11
-
12
- end
13
-
@@ -1,10 +0,0 @@
1
- class Typero::HashType < Typero::Type
2
- def default
3
- {}
4
- end
5
-
6
- def validate
7
- raise TypeError, 'Value is not hash type' unless @value.is_a?(Hash)
8
- end
9
- end
10
-
@@ -1,11 +0,0 @@
1
- class Typero::IntegerType < Typero::Type
2
- def set
3
- @value = @value.to_i
4
- end
5
-
6
- def validate
7
- raise TypeError, 'min is %s, got %s' % [@opts[:min], @value] if @opts[:min] && @value < @opts[:min]
8
- raise TypeError, 'max is %s, got %s' % [@opts[:max], @value] if @opts[:max] && @value > @opts[:max]
9
- end
10
- end
11
-
@@ -1,29 +0,0 @@
1
- class Typero::OibType < Typero::Type
2
- def check? oib
3
- oib = oib.to_s
4
-
5
- return false if (oib =~ /\d{11}/) != 0
6
- return false if oib.length != 11
7
-
8
- a = 10
9
- (0..9).each do |i|
10
- a = (a + oib[i,1].to_i) % 10
11
- a = 10 if a == 0
12
- a = (a * 2) % 11
13
- end
14
-
15
- kontrolna = 11 - a
16
- kontrolna = 0 if kontrolna == 10
17
-
18
- kontrolna == oib[10,1].to_i
19
- end
20
-
21
- def set
22
- @value = check?(@value) ? @value.to_i : nil
23
- end
24
-
25
- def validate
26
- raise TypeError.new('Not an OIB') unless check?(@value)
27
- end
28
- end
29
-
@@ -1,12 +0,0 @@
1
- class Typero::StringType < Typero::Type
2
- def set
3
- @value = @value.to_s unless @value.is_a?(String)
4
- @value = @value.downcase if @opts[:downcase]
5
- end
6
-
7
- def validate
8
- raise TypeError, 'min lenght is %s, you have %s' % [@opts[:min], @value.length] if @opts[:min] && @value.length < @opts[:min]
9
- raise TypeError, 'max lenght is %s, you have %s' % [@opts[:max], @value.length] if @opts[:max] && @value.length > @opts[:max]
10
- end
11
- end
12
-
@@ -1,10 +0,0 @@
1
- class Typero::UrlType < Typero::Type
2
- def set
3
- @value = 'http://%s' % @value unless @value.include?('://')
4
- end
5
-
6
- def validate
7
- raise TypeError, 'URL is not starting with http' unless @value =~ /^https?:\/\/./
8
- end
9
- end
10
-