typero 0.9.6 → 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 +4 -4
- data/.version +1 -1
- data/lib/adapters/sequel.rb +33 -24
- data/lib/typero/{params.rb → define.rb} +89 -64
- data/lib/typero/schema.rb +140 -124
- data/lib/typero/type/geo_extract.rb +75 -0
- data/lib/typero/type/type.rb +7 -5
- data/lib/typero/type/types/boolean_type.rb +1 -1
- data/lib/typero/type/types/currency_type.rb +4 -1
- data/lib/typero/type/types/date_type.rb +9 -4
- data/lib/typero/type/types/datetime_type.rb +9 -3
- data/lib/typero/type/types/email_type.rb +1 -1
- data/lib/typero/type/types/float_type.rb +7 -11
- data/lib/typero/type/types/hash_type.rb +3 -3
- data/lib/typero/type/types/image_type.rb +4 -2
- data/lib/typero/type/types/integer_type.rb +2 -2
- data/lib/typero/type/types/label_type.rb +1 -1
- data/lib/typero/type/types/locale_type.rb +1 -1
- data/lib/typero/type/types/model_type.rb +2 -2
- data/lib/typero/type/types/oib_type.rb +4 -4
- data/lib/typero/type/types/phone_type.rb +15 -0
- data/lib/typero/type/types/point_type.rb +9 -13
- data/lib/typero/type/types/simple_point_type.rb +9 -10
- data/lib/typero/type/types/slug_type.rb +25 -0
- data/lib/typero/type/types/string_type.rb +3 -3
- data/lib/typero/type/types/text_type.rb +10 -2
- data/lib/typero/type/types/timezone_type.rb +1 -1
- data/lib/typero/type/types/url_type.rb +2 -3
- data/lib/typero/type/types/uuid_type.rb +13 -0
- data/lib/typero/typero.rb +14 -1
- data/lib/typero.rb +2 -1
- metadata +9 -22
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 988ca72b1b08538ccf6bf49e2fcd449a97b4b403bc8306e6d9c1242df6ae0a54
|
|
4
|
+
data.tar.gz: dd90e591bc0b9790e60656e8f6a342b033ab2adca4d80b80a6a1e702878f895e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8940ebb5034757bb9c4a285b0e6e74bc65131ab5bdebdd9154b0643843836c5cab7c2a98b988361181bea0cf38d461f40662bf9dda926d074bf7b5756f1d6312
|
|
7
|
+
data.tar.gz: e77d16947abb2762bc33ed5c5ba855da56924f2187bb8d31c067d614561cd3f44a07730f8fb858882baa0876ef55da9529a495695454a58bf5c2fe205b81e1f7
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.10.0
|
data/lib/adapters/sequel.rb
CHANGED
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
module Sequel::Plugins::Typero
|
|
4
4
|
module ClassMethods
|
|
5
|
-
def
|
|
6
|
-
|
|
5
|
+
def schema name = nil, &block
|
|
6
|
+
name ||= self
|
|
7
|
+
name = name.to_s.underscore.singularize
|
|
8
|
+
value = Typero.schema name, type: :model, &block
|
|
9
|
+
|
|
10
|
+
if ENV['DB_MIGRATE'] == 'true' && defined?(AutoMigrate)
|
|
11
|
+
AutoMigrate.apply_schema self
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
value
|
|
7
15
|
end
|
|
8
16
|
end
|
|
9
17
|
|
|
@@ -12,31 +20,32 @@ module Sequel::Plugins::Typero
|
|
|
12
20
|
def validate
|
|
13
21
|
super
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
# Typero schema check
|
|
24
|
+
if schema = Typero.schema?(self.class)
|
|
25
|
+
schema.validate(self) do |name, err|
|
|
26
|
+
errors.add(name, err) unless (errors.on(name) || []).include?(err)
|
|
27
|
+
end
|
|
20
28
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
# this are rules unique to database, so we check them here
|
|
30
|
+
schema.rules.each do |field, rule|
|
|
31
|
+
# check uniqe fields
|
|
32
|
+
if unique = rule.dig(:meta, :unique)
|
|
33
|
+
id = self[:id] || 0
|
|
34
|
+
value = self[field]
|
|
35
|
+
|
|
36
|
+
# we only check if field is changed
|
|
37
|
+
if value.present? && column_changed?(field) && self.class.xwhere("LOWER(%s)=LOWER(?) and #{respond_to?(:ref) ? :ref : :id}::text<>?" % [field], value, id.to_s).first
|
|
38
|
+
error = unique.class == TrueClass ? %[Value "#{value}" for field "#{field}" has been already used, please chose another value.] : unique
|
|
39
|
+
errors.add(field, error) unless (errors.on(field) || []).include?(error)
|
|
40
|
+
end
|
|
32
41
|
end
|
|
33
|
-
end
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
# check protected fields
|
|
44
|
+
if (prot = rule.dig(:meta, :protected)) && self[:id]
|
|
45
|
+
if column_changed?(field)
|
|
46
|
+
error = prot.class == TrueClass ? "value once defined can't be overwritten." : prot
|
|
47
|
+
errors.add(field, error) unless (errors.on(field) || []).include?(error)
|
|
48
|
+
end
|
|
40
49
|
end
|
|
41
50
|
end
|
|
42
51
|
end
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
# Base class for schema
|
|
2
|
-
# Accepts
|
|
1
|
+
# Base class for schema definition DSL.
|
|
2
|
+
# Accepts a block of field declarations and returns hash of parsed rules.
|
|
3
|
+
|
|
4
|
+
require 'set'
|
|
5
|
+
require 'json'
|
|
3
6
|
|
|
4
7
|
module Typero
|
|
5
|
-
class
|
|
8
|
+
class Define
|
|
6
9
|
attr_reader :db_rules
|
|
7
10
|
|
|
8
|
-
def initialize &block
|
|
11
|
+
def initialize rules = nil, &block
|
|
9
12
|
@db_rules = []
|
|
10
|
-
@rules = {}
|
|
11
|
-
instance_exec &block
|
|
13
|
+
@rules = rules || {}
|
|
14
|
+
instance_exec &block if block
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
def rules
|
|
@@ -21,6 +24,52 @@ module Typero
|
|
|
21
24
|
def set field, *args, &block
|
|
22
25
|
raise "Field name not given (Typero)" unless field
|
|
23
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
|
|
24
73
|
if args.first.is_a?(Hash)
|
|
25
74
|
opts = args.first || {}
|
|
26
75
|
else
|
|
@@ -29,22 +78,10 @@ module Typero
|
|
|
29
78
|
end
|
|
30
79
|
|
|
31
80
|
opts[:type] = :string if opts[:type].nil?
|
|
81
|
+
opts
|
|
82
|
+
end
|
|
32
83
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if field.include?('!')
|
|
36
|
-
if block
|
|
37
|
-
field = field.sub('!', '')
|
|
38
|
-
@block_type = field.to_sym
|
|
39
|
-
instance_exec &block
|
|
40
|
-
@block_type = nil
|
|
41
|
-
return
|
|
42
|
-
else
|
|
43
|
-
raise ArgumentError.new 'If you use ! you have to provide a block'
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# name? - opional name
|
|
84
|
+
def parse_field_name field, opts
|
|
48
85
|
if field.include?('?')
|
|
49
86
|
field = field.sub('?', '')
|
|
50
87
|
opts[:required] = false
|
|
@@ -53,78 +90,66 @@ module Typero
|
|
|
53
90
|
opts[:required] = opts.delete(:req) unless opts[:req].nil?
|
|
54
91
|
opts[:required] = true if opts[:required].nil?
|
|
55
92
|
|
|
56
|
-
|
|
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
|
|
57
115
|
if opts[:type].is_a?(Array)
|
|
58
116
|
opts[:type] = opts[:type].first
|
|
59
117
|
opts[:array] = true
|
|
60
118
|
end
|
|
61
119
|
|
|
62
|
-
# no duplicates
|
|
120
|
+
# Set[:type] -> typed array (no duplicates)
|
|
63
121
|
if opts[:type].is_a?(Set)
|
|
64
122
|
opts[:type] = opts[:type].to_a.first
|
|
65
123
|
opts[:array] = true
|
|
66
124
|
end
|
|
67
125
|
|
|
126
|
+
# block type override (integer! do ... end)
|
|
68
127
|
opts[:type] = @block_type if @block_type
|
|
69
128
|
|
|
70
|
-
#
|
|
129
|
+
# boolean variants
|
|
71
130
|
if opts[:type].is_a?(TrueClass) || opts[:type] == :true
|
|
72
131
|
opts[:required] = false
|
|
73
132
|
opts[:default] = true
|
|
74
133
|
opts[:type] = :boolean
|
|
75
134
|
elsif opts[:type].is_a?(FalseClass) || opts[:type] == :false || opts[:type] == :boolean
|
|
76
|
-
opts[:required] = false
|
|
135
|
+
opts[:required] = false
|
|
77
136
|
opts[:default] = false if opts[:default].nil?
|
|
78
137
|
opts[:type] = :boolean
|
|
79
138
|
end
|
|
80
139
|
|
|
81
|
-
# model / schema
|
|
82
|
-
if opts[:type].
|
|
140
|
+
# model / schema reference
|
|
141
|
+
if opts[:type].is_a?(Typero::Schema)
|
|
83
142
|
opts[:model] = opts.delete(:type)
|
|
84
143
|
end
|
|
85
144
|
opts[:model] = opts.delete(:schema) if opts[:schema]
|
|
86
145
|
opts[:type] = :model if opts[:model]
|
|
146
|
+
end
|
|
87
147
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
opts[:model] = Typero.schema &block
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
opts[:type] ||= 'string'
|
|
94
|
-
opts[:type] = opts[:type].to_s.downcase.to_sym
|
|
95
|
-
|
|
96
|
-
opts[:description] = opts.delete(:desc) unless opts[:desc].nil?
|
|
97
|
-
|
|
98
|
-
# chek alloed params, all optional should go in meta
|
|
148
|
+
def validate_opts opts
|
|
149
|
+
type = Typero::Type.load opts[:type]
|
|
99
150
|
opts.keys.each do |key|
|
|
100
|
-
type = Typero::Type.load opts[:type]
|
|
101
151
|
type.allowed_opt?(key) {|err| raise ArgumentError, err }
|
|
102
152
|
end
|
|
103
|
-
|
|
104
|
-
field = field.to_sym
|
|
105
|
-
|
|
106
|
-
db :add_index, field if opts.delete(:index)
|
|
107
|
-
|
|
108
|
-
# trigger error if type not found
|
|
109
|
-
Typero::Type.load opts[:type]
|
|
110
|
-
|
|
111
|
-
@rules[field] = opts
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# pass values for db_schema only
|
|
115
|
-
# db :timestamps
|
|
116
|
-
# db :add_index, :code -> t.add_index :code
|
|
117
|
-
def db *args
|
|
118
|
-
@db_rules.push args.unshift(:db_rule!)
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# set :age, type: :integer -> integer :age
|
|
122
|
-
# email :email
|
|
123
|
-
#
|
|
124
|
-
# set :emails, Array[:email]
|
|
125
|
-
# email Array[:emails]
|
|
126
|
-
def method_missing field, *args, &block
|
|
127
|
-
set field, *args, &block
|
|
128
153
|
end
|
|
129
154
|
end
|
|
130
155
|
end
|
data/lib/typero/schema.rb
CHANGED
|
@@ -1,38 +1,18 @@
|
|
|
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)
|
|
20
|
-
# rules.valid? (@object)
|
|
21
|
-
# rules.validate(@object) {|errors| ... }
|
|
22
|
-
|
|
23
1
|
module Typero
|
|
24
2
|
class Schema
|
|
25
3
|
SCHEMA_STORE ||= {}
|
|
26
4
|
|
|
27
|
-
attr_reader :klass
|
|
28
|
-
|
|
29
|
-
|
|
5
|
+
attr_reader :klass, :opts
|
|
6
|
+
|
|
7
|
+
# accepts dsl block to define schema
|
|
8
|
+
# or define: keyword for internal use (only/except)
|
|
9
|
+
def initialize name, opts = nil, define: nil, &block
|
|
10
|
+
@opts = opts || {}
|
|
30
11
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@
|
|
35
|
-
@schema = Params.new &block
|
|
12
|
+
if define
|
|
13
|
+
@schema = define
|
|
14
|
+
elsif block
|
|
15
|
+
@schema = Define.new &block
|
|
36
16
|
|
|
37
17
|
if name
|
|
38
18
|
@klass = name
|
|
@@ -43,78 +23,48 @@ module Typero
|
|
|
43
23
|
end
|
|
44
24
|
end
|
|
45
25
|
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# remove undefined keys if Hash provided
|
|
54
|
-
if @options[:strict] && object.is_a?(Hash)
|
|
55
|
-
undefined = object.keys.map(&:to_s) - @schema.rules.keys.map(&:to_s)
|
|
56
|
-
object.delete_if { |k, _| undefined.include?(k.to_s) }
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
@schema.rules.each do |field, opts|
|
|
60
|
-
# force filed as a symbol
|
|
61
|
-
field = field.to_sym
|
|
62
|
-
|
|
63
|
-
for k in opts.keys
|
|
64
|
-
opts[k] = @object.instance_exec(&opts[k]) if opts[k].is_a?(Proc)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# set value to default if value is blank and default given
|
|
68
|
-
@object[field] = opts[:default] if opts[:default] && @object[field].blank?
|
|
69
|
-
|
|
70
|
-
if @object.respond_to?(:key?)
|
|
71
|
-
if @object.key?(field)
|
|
72
|
-
value = @object[field]
|
|
73
|
-
elsif @object.key?(field.to_s)
|
|
74
|
-
# invalid string key, needs fix
|
|
75
|
-
value = @object[field] = @object.delete(field.to_s)
|
|
76
|
-
end
|
|
77
|
-
else
|
|
78
|
-
value = @object[field]
|
|
79
|
-
end
|
|
26
|
+
# returns new schema with only specified keys
|
|
27
|
+
def only *keys
|
|
28
|
+
keys = keys.map(&:to_sym)
|
|
29
|
+
filtered = rules.select { |k, _| keys.include?(k) }
|
|
30
|
+
self.class.new(nil, define: Define.new(filtered))
|
|
31
|
+
end
|
|
80
32
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
33
|
+
# returns new schema without specified keys
|
|
34
|
+
def except *keys
|
|
35
|
+
keys = keys.map(&:to_sym)
|
|
36
|
+
filtered = rules.reject { |k, _| keys.include?(k) }
|
|
37
|
+
self.class.new(nil, define: Define.new(filtered))
|
|
38
|
+
end
|
|
86
39
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
40
|
+
# validates any instance object with hash variable interface
|
|
41
|
+
# it also coerces values
|
|
42
|
+
def validate object, options = nil
|
|
43
|
+
@object = object
|
|
44
|
+
@errors = {}
|
|
45
|
+
options ||= {}
|
|
91
46
|
|
|
92
|
-
|
|
47
|
+
strip_undefined_keys! if options[:strict] && object.is_a?(Hash)
|
|
93
48
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
49
|
+
@schema.rules.each do |field, raw_opts|
|
|
50
|
+
field = field.to_sym
|
|
51
|
+
opts = resolve_opts(raw_opts)
|
|
97
52
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
value = nil if value.to_s == ''
|
|
53
|
+
apply_default field, opts
|
|
54
|
+
value = read_value field
|
|
101
55
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
56
|
+
value =
|
|
57
|
+
if opts[:array]
|
|
58
|
+
validate_array field, value, opts
|
|
59
|
+
else
|
|
60
|
+
validate_scalar field, value, opts
|
|
106
61
|
end
|
|
107
62
|
|
|
108
|
-
value = check_filed_value field, value, opts
|
|
109
|
-
|
|
110
|
-
add_required_error field, value, opts
|
|
111
|
-
end
|
|
112
|
-
|
|
113
63
|
# present empty string values as nil
|
|
114
|
-
@object[field] = value
|
|
64
|
+
@object[field] = blank?(value) ? nil : value
|
|
115
65
|
end
|
|
116
66
|
|
|
117
|
-
if @errors.
|
|
67
|
+
if @errors.any? && block_given?
|
|
118
68
|
@errors.each { |k, v| yield(k, v) }
|
|
119
69
|
end
|
|
120
70
|
|
|
@@ -122,29 +72,24 @@ module Typero
|
|
|
122
72
|
end
|
|
123
73
|
|
|
124
74
|
def valid? object
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
127
81
|
end
|
|
128
82
|
|
|
129
83
|
# returns field, db_type, db_opts
|
|
130
84
|
def db_schema
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
# add array true to field it ont defined in schema
|
|
136
|
-
schema_opts = @schema.rules[field]
|
|
137
|
-
opts[:array] = true if schema_opts[:array]
|
|
138
|
-
|
|
139
|
-
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]
|
|
140
89
|
end
|
|
141
|
-
|
|
142
|
-
out += @schema.db_rules
|
|
143
|
-
|
|
144
|
-
out
|
|
145
90
|
end
|
|
146
91
|
|
|
147
|
-
# iterate
|
|
92
|
+
# iterate through all the rules via block interface
|
|
148
93
|
# schema.rules do |field, opts|
|
|
149
94
|
# schema.rules(:url) do |field, opts|
|
|
150
95
|
def rules filter = nil, &block
|
|
@@ -159,7 +104,92 @@ module Typero
|
|
|
159
104
|
|
|
160
105
|
private
|
|
161
106
|
|
|
162
|
-
#
|
|
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
|
|
112
|
+
|
|
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)
|
|
118
|
+
end
|
|
119
|
+
opts
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def apply_default field, opts
|
|
123
|
+
if !opts[:default].nil? && @object[field].to_s.blank?
|
|
124
|
+
@object[field] = opts[:default]
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
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
|
|
139
|
+
end
|
|
140
|
+
|
|
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
|
|
179
|
+
check.get
|
|
180
|
+
rescue TypeError => e
|
|
181
|
+
if e.message[0] == '{'
|
|
182
|
+
JSON.parse(e.message).each do |key, msg|
|
|
183
|
+
add_error [field, key].join('.'), msg, opts
|
|
184
|
+
end
|
|
185
|
+
else
|
|
186
|
+
add_error field, e.message, opts
|
|
187
|
+
end
|
|
188
|
+
rescue JSON::ParserError
|
|
189
|
+
add_error field, e.message, opts
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# adds error to hash, prefixing with field name if message starts lowercase
|
|
163
193
|
def add_error field, msg, opts
|
|
164
194
|
if @errors[field]
|
|
165
195
|
@errors[field] += ", %s" % msg
|
|
@@ -173,28 +203,14 @@ module Typero
|
|
|
173
203
|
end
|
|
174
204
|
end
|
|
175
205
|
|
|
176
|
-
def safe_type type
|
|
177
|
-
type.to_s.gsub(/[^\w]/, "").classify
|
|
178
|
-
end
|
|
179
|
-
|
|
180
206
|
def add_required_error field, value, opts
|
|
181
207
|
return unless opts[:required] && value.nil?
|
|
182
208
|
msg = opts[:required].class == TrueClass ? "is required" : opts[:required]
|
|
183
209
|
add_error field, msg, opts
|
|
184
210
|
end
|
|
185
211
|
|
|
186
|
-
def
|
|
187
|
-
|
|
188
|
-
check = klass.constantize.new value, opts
|
|
189
|
-
check.get
|
|
190
|
-
rescue TypeError => e
|
|
191
|
-
if e.message[0] == '{'
|
|
192
|
-
for key, msg in JSON.parse(e.message)
|
|
193
|
-
add_error [field, key].join('.'), msg, opts
|
|
194
|
-
end
|
|
195
|
-
else
|
|
196
|
-
add_error field, e.message, opts
|
|
197
|
-
end
|
|
212
|
+
def blank? value
|
|
213
|
+
value.to_s.sub(/\s+/, '') == ''
|
|
198
214
|
end
|
|
199
215
|
end
|
|
200
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
|
data/lib/typero/type/type.rb
CHANGED
|
@@ -5,12 +5,12 @@ module Typero
|
|
|
5
5
|
ERRORS = {
|
|
6
6
|
en: {
|
|
7
7
|
# errors shared between various types
|
|
8
|
-
min_length_error: 'min
|
|
9
|
-
max_length_error: 'max
|
|
8
|
+
min_length_error: 'min length is %s, you have %s',
|
|
9
|
+
max_length_error: 'max length is %s, you have %s',
|
|
10
10
|
min_value_error: 'min is %s, got %s',
|
|
11
11
|
max_value_error: 'max is %s, got %s',
|
|
12
12
|
unallowed_characters_error: 'is having unallowed characters',
|
|
13
|
-
not_in_range: 'Value
|
|
13
|
+
not_in_range: 'Value is not in allowed range (%s)'
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -23,6 +23,8 @@ module Typero
|
|
|
23
23
|
:default,
|
|
24
24
|
:description,
|
|
25
25
|
:delimiter,
|
|
26
|
+
:duplicates,
|
|
27
|
+
:index,
|
|
26
28
|
:max_count,
|
|
27
29
|
:meta,
|
|
28
30
|
:min_count,
|
|
@@ -80,7 +82,7 @@ module Typero
|
|
|
80
82
|
###
|
|
81
83
|
|
|
82
84
|
def initialize value, opts={}, &block
|
|
83
|
-
value = value.strip
|
|
85
|
+
value = value.strip if value.is_a?(String)
|
|
84
86
|
|
|
85
87
|
opts.keys.each {|key| self.class.allowed_opt?(key) }
|
|
86
88
|
|
|
@@ -101,7 +103,7 @@ module Typero
|
|
|
101
103
|
if value.nil?
|
|
102
104
|
opts[:default].nil? ? default : opts[:default]
|
|
103
105
|
else
|
|
104
|
-
|
|
106
|
+
coerce
|
|
105
107
|
|
|
106
108
|
if opts[:values] && !opts[:values].map(&:to_s).include?(@value.to_s)
|
|
107
109
|
error_for(:not_in_range, opts[:values].join(', '))
|
|
@@ -6,8 +6,11 @@ require_relative './float_type'
|
|
|
6
6
|
|
|
7
7
|
class Typero::CurrencyType < Typero::FloatType
|
|
8
8
|
|
|
9
|
-
def
|
|
9
|
+
def coerce
|
|
10
10
|
value { |data| data.to_f.round(2) }
|
|
11
|
+
|
|
12
|
+
error_for(:min_value_error, opts[:min], value) if opts[:min] && value < opts[:min]
|
|
13
|
+
error_for(:max_value_error, opts[:max], value) if opts[:max] && value > opts[:max]
|
|
11
14
|
end
|
|
12
15
|
|
|
13
16
|
def db_schema
|
|
@@ -2,12 +2,17 @@ class Typero::DateType < Typero::Type
|
|
|
2
2
|
opts :min, 'Smallest date-time allowed'
|
|
3
3
|
opts :max, 'Maximal date-time allowed'
|
|
4
4
|
|
|
5
|
+
error :en, :invalid_date, 'is not a valid date'
|
|
5
6
|
error :en, :min_date, 'Minimal allowed date is %s'
|
|
6
7
|
error :en, :max_date, 'Maximal allowed date is %s'
|
|
7
8
|
|
|
8
|
-
def
|
|
9
|
+
def coerce
|
|
9
10
|
unless [Date].include?(value.class)
|
|
10
|
-
|
|
11
|
+
begin
|
|
12
|
+
value { |data| DateTime.parse(data.to_s) }
|
|
13
|
+
rescue Date::Error, ArgumentError
|
|
14
|
+
error_for(:invalid_date)
|
|
15
|
+
end
|
|
11
16
|
end
|
|
12
17
|
|
|
13
18
|
value { |data| DateTime.new(data.year, data.month, data.day) }
|
|
@@ -24,12 +29,12 @@ class Typero::DateType < Typero::Type
|
|
|
24
29
|
def check_date_min_max
|
|
25
30
|
if min = opts[:min]
|
|
26
31
|
min = DateTime.parse(min)
|
|
27
|
-
error_for(:min_date, min)
|
|
32
|
+
error_for(:min_date, min) if min > value
|
|
28
33
|
end
|
|
29
34
|
|
|
30
35
|
if max = opts[:max]
|
|
31
36
|
max = DateTime.parse(max)
|
|
32
|
-
error_for(:max_date, max)
|
|
37
|
+
error_for(:max_date, max) if value > max
|
|
33
38
|
end
|
|
34
39
|
|
|
35
40
|
value
|
|
@@ -4,16 +4,22 @@ class Typero::DatetimeType < Typero::DateType
|
|
|
4
4
|
opts :min, 'Smallest date allowed'
|
|
5
5
|
opts :max, 'Maximal date allowed'
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
error :en, :invalid_datetime, 'is not a valid datetime'
|
|
8
|
+
|
|
9
|
+
def coerce
|
|
8
10
|
unless [Time, DateTime].include?(value.class)
|
|
9
|
-
|
|
11
|
+
begin
|
|
12
|
+
value { |data| DateTime.parse(data.to_s) }
|
|
13
|
+
rescue Date::Error, ArgumentError
|
|
14
|
+
error_for(:invalid_datetime)
|
|
15
|
+
end
|
|
10
16
|
end
|
|
11
17
|
|
|
12
18
|
check_date_min_max
|
|
13
19
|
end
|
|
14
20
|
|
|
15
21
|
def db_schema
|
|
16
|
-
[:
|
|
22
|
+
[:timestamp]
|
|
17
23
|
end
|
|
18
24
|
end
|
|
19
25
|
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
class Typero::FloatType < Typero::Type
|
|
2
2
|
opts :min, 'Minimum value'
|
|
3
|
-
opts :max, '
|
|
3
|
+
opts :max, 'Maximum value'
|
|
4
4
|
opts :round, 'Round to (decimal spaces)'
|
|
5
5
|
|
|
6
|
-
def
|
|
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
|
-
error_for(:
|
|
15
|
-
error_for(:
|
|
13
|
+
error_for(:min_value_error, opts[:min], value) if opts[:min] && value < opts[:min]
|
|
14
|
+
error_for(:max_value_error, opts[:max], value) if opts[:max] && value > opts[:max]
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
def db_schema
|
|
19
|
-
|
|
20
|
-
opts[:null] = false if opts[:required]
|
|
21
|
-
[:float, opts]
|
|
18
|
+
[:float, {}]
|
|
22
19
|
end
|
|
23
20
|
end
|
|
24
|
-
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
class Typero::HashType < Typero::Type
|
|
2
2
|
error :en, :not_hash_type_error, 'value is not hash type'
|
|
3
3
|
|
|
4
|
-
def
|
|
4
|
+
def coerce
|
|
5
5
|
if value.is_a?(String) && value[0,1] == '{'
|
|
6
|
-
@value = JSON.
|
|
6
|
+
@value = JSON.parse(value)
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
@value ||= {}
|
|
10
|
-
@value.delete_if {|_, v| v.empty? }
|
|
10
|
+
@value.delete_if {|_, v| v.respond_to?(:empty?) && v.empty? }
|
|
11
11
|
|
|
12
12
|
error_for(:not_hash_type_error) unless @value.respond_to?(:keys) && @value.respond_to?(:values)
|
|
13
13
|
|
|
@@ -6,11 +6,13 @@ class Typero::ImageType < Typero::Type
|
|
|
6
6
|
|
|
7
7
|
opts :strict, 'Force image to have known extension (%s)' % FORMATS.join(', ')
|
|
8
8
|
|
|
9
|
-
def
|
|
9
|
+
def coerce
|
|
10
10
|
error_for(:image_not_starting_error) unless value =~ /^https?:\/\/./
|
|
11
11
|
|
|
12
12
|
if opts[:strict]
|
|
13
|
-
|
|
13
|
+
# strip query string and fragment before checking extension
|
|
14
|
+
path = value.split('?').first.split('#').first
|
|
15
|
+
ext = path.split('.').last.downcase
|
|
14
16
|
error_for(:image_not_image_format) unless FORMATS.include?(ext)
|
|
15
17
|
end
|
|
16
18
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class Typero::ModelType < Typero::Type
|
|
2
|
-
def
|
|
2
|
+
def coerce
|
|
3
3
|
value(&:to_h)
|
|
4
4
|
|
|
5
5
|
errors = {}
|
|
@@ -10,7 +10,7 @@ class Typero::ModelType < Typero::Type
|
|
|
10
10
|
errors[field] = error
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
@value.delete_if {|_, v| v.empty? }
|
|
13
|
+
@value.delete_if {|_, v| v.respond_to?(:empty?) && v.empty? }
|
|
14
14
|
|
|
15
15
|
raise TypeError.new errors.to_json if errors.keys.first
|
|
16
16
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
class Typero::OibType < Typero::Type
|
|
2
2
|
error :en, :not_an_oib_error, 'not in an OIB format'
|
|
3
3
|
|
|
4
|
-
def
|
|
4
|
+
def coerce
|
|
5
5
|
value do |data|
|
|
6
|
-
check?(data) ? data.
|
|
6
|
+
check?(data) ? data.to_s : error_for(:not_an_oib_error)
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
end
|
|
7
|
+
def coerce
|
|
8
|
+
if value.is_a?(String) && !value.include?('POINT')
|
|
9
|
+
coords = extract_coords(value)
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
|
|
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,15 +1,15 @@
|
|
|
1
|
-
# Same as point, but we keep data as a float
|
|
1
|
+
# Same as point, but we keep data as a float array [lat, lon]
|
|
2
2
|
|
|
3
3
|
class Typero::SimplePointType < Typero::Type
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
value { point }
|
|
9
|
-
end
|
|
4
|
+
include Typero::GeoExtract
|
|
5
|
+
|
|
6
|
+
def coerce
|
|
7
|
+
coords = extract_coords(value)
|
|
10
8
|
|
|
11
|
-
if
|
|
12
|
-
value {
|
|
9
|
+
if coords
|
|
10
|
+
value { coords }
|
|
11
|
+
else
|
|
12
|
+
error_for(:unallowed_characters_error)
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -17,4 +17,3 @@ class Typero::SimplePointType < Typero::Type
|
|
|
17
17
|
[:float, { array: true }]
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
|
-
|
|
@@ -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
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
class Typero::StringType < Typero::Type
|
|
2
|
-
opts :min, '
|
|
3
|
-
opts :max, '
|
|
2
|
+
opts :min, 'Minimum string length'
|
|
3
|
+
opts :max, 'Maximum string length'
|
|
4
4
|
opts :downcase, 'is the string in downcase?'
|
|
5
5
|
|
|
6
|
-
def
|
|
6
|
+
def coerce
|
|
7
7
|
value(&:to_s)
|
|
8
8
|
value(&:downcase) if opts[:downcase]
|
|
9
9
|
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
require_relative 'string_type'
|
|
2
2
|
|
|
3
3
|
class Typero::TextType < Typero::StringType
|
|
4
|
-
opts :min, '
|
|
5
|
-
opts :max, '
|
|
4
|
+
opts :min, 'Minimum string length'
|
|
5
|
+
opts :max, 'Maximum string length'
|
|
6
|
+
|
|
7
|
+
def coerce
|
|
8
|
+
value(&:to_s)
|
|
9
|
+
value(&:downcase) if opts[:downcase]
|
|
10
|
+
|
|
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
|
|
6
14
|
|
|
7
15
|
def db_schema
|
|
8
16
|
[:text, {}]
|
|
@@ -1,9 +1,8 @@
|
|
|
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
|
|
5
|
-
|
|
6
|
-
error_for(:url_not_starting_error) unless parts[1]
|
|
4
|
+
def coerce
|
|
5
|
+
error_for(:url_not_starting_error) unless value =~ /^https?:\/\//
|
|
7
6
|
end
|
|
8
7
|
|
|
9
8
|
def db_schema
|
|
@@ -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/typero.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# module quick access to other types
|
|
2
|
+
|
|
1
3
|
module Typero
|
|
2
4
|
extend self
|
|
3
5
|
|
|
@@ -50,7 +52,7 @@ module Typero
|
|
|
50
52
|
# Typero.schema type: :model
|
|
51
53
|
out = []
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
Schema::SCHEMA_STORE.values.each do |schema|
|
|
54
56
|
if schema.opts[name.keys.first] == name.values.first
|
|
55
57
|
out.push schema.klass
|
|
56
58
|
end
|
|
@@ -64,6 +66,17 @@ module Typero
|
|
|
64
66
|
end
|
|
65
67
|
end
|
|
66
68
|
|
|
69
|
+
# same as schema but returns nil if not found
|
|
70
|
+
def schema? name
|
|
71
|
+
klass = name.to_s.classify if name
|
|
72
|
+
Typero::Schema::SCHEMA_STORE[klass] if klass
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# get array of database fields, ruby Sequel compatibile
|
|
76
|
+
def db_schema name
|
|
77
|
+
Typero.schema(name).db_schema
|
|
78
|
+
end
|
|
79
|
+
|
|
67
80
|
def defined? name
|
|
68
81
|
Typero::Type.load name
|
|
69
82
|
true
|
data/lib/typero.rb
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
# base libs
|
|
4
4
|
require_relative 'typero/typero'
|
|
5
5
|
require_relative 'typero/schema'
|
|
6
|
-
require_relative 'typero/
|
|
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,29 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: typero
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dino Reic
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
-
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: fast_blank
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0'
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '0'
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
27
12
|
description: Simple and fast ruby type system. Enforce types as Array, Email, Boolean
|
|
28
13
|
for ruby class instances
|
|
29
14
|
email: reic.dino@gmail.com
|
|
@@ -34,8 +19,9 @@ files:
|
|
|
34
19
|
- "./.version"
|
|
35
20
|
- "./lib/adapters/sequel.rb"
|
|
36
21
|
- "./lib/typero.rb"
|
|
37
|
-
- "./lib/typero/
|
|
22
|
+
- "./lib/typero/define.rb"
|
|
38
23
|
- "./lib/typero/schema.rb"
|
|
24
|
+
- "./lib/typero/type/geo_extract.rb"
|
|
39
25
|
- "./lib/typero/type/type.rb"
|
|
40
26
|
- "./lib/typero/type/types/boolean_type.rb"
|
|
41
27
|
- "./lib/typero/type/types/currency_type.rb"
|
|
@@ -50,19 +36,21 @@ files:
|
|
|
50
36
|
- "./lib/typero/type/types/locale_type.rb"
|
|
51
37
|
- "./lib/typero/type/types/model_type.rb"
|
|
52
38
|
- "./lib/typero/type/types/oib_type.rb"
|
|
39
|
+
- "./lib/typero/type/types/phone_type.rb"
|
|
53
40
|
- "./lib/typero/type/types/point_type.rb"
|
|
54
41
|
- "./lib/typero/type/types/simple_point_type.rb"
|
|
42
|
+
- "./lib/typero/type/types/slug_type.rb"
|
|
55
43
|
- "./lib/typero/type/types/string_type.rb"
|
|
56
44
|
- "./lib/typero/type/types/text_type.rb"
|
|
57
45
|
- "./lib/typero/type/types/time_type.rb"
|
|
58
46
|
- "./lib/typero/type/types/timezone_type.rb"
|
|
59
47
|
- "./lib/typero/type/types/url_type.rb"
|
|
48
|
+
- "./lib/typero/type/types/uuid_type.rb"
|
|
60
49
|
- "./lib/typero/typero.rb"
|
|
61
50
|
homepage: https://github.com/dux/typero
|
|
62
51
|
licenses:
|
|
63
52
|
- MIT
|
|
64
53
|
metadata: {}
|
|
65
|
-
post_install_message:
|
|
66
54
|
rdoc_options: []
|
|
67
55
|
require_paths:
|
|
68
56
|
- lib
|
|
@@ -77,8 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
77
65
|
- !ruby/object:Gem::Version
|
|
78
66
|
version: '0'
|
|
79
67
|
requirements: []
|
|
80
|
-
rubygems_version:
|
|
81
|
-
signing_key:
|
|
68
|
+
rubygems_version: 4.0.8
|
|
82
69
|
specification_version: 4
|
|
83
70
|
summary: Ruby type system
|
|
84
71
|
test_files: []
|