typero 0.9.9 → 0.10.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: cb6ff45894808ab507b69280a5c0789b1a370aadfc8aa9e02a4d246080dd2b7c
4
- data.tar.gz: 4e7a1e952d836c72e4bcdb042335e61fcf7b123ab9b9a952f05c7d12f6a12ee5
3
+ metadata.gz: 988ca72b1b08538ccf6bf49e2fcd449a97b4b403bc8306e6d9c1242df6ae0a54
4
+ data.tar.gz: dd90e591bc0b9790e60656e8f6a342b033ab2adca4d80b80a6a1e702878f895e
5
5
  SHA512:
6
- metadata.gz: 45688f02580d891eb335d0eea0fbd56a4572f2571da2859c6dbc29253b848334640c6748ccd4fba3428b2a22309c3ad8ec1d21d2a42bc5da623d70785bc673dc
7
- data.tar.gz: cfd0091edbc1b7f30c6fcd3a8ba4e0cb6965b8389405abadaaac1b6b2fa0040fe0c39123d5d74c19c497446f42a40bc4110d4a95f54462867366edd27ee1a55d
6
+ metadata.gz: 8940ebb5034757bb9c4a285b0e6e74bc65131ab5bdebdd9154b0643843836c5cab7c2a98b988361181bea0cf38d461f40662bf9dda926d074bf7b5756f1d6312
7
+ data.tar.gz: e77d16947abb2762bc33ed5c5ba855da56924f2187bb8d31c067d614561cd3f44a07730f8fb858882baa0876ef55da9529a495695454a58bf5c2fe205b81e1f7
data/.version CHANGED
@@ -1 +1 @@
1
- 0.9.9
1
+ 0.10.0
data/lib/typero/define.rb CHANGED
@@ -1,21 +1,5 @@
1
- # Base class for schema validation, this loads and defined schema.
2
- # Accepts set of params and returns hash of parsed rules
3
-
4
- # module Typero
5
- # class Loader
6
- # def timestamps
7
- # created_at Time
8
- # created_by_ref
9
- # end
10
- # end
11
- # end
12
-
13
- # class Task < ApplicationModel
14
- # schema do
15
- # name # string type
16
- # timestamps # metod defined -> call
17
- # end
18
- # end
1
+ # Base class for schema definition DSL.
2
+ # Accepts a block of field declarations and returns hash of parsed rules.
19
3
 
20
4
  require 'set'
21
5
  require 'json'
@@ -40,6 +24,52 @@ module Typero
40
24
  def set field, *args, &block
41
25
  raise "Field name not given (Typero)" unless field
42
26
 
27
+ opts = parse_args args
28
+ field = field.to_s
29
+
30
+ # bang suffix defines block type for all fields in the block
31
+ if field.include?('!')
32
+ return define_block_type(field, &block)
33
+ end
34
+
35
+ # question mark suffix makes field optional
36
+ field = parse_field_name field, opts
37
+
38
+ resolve_type opts
39
+
40
+ # inline block defines a nested model schema
41
+ if block_given?
42
+ opts[:type] = :model
43
+ opts[:model] = Typero.schema &block
44
+ end
45
+
46
+ opts[:type] = opts[:type].to_s.downcase.to_sym
47
+ opts[:description] = opts.delete(:desc) unless opts[:desc].nil?
48
+
49
+ validate_opts opts
50
+
51
+ field = field.to_sym
52
+ db(:add_index, field) if opts.delete(:index)
53
+
54
+ @rules[field] = opts
55
+ end
56
+
57
+ # pass values for db_schema only
58
+ # db :timestamps
59
+ # db :add_index, :code -> t.add_index :code
60
+ def db *args
61
+ @db_rules.push args
62
+ end
63
+
64
+ # if method undefined, call set method
65
+ # age Integer -> set :age, type: :integer
66
+ def method_missing field, *args, &block
67
+ set field, *args, &block
68
+ end
69
+
70
+ # --- argument parsing ---
71
+
72
+ def parse_args args
43
73
  if args.first.is_a?(Hash)
44
74
  opts = args.first || {}
45
75
  else
@@ -48,22 +78,10 @@ module Typero
48
78
  end
49
79
 
50
80
  opts[:type] = :string if opts[:type].nil?
81
+ opts
82
+ end
51
83
 
52
- field = field.to_s
53
-
54
- if field.include?('!')
55
- if block
56
- field = field.sub('!', '')
57
- @block_type = field.to_sym
58
- instance_exec &block
59
- @block_type = nil
60
- return
61
- else
62
- raise ArgumentError.new 'If you use ! you have to provide a block'
63
- end
64
- end
65
-
66
- # name? - opional name
84
+ def parse_field_name field, opts
67
85
  if field.include?('?')
68
86
  field = field.sub('?', '')
69
87
  opts[:required] = false
@@ -72,21 +90,43 @@ module Typero
72
90
  opts[:required] = opts.delete(:req) unless opts[:req].nil?
73
91
  opts[:required] = true if opts[:required].nil?
74
92
 
75
- # array that allows duplicates
93
+ field
94
+ end
95
+
96
+ # --- type resolution ---
97
+
98
+ def define_block_type field, &block
99
+ raise ArgumentError, 'If you use ! you have to provide a block' unless block
100
+
101
+ field = field.sub('!', '')
102
+ @block_type = field.to_sym
103
+ instance_exec &block
104
+ @block_type = nil
105
+ end
106
+
107
+ def resolve_type opts
108
+ # bare Array or Set class -> array of strings
109
+ if opts[:type] == Array || opts[:type] == Set
110
+ opts[:type] = :string
111
+ opts[:array] = true
112
+ end
113
+
114
+ # Array[:type] -> typed array
76
115
  if opts[:type].is_a?(Array)
77
116
  opts[:type] = opts[:type].first
78
117
  opts[:array] = true
79
118
  end
80
119
 
81
- # no duplicates array
120
+ # Set[:type] -> typed array (no duplicates)
82
121
  if opts[:type].is_a?(Set)
83
122
  opts[:type] = opts[:type].to_a.first
84
123
  opts[:array] = true
85
124
  end
86
125
 
126
+ # block type override (integer! do ... end)
87
127
  opts[:type] = @block_type if @block_type
88
128
 
89
- # Boolean
129
+ # boolean variants
90
130
  if opts[:type].is_a?(TrueClass) || opts[:type] == :true
91
131
  opts[:required] = false
92
132
  opts[:default] = true
@@ -97,49 +137,19 @@ module Typero
97
137
  opts[:type] = :boolean
98
138
  end
99
139
 
100
- # model / schema
140
+ # model / schema reference
101
141
  if opts[:type].is_a?(Typero::Schema)
102
142
  opts[:model] = opts.delete(:type)
103
143
  end
104
144
  opts[:model] = opts.delete(:schema) if opts[:schema]
105
145
  opts[:type] = :model if opts[:model]
146
+ end
106
147
 
107
- if block_given?
108
- opts[:type] = :model
109
- opts[:model] = Typero.schema &block
110
- end
111
-
112
- opts[:type] = opts[:type].to_s.downcase.to_sym
113
-
114
- opts[:description] = opts.delete(:desc) unless opts[:desc].nil?
115
-
116
- # check allowed params, all optional should go in meta
148
+ def validate_opts opts
117
149
  type = Typero::Type.load opts[:type]
118
150
  opts.keys.each do |key|
119
151
  type.allowed_opt?(key) {|err| raise ArgumentError, err }
120
152
  end
121
-
122
- field = field.to_sym
123
-
124
- if opts.delete(:index)
125
- db :add_index, field
126
- end
127
-
128
- @rules[field] = opts
129
- end
130
-
131
- # pass values for db_schema only
132
- # db :timestamps
133
- # db :add_index, :code -> t.add_index :code
134
- def db *args
135
- @db_rules.push args.unshift(:db_rule!)
136
- end
137
-
138
- # if method undefine, call set method
139
- # age Integer -> set :age, type: :integer
140
- # email Array[:emails] -> set :emails, Array[:email]
141
- def method_missing field, *args, &block
142
- set field, *args, &block
143
153
  end
144
154
  end
145
155
  end
data/lib/typero/schema.rb CHANGED
@@ -1,31 +1,8 @@
1
- # Typero.schema :user, type: :model do
2
-
3
- # Typero.schema :some_name, type: :model, db: DB_LOG do
4
- # set :name, String, req: true
5
- # set :email, :email, req: true
6
- # set :emails, [:email], min: 2
7
- # end
8
- #
9
- # rules = Typero.schema :some_name
10
- #
11
- # or
12
- #
13
- # rules = Typero.schema do
14
- # string :name, req: true # generic email string
15
- # email :email, req: true # string of type email
16
- # emails [:skills], min: 2 # list of emails in filed named "emails"
17
- # end
18
- #
19
- # errors = rules.validate (@object || @hash) {|errors| ... }
20
- # rules.valid? (@object)
21
-
22
1
  module Typero
23
2
  class Schema
24
3
  SCHEMA_STORE ||= {}
25
4
 
26
- attr_reader :klass
27
- attr_reader :schema
28
- attr_reader :opts
5
+ attr_reader :klass, :opts
29
6
 
30
7
  # accepts dsl block to define schema
31
8
  # or define: keyword for internal use (only/except)
@@ -63,77 +40,31 @@ module Typero
63
40
  # validates any instance object with hash variable interface
64
41
  # it also coerces values
65
42
  def validate object, options = nil
66
- @object = object
67
- @errors = {}
68
- options = options || {}
69
-
70
- # remove undefined keys if Hash provided
71
- if options[:strict] && object.is_a?(Hash)
72
- undefined = object.keys.map(&:to_s) - @schema.rules.keys.map(&:to_s)
73
- object.delete_if { |k, _| undefined.include?(k.to_s) }
74
- end
75
-
76
- @schema.rules.each do |field, opts|
77
- # force field as a symbol
78
- field = field.to_sym
79
-
80
- opts.keys.each do |k|
81
- opts[k] = @object.instance_exec(&opts[k]) if opts[k].is_a?(Proc)
82
- end
83
-
84
- # set value to default if value is blank and default given
85
- if !opts[:default].nil? && @object[field].to_s.blank?
86
- @object[field] = opts[:default]
87
- end
43
+ @object = object
44
+ @errors = {}
45
+ options ||= {}
88
46
 
89
- if @object.respond_to?(:key?)
90
- if @object.key?(field)
91
- value = @object[field]
92
- elsif @object.key?(field.to_s)
93
- # invalid string key, needs fix
94
- value = @object[field] = @object.delete(field.to_s)
95
- end
96
- else
97
- value = @object[field]
98
- end
47
+ strip_undefined_keys! if options[:strict] && object.is_a?(Hash)
99
48
 
100
- if opts[:array]
101
- unless value.respond_to?(:each)
102
- opts[:delimiter] ||= /\s*[,\n]\s*/
103
- value = value.to_s.split(opts[:delimiter])
104
- end
105
-
106
- value = value
107
- .flatten
108
- .map { |el| el.to_s == '' ? nil : check_field_value(field, el, opts) }
109
- .compact
110
-
111
- value = Set.new(value).to_a unless opts[:duplicates]
112
-
113
- opts[:max_count] ||= 100
114
- add_error(field, 'Max number of array elements is %d, you have %d' % [opts[:max_count], value.length], opts) if value && value.length > opts[:max_count]
115
- add_error(field, 'Min number of array elements is %d, you have %d' % [opts[:min_count], value.length], opts) if value && opts[:min_count] && value.length < opts[:min_count]
49
+ @schema.rules.each do |field, raw_opts|
50
+ field = field.to_sym
51
+ opts = resolve_opts(raw_opts)
116
52
 
117
- add_required_error field, value.first, opts
118
- else
119
- value = nil if value.to_s == ''
53
+ apply_default field, opts
54
+ value = read_value field
120
55
 
121
- # if value is not list of allowed values, raise error
122
- allowed = opts[:allow] || opts[:allowed] || opts[:values]
123
- if value && allowed && !allowed.map(&:to_s).include?(value.to_s)
124
- add_error field, 'Value "%s" is not allowed' % value, opts
56
+ value =
57
+ if opts[:array]
58
+ validate_array field, value, opts
59
+ else
60
+ validate_scalar field, value, opts
125
61
  end
126
62
 
127
- value = check_field_value field, value, opts
128
-
129
- add_required_error field, value, opts
130
- end
131
-
132
63
  # present empty string values as nil
133
- @object[field] = value.to_s.sub(/\s+/, '') == '' ? nil : value
64
+ @object[field] = blank?(value) ? nil : value
134
65
  end
135
66
 
136
- if @errors.keys.length > 0 && block_given?
67
+ if @errors.any? && block_given?
137
68
  @errors.each { |k, v| yield(k, v) }
138
69
  end
139
70
 
@@ -141,26 +72,21 @@ module Typero
141
72
  end
142
73
 
143
74
  def valid? object
144
- errors = validate object
145
- errors.keys.length == 0
75
+ validate(object).empty?
76
+ end
77
+
78
+ # returns raw db rules like [:timestamps] or [:add_index, :code]
79
+ def db_rules
80
+ @schema.db_rules
146
81
  end
147
82
 
148
83
  # returns field, db_type, db_opts
149
84
  def db_schema
150
- out = @schema.rules.inject([]) do |total, (field, opts)|
151
- # get db field schema
152
- type, opts = Typero::Type.load(opts[:type]).new(nil, opts).db_field
153
-
154
- # add array true to field if not defined in schema
155
- schema_opts = @schema.rules[field]
156
- opts[:array] = true if schema_opts[:array]
157
-
158
- total << [field, type, opts]
85
+ @schema.rules.map do |field, opts|
86
+ type, db_opts = Typero::Type.load(opts[:type]).new(nil, opts).db_field
87
+ db_opts[:array] = true if opts[:array]
88
+ [field, type, db_opts]
159
89
  end
160
-
161
- out += @schema.db_rules
162
-
163
- out
164
90
  end
165
91
 
166
92
  # iterate through all the rules via block interface
@@ -178,33 +104,78 @@ module Typero
178
104
 
179
105
  private
180
106
 
181
- # adds error to array or prefixes with field name
182
- def add_error field, msg, opts
183
- if @errors[field]
184
- @errors[field] += ", %s" % msg
185
- else
186
- if msg && msg[0, 1].downcase == msg[0, 1]
187
- field_name = opts[:name] || field.to_s.sub(/_id$/, "").capitalize
188
- msg = "%s %s" % [field_name, msg]
189
- end
107
+ # remove keys not defined in schema
108
+ def strip_undefined_keys!
109
+ defined_keys = @schema.rules.keys.map(&:to_s)
110
+ @object.delete_if { |k, _| !defined_keys.include?(k.to_s) }
111
+ end
190
112
 
191
- @errors[field] = msg
113
+ # dup opts so Proc resolution does not mutate stored schema
114
+ def resolve_opts raw_opts
115
+ opts = raw_opts.dup
116
+ opts.each do |k, v|
117
+ opts[k] = @object.instance_exec(&v) if v.is_a?(Proc)
192
118
  end
119
+ opts
193
120
  end
194
121
 
195
- def safe_type type
196
- type.to_s.gsub(/[^\w]/, "").classify
122
+ def apply_default field, opts
123
+ if !opts[:default].nil? && @object[field].to_s.blank?
124
+ @object[field] = opts[:default]
125
+ end
197
126
  end
198
127
 
199
- def add_required_error field, value, opts
200
- return unless opts[:required] && value.nil?
201
- msg = opts[:required].class == TrueClass ? "is required" : opts[:required]
202
- add_error field, msg, opts
128
+ # read value from object, normalizing string keys to symbols for Hash
129
+ def read_value field
130
+ if @object.respond_to?(:key?)
131
+ if @object.key?(field)
132
+ @object[field]
133
+ elsif @object.key?(field.to_s)
134
+ @object[field] = @object.delete(field.to_s)
135
+ end
136
+ else
137
+ @object[field]
138
+ end
203
139
  end
204
140
 
205
- def check_field_value field, value, opts
206
- klass = "Typero::%sType" % safe_type(opts[:type])
207
- check = klass.constantize.new value, opts
141
+ def validate_array field, value, opts
142
+ unless value.respond_to?(:each)
143
+ delimiter = opts[:delimiter] || /\s*[,\n]\s*/
144
+ value = value.to_s.split(delimiter)
145
+ end
146
+
147
+ value = value
148
+ .flatten
149
+ .map { |el| el.to_s == '' ? nil : coerce_value(field, el, opts) }
150
+ .compact
151
+
152
+ value = Set.new(value).to_a unless opts[:duplicates]
153
+
154
+ max_count = opts[:max_count] || 100
155
+ add_error(field, 'Max number of array elements is %d, you have %d' % [max_count, value.length], opts) if value.length > max_count
156
+ add_error(field, 'Min number of array elements is %d, you have %d' % [opts[:min_count], value.length], opts) if opts[:min_count] && value.length < opts[:min_count]
157
+
158
+ add_required_error field, value.first, opts
159
+ value
160
+ end
161
+
162
+ def validate_scalar field, value, opts
163
+ value = nil if value.to_s == ''
164
+
165
+ allowed = opts[:allow] || opts[:allowed] || opts[:values]
166
+ if value && allowed && !allowed.map(&:to_s).include?(value.to_s)
167
+ add_error field, 'Value "%s" is not allowed' % value, opts
168
+ end
169
+
170
+ value = coerce_value field, value, opts
171
+ add_required_error field, value, opts
172
+ value
173
+ end
174
+
175
+ # coerce a single value through its type class
176
+ def coerce_value field, value, opts
177
+ klass = Typero::Type.load(opts[:type])
178
+ check = klass.new value, opts
208
179
  check.get
209
180
  rescue TypeError => e
210
181
  if e.message[0] == '{'
@@ -217,5 +188,29 @@ module Typero
217
188
  rescue JSON::ParserError
218
189
  add_error field, e.message, opts
219
190
  end
191
+
192
+ # adds error to hash, prefixing with field name if message starts lowercase
193
+ def add_error field, msg, opts
194
+ if @errors[field]
195
+ @errors[field] += ", %s" % msg
196
+ else
197
+ if msg && msg[0, 1].downcase == msg[0, 1]
198
+ field_name = opts[:name] || field.to_s.sub(/_id$/, "").capitalize
199
+ msg = "%s %s" % [field_name, msg]
200
+ end
201
+
202
+ @errors[field] = msg
203
+ end
204
+ end
205
+
206
+ def add_required_error field, value, opts
207
+ return unless opts[:required] && value.nil?
208
+ msg = opts[:required].class == TrueClass ? "is required" : opts[:required]
209
+ add_error field, msg, opts
210
+ end
211
+
212
+ def blank? value
213
+ value.to_s.sub(/\s+/, '') == ''
214
+ end
220
215
  end
221
216
  end
@@ -0,0 +1,75 @@
1
+ # Shared coordinate extraction from map URLs and plain lat,lon strings.
2
+ # Returns [lat, lon] as strings, or nil if no match.
3
+
4
+ module Typero
5
+ module GeoExtract
6
+ # extract lat, lon from various map URLs or plain "lat,lon" string
7
+ def extract_coords data
8
+ data = data.to_s.strip
9
+
10
+ coords = extract_from_url(data) || extract_from_string(data)
11
+ return nil unless coords
12
+
13
+ coords.map { |c| c.to_s.strip }
14
+ end
15
+
16
+ private
17
+
18
+ def extract_from_url data
19
+ return nil unless data.include?('://')
20
+
21
+ # Google Maps: /@45.815,15.982,... or ?q=45.815,15.982
22
+ if data.include?('/@')
23
+ parts = data.split('/@', 2).last.split(',')
24
+ return [parts[0], parts[1]] if parts.length >= 2
25
+ end
26
+
27
+ if data =~ /google.*[?&]q=([-\d.]+),([-\d.]+)/
28
+ return [$1, $2]
29
+ end
30
+
31
+ # OpenStreetMap: /#map=15/45.815/15.982
32
+ if data =~ /openstreetmap.*#map=\d+\/([-\d.]+)\/([-\d.]+)/
33
+ return [$1, $2]
34
+ end
35
+
36
+ # OpenStreetMap: ?mlat=45.815&mlon=15.982
37
+ if data =~ /openstreetmap.*[?&]mlat=([-\d.]+).*[?&]mlon=([-\d.]+)/
38
+ return [$1, $2]
39
+ end
40
+
41
+ # Apple Maps: ?ll=45.815,15.982
42
+ if data =~ /maps\.apple\.com.*[?&]ll=([-\d.]+),([-\d.]+)/
43
+ return [$1, $2]
44
+ end
45
+
46
+ # Waze: ?ll=45.815,15.982
47
+ if data =~ /waze\.com.*[?&]ll=([-\d.]+),([-\d.]+)/
48
+ return [$1, $2]
49
+ end
50
+
51
+ # Bing Maps: ?cp=45.815~15.982
52
+ if data =~ /bing\.com.*[?&]cp=([-\d.]+)~([-\d.]+)/
53
+ return [$1, $2]
54
+ end
55
+
56
+ # Bing Maps: /point.45.815_15.982
57
+ if data =~ /bing\.com.*point\.([-\d.]+)_([-\d.]+)/
58
+ return [$1, $2]
59
+ end
60
+
61
+ nil
62
+ end
63
+
64
+ def extract_from_string data
65
+ return nil if data.include?('://') || data.include?('POINT')
66
+
67
+ if data.include?(',')
68
+ parts = data.split(/\s*,\s*/)
69
+ return [parts[0], parts[1]] if parts.length >= 2
70
+ end
71
+
72
+ nil
73
+ end
74
+ end
75
+ end
@@ -103,7 +103,7 @@ module Typero
103
103
  if value.nil?
104
104
  opts[:default].nil? ? default : opts[:default]
105
105
  else
106
- set
106
+ coerce
107
107
 
108
108
  if opts[:values] && !opts[:values].map(&:to_s).include?(@value.to_s)
109
109
  error_for(:not_in_range, opts[:values].join(', '))
@@ -1,7 +1,7 @@
1
1
  class Typero::BooleanType < Typero::Type
2
2
  error :en, :unsupported_boolean, 'Unsupported boolean param value: %s'
3
3
 
4
- def set
4
+ def coerce
5
5
  value do |_|
6
6
  bool = _.to_s
7
7
 
@@ -6,7 +6,7 @@ require_relative './float_type'
6
6
 
7
7
  class Typero::CurrencyType < Typero::FloatType
8
8
 
9
- def set
9
+ def coerce
10
10
  value { |data| data.to_f.round(2) }
11
11
 
12
12
  error_for(:min_value_error, opts[:min], value) if opts[:min] && value < opts[:min]
@@ -6,7 +6,7 @@ class Typero::DateType < Typero::Type
6
6
  error :en, :min_date, 'Minimal allowed date is %s'
7
7
  error :en, :max_date, 'Maximal allowed date is %s'
8
8
 
9
- def set
9
+ def coerce
10
10
  unless [Date].include?(value.class)
11
11
  begin
12
12
  value { |data| DateTime.parse(data.to_s) }
@@ -6,7 +6,7 @@ class Typero::DatetimeType < Typero::DateType
6
6
 
7
7
  error :en, :invalid_datetime, 'is not a valid datetime'
8
8
 
9
- def set
9
+ def coerce
10
10
  unless [Time, DateTime].include?(value.class)
11
11
  begin
12
12
  value { |data| DateTime.parse(data.to_s) }
@@ -2,7 +2,7 @@ class Typero::EmailType < Typero::Type
2
2
  error :en, :not_8_chars_error, 'is not having at least 8 characters'
3
3
  error :en, :missing_monkey_error, 'is missing @'
4
4
 
5
- def set
5
+ def coerce
6
6
  value do |email|
7
7
  email.downcase.gsub(/\s+/,'+')
8
8
  end
@@ -3,12 +3,11 @@ class Typero::FloatType < Typero::Type
3
3
  opts :max, 'Maximum value'
4
4
  opts :round, 'Round to (decimal spaces)'
5
5
 
6
- def set
7
- @value =
6
+ def coerce
8
7
  if opts[:round]
9
- value.to_f.round(opts[:round])
8
+ value { |data| data.to_f.round(opts[:round]) }
10
9
  else
11
- value.to_f
10
+ value { |data| data.to_f }
12
11
  end
13
12
 
14
13
  error_for(:min_value_error, opts[:min], value) if opts[:min] && value < opts[:min]
@@ -19,4 +18,3 @@ class Typero::FloatType < Typero::Type
19
18
  [:float, {}]
20
19
  end
21
20
  end
22
-
@@ -1,7 +1,7 @@
1
1
  class Typero::HashType < Typero::Type
2
2
  error :en, :not_hash_type_error, 'value is not hash type'
3
3
 
4
- def set
4
+ def coerce
5
5
  if value.is_a?(String) && value[0,1] == '{'
6
6
  @value = JSON.parse(value)
7
7
  end
@@ -6,7 +6,7 @@ class Typero::ImageType < Typero::Type
6
6
 
7
7
  opts :strict, 'Force image to have known extension (%s)' % FORMATS.join(', ')
8
8
 
9
- def set
9
+ def coerce
10
10
  error_for(:image_not_starting_error) unless value =~ /^https?:\/\/./
11
11
 
12
12
  if opts[:strict]
@@ -2,7 +2,7 @@ class Typero::IntegerType < Typero::Type
2
2
  opts :min, 'Minimum value'
3
3
  opts :max, 'Maximum value'
4
4
 
5
- def set
5
+ def coerce
6
6
  value(&:to_i)
7
7
 
8
8
  error_for(:min_value_error, opts[:min], value) if opts[:min] && value < opts[:min]
@@ -1,5 +1,5 @@
1
1
  class Typero::LabelType < Typero::Type
2
- def set
2
+ def coerce
3
3
  value do |data|
4
4
  data
5
5
  .to_s
@@ -1,7 +1,7 @@
1
1
  class Typero::LocaleType < Typero::Type
2
2
  error :en, :locale_bad_format, 'Locale "%s" is in bad format (should be xx or xx-xx)'
3
3
 
4
- def set
4
+ def coerce
5
5
  error_for(:locale_bad_format, value) unless value =~ /^[\w\-]{2,5}$/
6
6
  end
7
7
 
@@ -1,5 +1,5 @@
1
1
  class Typero::ModelType < Typero::Type
2
- def set
2
+ def coerce
3
3
  value(&:to_h)
4
4
 
5
5
  errors = {}
@@ -1,7 +1,7 @@
1
1
  class Typero::OibType < Typero::Type
2
2
  error :en, :not_an_oib_error, 'not in an OIB format'
3
3
 
4
- def set
4
+ def coerce
5
5
  value do |data|
6
6
  check?(data) ? data.to_s : error_for(:not_an_oib_error)
7
7
  end
@@ -22,7 +22,7 @@ class Typero::OibType < Typero::Type
22
22
  return false unless oib.match(/^[0-9]{11}$/)
23
23
 
24
24
  control_sum = (0..9).inject(10) do |middle, position|
25
- middle += oib.at(position).to_i
25
+ middle += oib[position].to_i
26
26
  middle %= 10
27
27
  middle = 10 if middle == 0
28
28
  middle *= 2
@@ -32,7 +32,7 @@ class Typero::OibType < Typero::Type
32
32
  control_sum = 11 - control_sum
33
33
  control_sum = 0 if control_sum == 10
34
34
 
35
- return control_sum == oib.at(10).to_i
35
+ return control_sum == oib[10].to_i
36
36
  end
37
37
 
38
38
  end
@@ -0,0 +1,15 @@
1
+ class Typero::PhoneType < Typero::Type
2
+ error :en, :invalid_phone, 'is not a valid phone number'
3
+
4
+ def coerce
5
+ value do |data|
6
+ data.to_s.gsub(/[\(\)\-]/, ' ').gsub(/\s+/, ' ').strip
7
+ end
8
+
9
+ error_for(:invalid_phone) unless value =~ /^[\d\s\+]+$/ && value.scan(/\d/).length >= 5
10
+ end
11
+
12
+ def db_schema
13
+ [:string, { limit: 50 }]
14
+ end
15
+ end
@@ -2,20 +2,17 @@
2
2
  # point = @object.class.xselect("ST_AsText(#{field}) as #{field}").where(id: @object.id).first[field.to_sym]
3
3
 
4
4
  class Typero::PointType < Typero::Type
5
- def set
6
- if value.include?('/@')
7
- # extract value from google maps link
8
- point = value.split('/@', 2).last.split(',')
9
- value { [point[0], point[1]].join(',') }
10
- end
5
+ include Typero::GeoExtract
11
6
 
12
- if !value.include?('POINT') && value.include?(',')
13
- point = value.sub(/\s*,\s*/, ' ')
14
- value { 'SRID=4326;POINT(%s)' % point }
15
- end
7
+ def coerce
8
+ if value.is_a?(String) && !value.include?('POINT')
9
+ coords = extract_coords(value)
16
10
 
17
- if value && value.include?(',') && value !~ /^SRID=4326;POINT\(/
18
- error_for(:unallowed_characters_error)
11
+ if coords
12
+ value { 'SRID=4326;POINT(%s %s)' % [coords[0], coords[1]] }
13
+ else
14
+ error_for(:unallowed_characters_error)
15
+ end
19
16
  end
20
17
  end
21
18
 
@@ -23,4 +20,3 @@ class Typero::PointType < Typero::Type
23
20
  [:geography, {}]
24
21
  end
25
22
  end
26
-
@@ -1,22 +1,19 @@
1
- # Same as point, but we keep data as a float in Array
1
+ # Same as point, but we keep data as a float array [lat, lon]
2
2
 
3
3
  class Typero::SimplePointType < Typero::Type
4
- def set
5
- if value.include?('/@')
6
- # extract value from google maps link
7
- point = value.split('/@', 2).last.split(',')[0,2]
8
- value { point }
9
- end
4
+ include Typero::GeoExtract
10
5
 
11
- if !value.include?('POINT') && value.include?(',')
12
- value { value.split(/\s*,\s*/)[0,2] }
13
- end
6
+ def coerce
7
+ coords = extract_coords(value)
14
8
 
15
- # value { value.map { sprintf("%.16f", _1).to_f } }
9
+ if coords
10
+ value { coords }
11
+ else
12
+ error_for(:unallowed_characters_error)
13
+ end
16
14
  end
17
15
 
18
16
  def db_schema
19
17
  [:float, { array: true }]
20
18
  end
21
19
  end
22
-
@@ -0,0 +1,25 @@
1
+ class Typero::SlugType < Typero::Type
2
+ opts :max, 'Maximum slug length'
3
+
4
+ error :en, :invalid_slug, 'contains invalid characters'
5
+
6
+ def coerce
7
+ max = opts[:max] || 255
8
+
9
+ value do |data|
10
+ data
11
+ .to_s
12
+ .downcase
13
+ .gsub(/[^\w\-]/, '-')
14
+ .gsub(/\-+/, '-')
15
+ .sub(/^\-/, '')
16
+ .sub(/\-$/, '')[0, max]
17
+ end
18
+
19
+ error_for(:invalid_slug) unless value =~ /^[\w][\w\-]*[\w]$|^[\w]$/
20
+ end
21
+
22
+ def db_schema
23
+ [:string, { limit: opts[:max] || 255 }]
24
+ end
25
+ end
@@ -3,7 +3,7 @@ class Typero::StringType < Typero::Type
3
3
  opts :max, 'Maximum string length'
4
4
  opts :downcase, 'is the string in downcase?'
5
5
 
6
- def set
6
+ def coerce
7
7
  value(&:to_s)
8
8
  value(&:downcase) if opts[:downcase]
9
9
 
@@ -4,7 +4,7 @@ class Typero::TextType < Typero::StringType
4
4
  opts :min, 'Minimum string length'
5
5
  opts :max, 'Maximum string length'
6
6
 
7
- def set
7
+ def coerce
8
8
  value(&:to_s)
9
9
  value(&:downcase) if opts[:downcase]
10
10
 
@@ -1,7 +1,7 @@
1
1
  class Typero::TimezoneType < Typero::Type
2
2
  error :en, :invalid_time_zone, 'Invalid time zone'
3
3
 
4
- def set
4
+ def coerce
5
5
  TZInfo::Timezone.get(value)
6
6
  rescue TZInfo::InvalidTimezoneIdentifier
7
7
  error_for :invalid_time_zone
@@ -1,7 +1,7 @@
1
1
  class Typero::UrlType < Typero::Type
2
2
  error :en, :url_not_starting_error, 'URL is not starting with http or https'
3
3
 
4
- def set
4
+ def coerce
5
5
  error_for(:url_not_starting_error) unless value =~ /^https?:\/\//
6
6
  end
7
7
 
@@ -0,0 +1,13 @@
1
+ class Typero::UuidType < Typero::Type
2
+ error :en, :invalid_uuid, 'is not a valid UUID'
3
+
4
+ def coerce
5
+ value { |data| data.to_s.strip.downcase }
6
+
7
+ error_for(:invalid_uuid) unless value =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
8
+ end
9
+
10
+ def db_schema
11
+ [:string, { limit: 36 }]
12
+ end
13
+ end
data/lib/typero.rb CHANGED
@@ -5,6 +5,7 @@ require_relative 'typero/typero'
5
5
  require_relative 'typero/schema'
6
6
  require_relative 'typero/define'
7
7
  require_relative 'typero/type/type'
8
+ require_relative 'typero/type/geo_extract'
8
9
 
9
10
  # checker types
10
11
  Dir['%s/typero/type/types/*.rb' % __dir__].each do |file|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typero
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.9
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dino Reic
@@ -21,6 +21,7 @@ files:
21
21
  - "./lib/typero.rb"
22
22
  - "./lib/typero/define.rb"
23
23
  - "./lib/typero/schema.rb"
24
+ - "./lib/typero/type/geo_extract.rb"
24
25
  - "./lib/typero/type/type.rb"
25
26
  - "./lib/typero/type/types/boolean_type.rb"
26
27
  - "./lib/typero/type/types/currency_type.rb"
@@ -35,13 +36,16 @@ files:
35
36
  - "./lib/typero/type/types/locale_type.rb"
36
37
  - "./lib/typero/type/types/model_type.rb"
37
38
  - "./lib/typero/type/types/oib_type.rb"
39
+ - "./lib/typero/type/types/phone_type.rb"
38
40
  - "./lib/typero/type/types/point_type.rb"
39
41
  - "./lib/typero/type/types/simple_point_type.rb"
42
+ - "./lib/typero/type/types/slug_type.rb"
40
43
  - "./lib/typero/type/types/string_type.rb"
41
44
  - "./lib/typero/type/types/text_type.rb"
42
45
  - "./lib/typero/type/types/time_type.rb"
43
46
  - "./lib/typero/type/types/timezone_type.rb"
44
47
  - "./lib/typero/type/types/url_type.rb"
48
+ - "./lib/typero/type/types/uuid_type.rb"
45
49
  - "./lib/typero/typero.rb"
46
50
  homepage: https://github.com/dux/typero
47
51
  licenses:
@@ -61,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
61
65
  - !ruby/object:Gem::Version
62
66
  version: '0'
63
67
  requirements: []
64
- rubygems_version: 3.6.9
68
+ rubygems_version: 4.0.8
65
69
  specification_version: 4
66
70
  summary: Ruby type system
67
71
  test_files: []