typero 0.3.4 → 0.8.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.
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
-