typero 0.8.0 → 0.9.4

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.version +1 -1
  3. data/lib/adapters/sequel.rb +10 -27
  4. data/lib/typero.rb +3 -0
  5. data/lib/typero/params.rb +57 -12
  6. data/lib/typero/schema.rb +165 -0
  7. data/lib/typero/type/type.rb +68 -10
  8. data/lib/typero/type/types/boolean_type.rb +26 -0
  9. data/lib/typero/type/types/currency_type.rb +21 -0
  10. data/lib/typero/type/types/date_type.rb +38 -0
  11. data/lib/typero/type/types/datetime_type.rb +19 -0
  12. data/lib/typero/type/types/email_type.rb +20 -0
  13. data/lib/typero/type/types/float_type.rb +24 -0
  14. data/lib/typero/type/types/hash_type.rb +31 -0
  15. data/lib/typero/type/types/{image.rb → image_type.rb} +4 -10
  16. data/lib/typero/type/types/integer_type.rb +16 -0
  17. data/lib/typero/type/types/label_type.rb +20 -0
  18. data/lib/typero/type/types/locale_type.rb +13 -0
  19. data/lib/typero/type/types/model_type.rb +23 -0
  20. data/lib/typero/type/types/{oib.rb → oib_type.rb} +7 -10
  21. data/lib/typero/type/types/point_type.rb +25 -0
  22. data/lib/typero/type/types/string_type.rb +23 -0
  23. data/lib/typero/type/types/{text.rb → text_type.rb} +3 -5
  24. data/lib/typero/type/types/time_type.rb +5 -0
  25. data/lib/typero/type/types/timezone_type.rb +15 -0
  26. data/lib/typero/type/types/url_type.rb +13 -0
  27. data/lib/typero/typero.rb +72 -141
  28. metadata +26 -21
  29. data/lib/typero/type/types/boolean.rb +0 -16
  30. data/lib/typero/type/types/currency.rb +0 -19
  31. data/lib/typero/type/types/date.rb +0 -10
  32. data/lib/typero/type/types/datetime.rb +0 -10
  33. data/lib/typero/type/types/email.rb +0 -21
  34. data/lib/typero/type/types/float.rb +0 -21
  35. data/lib/typero/type/types/hash.rb +0 -22
  36. data/lib/typero/type/types/integer.rb +0 -21
  37. data/lib/typero/type/types/label.rb +0 -15
  38. data/lib/typero/type/types/point.rb +0 -29
  39. data/lib/typero/type/types/string.rb +0 -23
  40. data/lib/typero/type/types/url.rb +0 -18
@@ -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,38 @@
1
+ class Typero::DateType < Typero::Type
2
+ opts :min, 'Smallest date-time allowed'
3
+ opts :max, 'Maximal date-time allowed'
4
+
5
+ error :en, :min_date, 'Minimal allowed date is %s'
6
+ error :en, :max_date, 'Maximal allowed date is %s'
7
+
8
+ def set
9
+ unless [Date].include?(value.class)
10
+ value { |data| DateTime.parse(data) }
11
+ end
12
+
13
+ value { |data| DateTime.new(data.year, data.month, data.day) }
14
+
15
+ check_date_min_max
16
+ end
17
+
18
+ def db_schema
19
+ [:date, {}]
20
+ end
21
+
22
+ private
23
+
24
+ def check_date_min_max
25
+ if min = opts[:min]
26
+ min = DateTime.parse(min)
27
+ error_for(:min_date, min) % min if min > value
28
+ end
29
+
30
+ if max = opts[:max]
31
+ max = DateTime.parse(max)
32
+ error_for(:max_date, max) % max if value > max
33
+ end
34
+
35
+ value
36
+ end
37
+ end
38
+
@@ -0,0 +1,19 @@
1
+ require_relative 'date_type'
2
+
3
+ class Typero::DatetimeType < Typero::DateType
4
+ opts :min, 'Smallest date allowed'
5
+ opts :max, 'Maximal date allowed'
6
+
7
+ def set
8
+ unless [Time, DateTime].include?(value.class)
9
+ value { |data| DateTime.parse(data) }
10
+ end
11
+
12
+ check_date_min_max
13
+ end
14
+
15
+ def db_schema
16
+ [:datetime]
17
+ end
18
+ end
19
+
@@ -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,31 @@
1
+ class Typero::HashType < Typero::Type
2
+ error :en, :not_hash_type_error, 'value is not hash type'
3
+
4
+ def set
5
+ if value.is_a?(String) && value[0,1] == '{'
6
+ @value = JSON.load(value)
7
+ end
8
+
9
+ @value ||= {}
10
+
11
+ error_for(:not_hash_type_error) unless @value.respond_to?(:keys) && @value.respond_to?(:values)
12
+
13
+ if opts[:allow]
14
+ for key in @value.keys
15
+ @value.delete(key) unless opts[:allow].include?(key)
16
+ end
17
+ end
18
+ end
19
+
20
+ def default
21
+ {}
22
+ end
23
+
24
+ def db_schema
25
+ [:jsonb, {
26
+ null: false,
27
+ default: '{}'
28
+ }]
29
+ end
30
+ end
31
+
@@ -7,22 +7,16 @@ class Typero::ImageType < Typero::Type
7
7
  opts :strict, 'Force image to have known extension (%s)' % FORMATS.join(', ')
8
8
 
9
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?:\/\/./
10
+ error_for(:image_not_starting_error) unless value =~ /^https?:\/\/./
15
11
 
16
12
  if opts[:strict]
17
- ext = @value.split('.').last.downcase
13
+ ext = value.split('.').last.downcase
18
14
  error_for(:image_not_image_format) unless FORMATS.include?(ext)
19
15
  end
20
16
  end
21
17
 
22
- def db_field
23
- opts = {}
24
- opts[:null] = false if @opts[:required]
25
- [:string, opts]
18
+ def db_schema
19
+ [:string]
26
20
  end
27
21
  end
28
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
+
@@ -0,0 +1,20 @@
1
+ class Typero::LabelType < Typero::Type
2
+ def set
3
+ value do |data|
4
+ data
5
+ .to_s
6
+ .gsub(/\s+/,'-')
7
+ .gsub(/[^\w\-]/,'')
8
+ .gsub(/\-+/, '-')[0,30]
9
+ .downcase
10
+ end
11
+
12
+ error_for(:unallowed_characters_error) unless value =~ /^[\w\-]+$/
13
+ end
14
+
15
+ def db_schema
16
+ [:string, {
17
+ limit: 30
18
+ }]
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ class Typero::LocaleType < Typero::Type
2
+ error :en, :locale_bad_format, 'Locale "%s" is in bad format (should be xx or xx-xx)'
3
+
4
+ def set
5
+ error_for(:locale_bad_format, value) unless value =~ /^[\w\-]{2,5}$/
6
+ end
7
+
8
+ def db_schema
9
+ [:string, { limit: 5 }]
10
+ end
11
+ end
12
+
13
+
@@ -0,0 +1,23 @@
1
+ class Typero::ModelType < Typero::Type
2
+ def set
3
+ value(&:to_h)
4
+
5
+ errors = {}
6
+
7
+ schema = opts[:model].is_a?(Typero::Schema) ? opts[:model] : Typero.schema(opts[:model])
8
+
9
+ # by default models in schems are strict true (remove undefined keys)
10
+ schema.validate value, strict: true do |field, error|
11
+ errors[field] = error
12
+ end
13
+
14
+ raise TypeError.new errors.to_json if errors.keys.first
15
+ end
16
+
17
+ def db_schema
18
+ [:jsonb, {
19
+ null: false
20
+ }]
21
+ end
22
+ end
23
+
@@ -2,18 +2,15 @@ class Typero::OibType < Typero::Type
2
2
  error :en, :not_an_oib_error, 'not in an OIB format'
3
3
 
4
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)
5
+ value do |data|
6
+ check?(data) ? data.to_i : error_for(:not_an_oib_error)
7
+ end
10
8
  end
11
9
 
12
- def db_field
13
- opts = {}
14
- opts[:null] = false if @opts[:required]
15
- opts[:limit] = 11
16
- [:string, opts]
10
+ def db_schema
11
+ [:string, {
12
+ limit: 11
13
+ }]
17
14
  end
18
15
 
19
16
  private
@@ -0,0 +1,25 @@
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.include?('/@')
7
+ point = value.split('/@', 2).last.split(',')
8
+ value { [point[0], point[1]].join(',') }
9
+ end
10
+
11
+ if !value.include?('POINT') && value.include?(',')
12
+ point = value.sub(/,\s*/, ' ')
13
+ value { 'SRID=4326;POINT(%s)' % point }
14
+ end
15
+
16
+ if value && value.include?(',') && !value =~ /^SRID=4326;POINT\(/
17
+ error_for(:unallowed_characters_error)
18
+ end
19
+ end
20
+
21
+ def db_schema
22
+ [:geography, {}]
23
+ end
24
+ end
25
+
@@ -0,0 +1,23 @@
1
+ class Typero::StringType < Typero::Type
2
+ opts :min, 'Minimun string length'
3
+ opts :max, 'Maximun string length'
4
+ opts :downcase, 'is the string in downcase?'
5
+
6
+ def set
7
+ value(&:to_s)
8
+ value(&:downcase) if opts[:downcase]
9
+
10
+ # this is database default for string type and it is good to define default unless defined
11
+ opts[:max] ||= 255
12
+
13
+ error_for(:min_length_error, opts[:min], value.length) if opts[:min] && value.length < opts[:min]
14
+ error_for(:max_length_error, opts[:max], value.length) if opts[:max] && value.length > opts[:max]
15
+ end
16
+
17
+ def db_schema
18
+ [:string, {
19
+ limit: @opts[:max] || 255
20
+ }]
21
+ end
22
+ end
23
+
@@ -1,12 +1,10 @@
1
- require_relative 'string'
1
+ require_relative 'string_type'
2
2
 
3
3
  class Typero::TextType < Typero::StringType
4
4
  opts :min, 'Minimun string length'
5
5
  opts :max, 'Maximun string length'
6
6
 
7
- def db_field
8
- opts = {}
9
- opts[:null] = false if @opts[:required]
10
- [:text, opts]
7
+ def db_schema
8
+ [:text, {}]
11
9
  end
12
10
  end
@@ -0,0 +1,5 @@
1
+ require_relative 'datetime_type'
2
+
3
+ class Typero::TimeType < Typero::DatetimeType
4
+ end
5
+
@@ -0,0 +1,15 @@
1
+ class Typero::TimezoneType < Typero::Type
2
+ error :en, :invalid_time_zone, 'Invalid time zone'
3
+
4
+ def set
5
+ TZInfo::Timezone.get(value)
6
+ rescue TZInfo::InvalidTimezoneIdentifier
7
+ error_for :invalid_time_zone
8
+ end
9
+
10
+ def db_schema
11
+ [:string, { length: 50 }]
12
+ end
13
+
14
+ end
15
+
@@ -0,0 +1,13 @@
1
+ class Typero::UrlType < Typero::Type
2
+ error :en, :url_not_starting_error, 'URL is not starting with http or https'
3
+
4
+ def set
5
+ parts = value.split('://')
6
+ error_for(:url_not_starting_error) unless parts[1]
7
+ end
8
+
9
+ def db_schema
10
+ [:string, {}]
11
+ end
12
+ end
13
+
data/lib/typero/typero.rb CHANGED
@@ -1,4 +1,4 @@
1
- # rules = Typero.new do
1
+ # rules = Typero.schema do
2
2
  # set :name, String, req: true
3
3
  # set :email, :email, req: true
4
4
  # set :skills, [:email], min: 2
@@ -6,7 +6,7 @@
6
6
  #
7
7
  # or
8
8
  #
9
- # rules = Typero.new do
9
+ # rules = Typero.schema do
10
10
  # string :name, req: true
11
11
  # email :email, req: true
12
12
  # email [:skills], min: 2
@@ -16,162 +16,93 @@
16
16
  # rules.valid?
17
17
  # rules.validate(@object) { |errors| ... }
18
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
- ###
19
+ module Typero
20
+ extend self
41
21
 
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?
22
+ VERSION = File.read File.expand_path "../../.version", File.dirname(__FILE__)
57
23
 
58
- # get field value
59
- value = @object[field]
24
+ # check and coerce value
25
+ # Typero.type(:label) -> Typero::LabelType
26
+ # Typero.type(:label, 'Foo bar') -> "foo-bar"
27
+ def type klass_name, value = :_undefined, opts = {}, &block
28
+ klass = Typero::Type.load(klass_name)
60
29
 
61
- if opts[:array]
62
- unless value.is_a?(Array)
63
- opts[:delimiter] ||= /\s*,\s*/
64
- value = value.to_s.split(opts[:delimiter])
30
+ if value == :_undefined
31
+ klass
32
+ else
33
+ begin
34
+ check = klass.new value, opts
35
+ check.get
36
+ rescue TypeError => error
37
+ if block
38
+ block.call error
39
+ false
40
+ else
41
+ raise error
65
42
  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
43
  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
44
  end
95
-
96
- @errors
97
45
  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
46
+ alias :set :type
47
+
48
+ # load or set type schema
49
+ # Typero.schema(:blog) { ... }
50
+ # Typero.schema(:blog, type: :model) { ... }
51
+ # Typero.schema(:blog)
52
+ # Typero.schema(type: :model)
53
+ def schema name=nil, opts=nil, &block
54
+ klass = name.to_s.classify if name && !name.is_a?(Hash)
55
+
56
+ if block_given?
57
+ Typero::Schema.new(&block).tap do |schema|
58
+ if klass
59
+ Typero::Schema::SCHEMAS[klass] = schema
60
+
61
+ if opts && opts[:type]
62
+ Typero::Schema::TYPES[opts[:type]] ||= []
63
+ Typero::Schema::TYPES[opts[:type]].push klass unless Typero::Schema::TYPES[opts[:type]].include?(klass)
64
+ end
65
+ end
66
+ end
140
67
  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]
68
+ # Schema not given, get schema
69
+ if name.is_a?(Hash)
70
+ # Typero.schema type: :model
71
+ if type = name[:type]
72
+ Typero::Schema::TYPES[type]
73
+ end
74
+ elsif klass
75
+ # Typero.schema :user
76
+ schema = Typero::Schema::SCHEMAS[klass]
77
+ schema ||= class_finder klass, :schema
78
+ schema || nil
79
+ else
80
+ raise ArgumentError, 'Schema type not defined.'
144
81
  end
145
-
146
- @errors[field] = msg
147
82
  end
148
83
  end
149
84
 
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
85
+ def defined? name
86
+ Typero::Type.load name
87
+ true
88
+ rescue ArgumentError
89
+ false
158
90
  end
159
91
 
160
- def check_filed_value field, value, opts
161
- return unless value
92
+ private
162
93
 
163
- klass = "Typero::%sType" % safe_type(opts[:type])
164
- check = klass.constantize.new value, opts
165
- check.value = check.default if check.value.nil?
94
+ # class_finder :user, :exporter, :representer
95
+ # find first UserExporter, User::Exporter, User::Representer, UserRepresenter
96
+ def class_finder *args
97
+ name = args.shift.to_s.classify
166
98
 
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
99
+ for el in args
100
+ for separator in ['_','/']
101
+ klass = [name, el].join(separator).classify
102
+ return klass.constantize if const_defined? klass
174
103
  end
175
104
  end
105
+
106
+ nil
176
107
  end
177
108
  end