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.
- checksums.yaml +4 -4
- data/.version +1 -1
- data/lib/adapters/sequel.rb +10 -27
- data/lib/typero.rb +3 -0
- data/lib/typero/params.rb +57 -12
- data/lib/typero/schema.rb +165 -0
- data/lib/typero/type/type.rb +68 -10
- data/lib/typero/type/types/boolean_type.rb +26 -0
- data/lib/typero/type/types/currency_type.rb +21 -0
- data/lib/typero/type/types/date_type.rb +38 -0
- data/lib/typero/type/types/datetime_type.rb +19 -0
- data/lib/typero/type/types/email_type.rb +20 -0
- data/lib/typero/type/types/float_type.rb +24 -0
- data/lib/typero/type/types/hash_type.rb +31 -0
- data/lib/typero/type/types/{image.rb → image_type.rb} +4 -10
- data/lib/typero/type/types/integer_type.rb +16 -0
- data/lib/typero/type/types/label_type.rb +20 -0
- data/lib/typero/type/types/locale_type.rb +13 -0
- data/lib/typero/type/types/model_type.rb +23 -0
- data/lib/typero/type/types/{oib.rb → oib_type.rb} +7 -10
- data/lib/typero/type/types/point_type.rb +25 -0
- data/lib/typero/type/types/string_type.rb +23 -0
- data/lib/typero/type/types/{text.rb → text_type.rb} +3 -5
- data/lib/typero/type/types/time_type.rb +5 -0
- data/lib/typero/type/types/timezone_type.rb +15 -0
- data/lib/typero/type/types/url_type.rb +13 -0
- data/lib/typero/typero.rb +72 -141
- metadata +26 -21
- data/lib/typero/type/types/boolean.rb +0 -16
- data/lib/typero/type/types/currency.rb +0 -19
- data/lib/typero/type/types/date.rb +0 -10
- data/lib/typero/type/types/datetime.rb +0 -10
- data/lib/typero/type/types/email.rb +0 -21
- data/lib/typero/type/types/float.rb +0 -21
- data/lib/typero/type/types/hash.rb +0 -22
- data/lib/typero/type/types/integer.rb +0 -21
- data/lib/typero/type/types/label.rb +0 -15
- data/lib/typero/type/types/point.rb +0 -29
- data/lib/typero/type/types/string.rb +0 -23
- data/lib/typero/type/types/url.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f8d03e15d714e38774a8f817aaf02c710e3ddca8b65f9d413aefb01678bd31b
|
4
|
+
data.tar.gz: ebd4cb0d01d43f7a93c3e2575daa832ba960dd8ece2a284676c763f4c43ef30c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a8dc1fdac410f59695f486860f078359a8344492335759b2df69f2fa382e3366000d70d994c4c05bdfa613c6c9dc207b66fefd2e4731f97dca97f94eda94b85
|
7
|
+
data.tar.gz: e7c6581d6fc6558687b023505564c669f59ce8e5dc0a298392f83fab92e1f47b27704a0b7644143161c6a264d601c2ac2a382c8cc0bdfb54f97ea52789846891
|
data/.version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.9.4
|
data/lib/adapters/sequel.rb
CHANGED
@@ -3,60 +3,43 @@
|
|
3
3
|
module Sequel::Plugins::TyperoAttributes
|
4
4
|
module ClassMethods
|
5
5
|
def typero
|
6
|
-
Typero.
|
6
|
+
Typero.schema self
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
10
|
module InstanceMethods
|
11
11
|
# calling typero! on any object will validate all fields
|
12
|
-
def
|
13
|
-
|
12
|
+
def validate
|
13
|
+
super
|
14
14
|
|
15
|
-
|
15
|
+
schema = Typero.schema(self.class) || return
|
16
16
|
|
17
|
-
|
17
|
+
schema.validate(self) do |name, err|
|
18
18
|
errors.add(name, err) unless (errors.on(name) || []).include?(err)
|
19
19
|
end
|
20
20
|
|
21
21
|
# this are rules unique to database, so we check them here
|
22
|
-
|
23
|
-
self[field] ||= {} if rule[:type] == 'hash'
|
24
|
-
|
22
|
+
schema.rules.each do |field, rule|
|
25
23
|
# check uniqe fields
|
26
|
-
if rule
|
24
|
+
if unique = rule.dig(:meta, :unique)
|
27
25
|
id = self[:id] || 0
|
28
26
|
value = self[field]
|
29
27
|
|
30
28
|
# we only check if field is changed
|
31
29
|
if value.present? && column_changed?(field) && self.class.xwhere('LOWER(%s)=LOWER(?) and id<>?' % field, value, id).first
|
32
|
-
error =
|
30
|
+
error = unique.class == TrueClass ? %[Value "#{value}" for field "#{field}" has been already used, please chose another value.] : unique
|
33
31
|
errors.add(field, error) unless (errors.on(field) || []).include?(error)
|
34
32
|
end
|
35
33
|
end
|
36
34
|
|
37
35
|
# check protected fields
|
38
|
-
if rule
|
36
|
+
if prot = rule.dig(:meta, :protected) && self[:id]
|
39
37
|
if column_changed?(field)
|
40
|
-
error =
|
38
|
+
error = prot.class == TrueClass ? "value once defined can't be overwritten." : prot
|
41
39
|
errors.add(field, error) unless (errors.on(field) || []).include?(error)
|
42
40
|
end
|
43
41
|
end
|
44
42
|
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
43
|
end
|
61
44
|
end
|
62
45
|
|
data/lib/typero.rb
CHANGED
data/lib/typero/params.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
# Base class for schema validation
|
2
2
|
|
3
|
-
|
3
|
+
module Typero
|
4
4
|
class Params
|
5
|
-
|
6
|
-
|
7
|
-
attr_reader :rules, :db_rules
|
5
|
+
attr_reader :db_rules
|
8
6
|
|
9
7
|
def initialize &block
|
10
8
|
@db_rules = []
|
@@ -12,10 +10,14 @@ class Typero
|
|
12
10
|
instance_exec &block
|
13
11
|
end
|
14
12
|
|
13
|
+
def rules
|
14
|
+
@rules.dup
|
15
|
+
end
|
16
|
+
|
15
17
|
private
|
16
18
|
|
17
19
|
# used in dsl to define schema field options
|
18
|
-
def set field, *args
|
20
|
+
def set field, *args, &block
|
19
21
|
raise "Field name not given (Typero)" unless field
|
20
22
|
|
21
23
|
if args.first.is_a?(Hash)
|
@@ -25,17 +27,31 @@ class Typero
|
|
25
27
|
opts[:type] ||= args[0]
|
26
28
|
end
|
27
29
|
|
28
|
-
opts[:type]
|
29
|
-
opts[:required] = true unless opts[:required].is_a?(FalseClass) || opts[:req].is_a?(FalseClass)
|
30
|
+
opts[:type] = :string if opts[:type].nil?
|
30
31
|
|
31
32
|
field = field.to_s
|
32
33
|
|
34
|
+
if field.include?('!')
|
35
|
+
if block
|
36
|
+
field = field.sub('!', '')
|
37
|
+
@block_type = field.to_sym
|
38
|
+
instance_exec &block
|
39
|
+
@block_type = nil
|
40
|
+
return
|
41
|
+
else
|
42
|
+
raise ArgumentError.new 'If you use ! you have to provide a block'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
33
46
|
# name? - opional name
|
34
47
|
if field.include?('?')
|
35
48
|
field = field.sub('?', '')
|
36
49
|
opts[:required] = false
|
37
50
|
end
|
38
51
|
|
52
|
+
opts[:required] = opts.delete(:req) unless opts[:req].nil?
|
53
|
+
opts[:required] = true if opts[:required].nil?
|
54
|
+
|
39
55
|
# array that allows duplicates
|
40
56
|
if opts[:type].is_a?(Array)
|
41
57
|
opts[:type] = opts[:type].first
|
@@ -48,20 +64,49 @@ class Typero
|
|
48
64
|
opts[:array] = true
|
49
65
|
end
|
50
66
|
|
67
|
+
opts[:type] = @block_type if @block_type
|
68
|
+
|
69
|
+
# Boolean
|
70
|
+
if opts[:type].is_a?(TrueClass) || opts[:type] == :true
|
71
|
+
opts[:required] = false
|
72
|
+
opts[:default] = true
|
73
|
+
opts[:type] = :boolean
|
74
|
+
elsif opts[:type].is_a?(FalseClass) || opts[:type] == :false || opts[:type] == :boolean
|
75
|
+
opts[:required] = false if opts[:required].nil?
|
76
|
+
opts[:default] = false if opts[:default].nil?
|
77
|
+
opts[:type] = :boolean
|
78
|
+
end
|
79
|
+
|
80
|
+
# model / schema
|
81
|
+
if opts[:type].class.ancestors.include?(Typero::Schema)
|
82
|
+
opts[:model] = opts.delete(:type)
|
83
|
+
end
|
84
|
+
opts[:model] = opts.delete(:schema) if opts[:schema]
|
85
|
+
opts[:type] = :model if opts[:model]
|
86
|
+
|
87
|
+
if block_given?
|
88
|
+
opts[:type] = :model
|
89
|
+
opts[:model] = Typero.schema &block
|
90
|
+
end
|
91
|
+
|
51
92
|
opts[:type] ||= 'string'
|
52
|
-
opts[:type] = opts[:type].to_s.downcase
|
93
|
+
opts[:type] = opts[:type].to_s.downcase.to_sym
|
53
94
|
|
54
95
|
opts[:description] = opts.delete(:desc) unless opts[:desc].nil?
|
55
96
|
|
56
97
|
# chek alloed params, all optional should go in meta
|
57
|
-
|
58
|
-
|
98
|
+
opts.keys.each do |key|
|
99
|
+
type = Typero::Type.load opts[:type]
|
100
|
+
type.allowed_opt?(key) {|err| raise ArgumentError, err }
|
101
|
+
end
|
59
102
|
|
60
103
|
field = field.to_sym
|
61
104
|
|
62
105
|
db :add_index, field if opts.delete(:index)
|
63
106
|
|
64
|
-
|
107
|
+
# trigger error if type not found
|
108
|
+
Typero::Type.load opts[:type]
|
109
|
+
|
65
110
|
@rules[field] = opts
|
66
111
|
end
|
67
112
|
|
@@ -78,7 +123,7 @@ class Typero
|
|
78
123
|
# set :emails, Array[:email]
|
79
124
|
# email Array[:emails]
|
80
125
|
def method_missing field, *args, &block
|
81
|
-
set field, *args
|
126
|
+
set field, *args, &block
|
82
127
|
end
|
83
128
|
end
|
84
129
|
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module Typero
|
2
|
+
class Schema
|
3
|
+
SCHEMAS = {}
|
4
|
+
TYPES = {}
|
5
|
+
|
6
|
+
# accepts dsl block to
|
7
|
+
def initialize &block
|
8
|
+
raise "Params not defined" unless block_given?
|
9
|
+
@schema = Params.new &block
|
10
|
+
end
|
11
|
+
|
12
|
+
# validates any instance object with hash variable interface
|
13
|
+
# it also coarces values
|
14
|
+
def validate object, options=nil
|
15
|
+
@options = options || {}
|
16
|
+
@object = object
|
17
|
+
@errors = {}
|
18
|
+
|
19
|
+
# remove undefined keys if Hash provided
|
20
|
+
if @options[:strict] && object.is_a?(Hash)
|
21
|
+
undefined = object.keys.map(&:to_s) - @schema.rules.keys.map(&:to_s)
|
22
|
+
object.delete_if { |k, _| undefined.include?(k.to_s) }
|
23
|
+
end
|
24
|
+
|
25
|
+
@schema.rules.each do |field, opts|
|
26
|
+
# force filed as a symbol
|
27
|
+
field = field.to_sym
|
28
|
+
|
29
|
+
for k in opts.keys
|
30
|
+
opts[k] = @object.instance_exec(&opts[k]) if opts[k].is_a?(Proc)
|
31
|
+
end
|
32
|
+
|
33
|
+
# set value to default if value is blank and default given
|
34
|
+
@object[field] = opts[:default] if opts[:default] && @object[field].blank?
|
35
|
+
|
36
|
+
if @object.respond_to?(:key?)
|
37
|
+
if @object.key?(field)
|
38
|
+
value = @object[field]
|
39
|
+
elsif @object.key?(field.to_s)
|
40
|
+
# invalid string key, needs fix
|
41
|
+
value = @object[field] = @object.delete(field.to_s)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
value = @object[field]
|
45
|
+
end
|
46
|
+
|
47
|
+
if opts[:array]
|
48
|
+
unless value.respond_to?(:each)
|
49
|
+
opts[:delimiter] ||= /\s*[,\n]\s*/
|
50
|
+
value = value.to_s.split(opts[:delimiter])
|
51
|
+
end
|
52
|
+
|
53
|
+
value = value
|
54
|
+
.flatten
|
55
|
+
.map { |el| el.to_s == '' ? nil : check_filed_value(field, el, opts) }
|
56
|
+
.compact
|
57
|
+
|
58
|
+
value = Set.new(value).to_a unless opts[:duplicates]
|
59
|
+
|
60
|
+
opts[:max_count] ||= 100
|
61
|
+
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]
|
62
|
+
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]
|
63
|
+
|
64
|
+
add_required_error field, value.first, opts
|
65
|
+
else
|
66
|
+
value = nil if value.to_s == ''
|
67
|
+
|
68
|
+
# if value is not list of allowed values, raise error
|
69
|
+
allowed = opts[:allow] || opts[:allowed] || opts[:values]
|
70
|
+
if value && allowed && !allowed.include?(value)
|
71
|
+
add_error field, 'Value "%s" is not allowed' % value, opts
|
72
|
+
end
|
73
|
+
|
74
|
+
value = check_filed_value field, value, opts
|
75
|
+
add_required_error field, value, opts
|
76
|
+
end
|
77
|
+
|
78
|
+
# present empty string values as nil
|
79
|
+
@object[field] = value.to_s.sub(/\s+/, '') == '' ? nil : value
|
80
|
+
end
|
81
|
+
|
82
|
+
if @errors.keys.length > 0 && block_given?
|
83
|
+
@errors.each { |k, v| yield(k, v) }
|
84
|
+
end
|
85
|
+
|
86
|
+
@errors
|
87
|
+
end
|
88
|
+
|
89
|
+
def valid? object
|
90
|
+
errors = validate object
|
91
|
+
errors.keys.length == 0
|
92
|
+
end
|
93
|
+
|
94
|
+
# returns field, db_type, db_opts
|
95
|
+
def db_schema
|
96
|
+
out = @schema.rules.inject([]) do |total, (field, opts)|
|
97
|
+
# get db filed schema
|
98
|
+
type, opts = Typero::Type.load(opts[:type]).new(nil, opts).db_field
|
99
|
+
|
100
|
+
# add array true to field it ont defined in schema
|
101
|
+
schema_opts = @schema.rules[field]
|
102
|
+
opts[:array] = true if schema_opts[:array]
|
103
|
+
|
104
|
+
total << [type, field, opts]
|
105
|
+
end
|
106
|
+
|
107
|
+
out += @schema.db_rules
|
108
|
+
|
109
|
+
out
|
110
|
+
end
|
111
|
+
|
112
|
+
# iterate trough all the ruels via block interface
|
113
|
+
# schema.rules do |field, opts|
|
114
|
+
# schema.rules(:url) do |field, opts|
|
115
|
+
def rules filter = nil, &block
|
116
|
+
return @schema.rules unless filter
|
117
|
+
out = @schema.rules
|
118
|
+
out = out.select { |k, v| v[:type].to_s == filter.to_s || v[:array_type].to_s == filter.to_s } if filter
|
119
|
+
return out unless block_given?
|
120
|
+
|
121
|
+
out.each { |k, v| yield k, v }
|
122
|
+
end
|
123
|
+
alias :to_h :rules
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
# adds error to array or prefixes with field name
|
128
|
+
def add_error field, msg, opts
|
129
|
+
if @errors[field]
|
130
|
+
@errors[field] += ", %s" % msg
|
131
|
+
else
|
132
|
+
if msg && msg[0, 1].downcase == msg[0, 1]
|
133
|
+
field_name = opts[:name] || field.to_s.sub(/_id$/, "").capitalize
|
134
|
+
msg = "%s %s" % [field_name, msg]
|
135
|
+
end
|
136
|
+
|
137
|
+
@errors[field] = msg
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def safe_type type
|
142
|
+
type.to_s.gsub(/[^\w]/, "").classify
|
143
|
+
end
|
144
|
+
|
145
|
+
def add_required_error field, value, opts
|
146
|
+
return unless opts[:required] && value.nil?
|
147
|
+
msg = opts[:required].class == TrueClass ? "is required" : opts[:required]
|
148
|
+
add_error field, msg, opts
|
149
|
+
end
|
150
|
+
|
151
|
+
def check_filed_value field, value, opts
|
152
|
+
klass = "Typero::%sType" % safe_type(opts[:type])
|
153
|
+
check = klass.constantize.new value, opts
|
154
|
+
check.get
|
155
|
+
rescue TypeError => e
|
156
|
+
if e.message[0] == '{'
|
157
|
+
for key, msg in JSON.parse(e.message)
|
158
|
+
add_error [field, key].join('.'), msg, opts
|
159
|
+
end
|
160
|
+
else
|
161
|
+
add_error field, e.message, opts
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
data/lib/typero/type/type.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Master class
|
2
2
|
|
3
|
-
|
3
|
+
module Typero
|
4
4
|
class Type
|
5
5
|
ERRORS = {
|
6
6
|
en: {
|
@@ -9,14 +9,32 @@ class Typero
|
|
9
9
|
max_length_error: 'max lenght 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
|
-
unallowed_characters_error: 'is having unallowed characters'
|
12
|
+
unallowed_characters_error: 'is having unallowed characters',
|
13
|
+
not_in_range: 'Value in not in allowed range (%s)'
|
13
14
|
}
|
14
15
|
}
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
# default shared allowed opts keys
|
18
|
+
OPTS = {}
|
19
|
+
OPTS_KEYS = [
|
20
|
+
:allow,
|
21
|
+
:allowed,
|
22
|
+
:array,
|
23
|
+
:default,
|
24
|
+
:description,
|
25
|
+
:delimiter,
|
26
|
+
:max_count,
|
27
|
+
:meta,
|
28
|
+
:min_count,
|
29
|
+
:model,
|
30
|
+
:name,
|
31
|
+
:req,
|
32
|
+
:required,
|
33
|
+
:type,
|
34
|
+
:values
|
35
|
+
]
|
36
|
+
|
37
|
+
attr_reader :opts
|
20
38
|
|
21
39
|
class << self
|
22
40
|
def load name
|
@@ -39,24 +57,64 @@ class Typero
|
|
39
57
|
OPTS[self] ||= {}
|
40
58
|
OPTS[self][key] = desc
|
41
59
|
end
|
60
|
+
|
61
|
+
def allowed_opt? name
|
62
|
+
return true if OPTS_KEYS.include?(name)
|
63
|
+
|
64
|
+
OPTS[self] ||= {}
|
65
|
+
return true if OPTS[self][name]
|
66
|
+
|
67
|
+
msg = %[Unallowed param "#{name}" for type "#{to_s}" found. Allowed are "#{OPTS_KEYS.join(', ')}"]
|
68
|
+
msg += %[ + "#{OPTS[self].keys.join(', ')}"] if OPTS[self].keys.first
|
69
|
+
|
70
|
+
block_given? ? yield(msg) : raise(ArgumentError, msg)
|
71
|
+
|
72
|
+
false
|
73
|
+
end
|
42
74
|
end
|
43
75
|
|
44
76
|
###
|
45
77
|
|
46
|
-
def initialize value, opts={}
|
78
|
+
def initialize value, opts={}, &block
|
79
|
+
value = value.strip.rstrip if value.is_a?(String)
|
80
|
+
|
81
|
+
opts.keys.each {|key| self.class.allowed_opt?(key) }
|
82
|
+
|
47
83
|
@value = value
|
48
84
|
@opts = opts
|
85
|
+
@block = block
|
86
|
+
end
|
87
|
+
|
88
|
+
def value &block
|
89
|
+
if block_given?
|
90
|
+
@value = block.call @value
|
91
|
+
else
|
92
|
+
@value
|
93
|
+
end
|
49
94
|
end
|
50
95
|
|
51
|
-
|
52
|
-
|
53
|
-
|
96
|
+
def get
|
97
|
+
if value.nil?
|
98
|
+
opts[:default].nil? ? default : opts[:default]
|
99
|
+
else
|
100
|
+
set
|
101
|
+
error_for(:not_in_range, opts[:values].join(', ')) if opts[:values] && !opts[:values].include?(@value)
|
102
|
+
value
|
103
|
+
end
|
54
104
|
end
|
55
105
|
|
56
106
|
def default
|
57
107
|
nil
|
58
108
|
end
|
59
109
|
|
110
|
+
def db_field
|
111
|
+
out = db_schema
|
112
|
+
out[1] ||= {}
|
113
|
+
out[1][:default] ||= opts[:default] unless opts[:default].nil?
|
114
|
+
out[1][:null] = false if !opts[:array] && opts[:required]
|
115
|
+
out
|
116
|
+
end
|
117
|
+
|
60
118
|
private
|
61
119
|
|
62
120
|
# get error from option or the default one
|