typero 0.3.4 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.version +1 -1
- data/lib/adapters/sequel.rb +69 -0
- data/lib/typero.rb +10 -161
- data/lib/typero/params.rb +84 -0
- data/lib/typero/type/type.rb +82 -0
- data/lib/typero/type/{boolean.rb → types/boolean.rb} +6 -0
- data/lib/typero/type/types/currency.rb +19 -0
- data/lib/typero/type/types/date.rb +10 -0
- data/lib/typero/type/types/datetime.rb +10 -0
- data/lib/typero/type/types/email.rb +21 -0
- data/lib/typero/type/types/float.rb +21 -0
- data/lib/typero/type/types/hash.rb +22 -0
- data/lib/typero/type/types/image.rb +28 -0
- data/lib/typero/type/types/integer.rb +21 -0
- data/lib/typero/type/{label.rb → types/label.rb} +7 -1
- data/lib/typero/type/types/oib.rb +42 -0
- data/lib/typero/type/types/point.rb +29 -0
- data/lib/typero/type/types/string.rb +23 -0
- data/lib/typero/type/types/text.rb +12 -0
- data/lib/typero/type/types/url.rb +18 -0
- data/lib/typero/typero.rb +177 -0
- metadata +26 -19
- data/lib/typero/type.rb +0 -28
- data/lib/typero/type/array.rb +0 -32
- data/lib/typero/type/email.rb +0 -14
- data/lib/typero/type/float.rb +0 -13
- data/lib/typero/type/hash.rb +0 -10
- data/lib/typero/type/integer.rb +0 -11
- data/lib/typero/type/oib.rb +0 -29
- data/lib/typero/type/string.rb +0 -12
- data/lib/typero/type/url.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba656a56b8d2e5d3d977a2973f6de881eefd3257397a4a4ebe908a7abaed70a7
|
4
|
+
data.tar.gz: 8374f340b6af235c274ceaa9aace33f0c462b5a203d667a199fa519c1f56f53e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba14e29595a5d17c3c3d102f49d1234a82e7ea23a7062a4a61c6dc56770d9d4922f1be7ae01663c41382efb6c6c51ed3fb5dfd4c188315ec1a5bf3f43eb699fc
|
7
|
+
data.tar.gz: c2d8b322fac4048067b809bf7944c8ca27bb787d4b590a68eec62169a768a65ddbb089359da73f550a32b59a0405b95629ae4932bf7a3dfb9d5426e2e26d3e7d
|
data/.version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.8.0
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sequel::Plugins::TyperoAttributes
|
4
|
+
module ClassMethods
|
5
|
+
def typero
|
6
|
+
Typero.new self
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
# calling typero! on any object will validate all fields
|
12
|
+
def typero! field_name=nil
|
13
|
+
return unless Typero.defined?(self.class)
|
14
|
+
|
15
|
+
typero = self.class.typero
|
16
|
+
|
17
|
+
typero.validate(self) do |name, err|
|
18
|
+
errors.add(name, err) unless (errors.on(name) || []).include?(err)
|
19
|
+
end
|
20
|
+
|
21
|
+
# this are rules unique to database, so we check them here
|
22
|
+
typero.rules.each do |field, rule|
|
23
|
+
self[field] ||= {} if rule[:type] == 'hash'
|
24
|
+
|
25
|
+
# check uniqe fields
|
26
|
+
if rule[:unique]
|
27
|
+
id = self[:id] || 0
|
28
|
+
value = self[field]
|
29
|
+
|
30
|
+
# we only check if field is changed
|
31
|
+
if value.present? && column_changed?(field) && self.class.xwhere('LOWER(%s)=LOWER(?) and id<>?' % field, value, id).first
|
32
|
+
error = rule[:unique].class == TrueClass ? %[Value '"#{value}"' for #{field} allready exists] : rule[:unique]
|
33
|
+
errors.add(field, error) unless (errors.on(field) || []).include?(error)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# check protected fields
|
38
|
+
if rule[:protected] && self[:id]
|
39
|
+
if column_changed?(field)
|
40
|
+
error = rule[:protected].class == TrueClass ? "value once defined can't be overwritten." : rule[:protected]
|
41
|
+
errors.add(field, error) unless (errors.on(field) || []).include?(error)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# check single field if single field given
|
47
|
+
if field_name
|
48
|
+
raise ArgumentError.new 'Field :%s not found in %s' % [field_name, self] unless self[field_name]
|
49
|
+
return unless errors.on(field_name)
|
50
|
+
|
51
|
+
errors.on(field_name).join(', ')
|
52
|
+
end
|
53
|
+
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
def validate
|
58
|
+
typero!
|
59
|
+
super
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module DatasetMethods
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
Sequel::Model.plugin :typero_attributes
|
69
|
+
|
data/lib/typero.rb
CHANGED
@@ -1,163 +1,12 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
# rules.validate(@object) { |errors| ... }
|
10
|
-
|
11
|
-
class Typero
|
12
|
-
attr_reader :rules
|
13
|
-
|
14
|
-
VERSION = File.read File.expand_path '../.version', File.dirname(__FILE__)
|
15
|
-
|
16
|
-
class << self
|
17
|
-
# validate single value in type
|
18
|
-
def validate value, type, opts={}
|
19
|
-
field = type.to_s.tableize.singularize.to_sym
|
20
|
-
|
21
|
-
# we need to have pointer to hash, so value can be changed (coerced) if needed
|
22
|
-
h = { field => value }
|
23
|
-
|
24
|
-
rule = new
|
25
|
-
rule.set field, type, opts
|
26
|
-
|
27
|
-
if error = rule.validate(h)[field]
|
28
|
-
block_given? ? yield(error) : raise(TypeError.new(error))
|
29
|
-
end
|
30
|
-
|
31
|
-
h[field]
|
32
|
-
end
|
33
|
-
|
34
|
-
# Typero.set(:label, 'Foo bar') -> "foo-bar"
|
35
|
-
def set type, value, opts={}
|
36
|
-
klass = 'Typero::%sType' % type.to_s.gsub(/[^\w]/,'').classify
|
37
|
-
check = klass.constantize.new value, opts
|
38
|
-
check.set
|
39
|
-
end
|
40
|
-
|
41
|
-
def cache
|
42
|
-
@@cache ||= {}
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
###
|
47
|
-
|
48
|
-
# accepts dsl block to
|
49
|
-
def initialize hash={}, &block
|
50
|
-
@rules = {}
|
51
|
-
hash.each { |k, v| set(k, v) }
|
52
|
-
instance_exec &block if block
|
53
|
-
end
|
54
|
-
|
55
|
-
# convert
|
56
|
-
# integer :age
|
57
|
-
# set :age, type: :integer
|
58
|
-
# email :email
|
59
|
-
# set :email, [:emails]
|
60
|
-
# email [:emails]
|
61
|
-
def method_missing name, *args, &block
|
62
|
-
field = args.shift
|
63
|
-
|
64
|
-
if field.class == Array
|
65
|
-
field = field.first
|
66
|
-
name = [name]
|
67
|
-
end
|
68
|
-
|
69
|
-
set field, type=name, *args
|
70
|
-
end
|
71
|
-
|
72
|
-
# coerce opts values
|
73
|
-
def parse_option opts
|
74
|
-
opts[:type] ||= 'string'
|
75
|
-
opts[:req] = opts.delete(:required) unless opts[:required].nil?
|
76
|
-
|
77
|
-
if opts[:type].is_a?(Array)
|
78
|
-
opts[:array_type] = opts[:type][0] if opts[:type][0]
|
79
|
-
opts[:type] = 'array'
|
80
|
-
end
|
81
|
-
|
82
|
-
opts[:type] = opts[:type].to_s.downcase
|
83
|
-
|
84
|
-
allowed_names = [:req, :uniq, :protected, :type, :min, :max, :array_type, :default, :downcase, :desc]
|
85
|
-
opts.keys.each do |key|
|
86
|
-
raise ArgumentError.new('%s is not allowed as typero option' % key) unless allowed_names.index(key)
|
87
|
-
end
|
88
|
-
|
89
|
-
opts
|
90
|
-
end
|
91
|
-
|
92
|
-
# used in dsl to define value
|
93
|
-
def set field, type=String, opts={}
|
94
|
-
klass = '::Typero::%sType' % type.to_s.gsub(/[^\w]/,'').classify
|
95
|
-
klass.constantize
|
96
|
-
|
97
|
-
opts = type.is_a?(Hash) ? type : opts.merge(type: type)
|
98
|
-
|
99
|
-
@rules[field] = parse_option opts
|
100
|
-
end
|
101
|
-
|
102
|
-
def safe_type type
|
103
|
-
type.to_s.gsub(/[^\w]/,'').classify
|
104
|
-
end
|
105
|
-
|
106
|
-
# adds error to array or prefixes with field name
|
107
|
-
def add_error field, msg
|
108
|
-
if @errors[field]
|
109
|
-
@errors[field] += ', %s' % msg
|
110
|
-
else
|
111
|
-
field_name = field.to_s.sub(/_id$/,'').humanize
|
112
|
-
@errors[field] = '%s %s' % [field_name, msg]
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
# validates any instance object or object with hash variable interface
|
117
|
-
# it also coarces values
|
118
|
-
def validate instance
|
119
|
-
@errors = {}
|
120
|
-
|
121
|
-
@rules.each do |field, opts|
|
122
|
-
# set value to default if value is blank and default given
|
123
|
-
instance[field] = opts[:default] if opts[:default] && instance[field].blank?
|
124
|
-
|
125
|
-
# get field value
|
126
|
-
value = instance[field]
|
127
|
-
|
128
|
-
if value.present?
|
129
|
-
klass = 'Typero::%sType' % safe_type(opts[:type])
|
130
|
-
check = klass.constantize.new value, opts
|
131
|
-
check.value = check.default if check.value.nil?
|
132
|
-
|
133
|
-
unless check.value.nil?
|
134
|
-
begin
|
135
|
-
check.set
|
136
|
-
check.validate
|
137
|
-
instance[field] = check.value
|
138
|
-
rescue TypeError => e
|
139
|
-
add_error field, e.message
|
140
|
-
end
|
141
|
-
end
|
142
|
-
elsif opts[:req]
|
143
|
-
msg = opts[:req].class == TrueClass ? 'is required' : opts[:req]
|
144
|
-
add_error field, msg
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
if @errors.keys.length > 0 && block_given?
|
149
|
-
@errors.each { |k,v| yield(k, v) }
|
150
|
-
end
|
151
|
-
|
152
|
-
@errors
|
153
|
-
end
|
154
|
-
|
155
|
-
def valid? instance
|
156
|
-
errors = validate instance
|
157
|
-
errors.keys.length == 0
|
158
|
-
end
|
1
|
+
# base libs
|
2
|
+
require_relative 'typero/typero'
|
3
|
+
require_relative 'typero/params'
|
4
|
+
require_relative 'typero/type/type'
|
5
|
+
|
6
|
+
# checker types
|
7
|
+
Dir['%s/typero/type/types/*.rb' % __dir__].each do |file|
|
8
|
+
require file
|
159
9
|
end
|
160
10
|
|
161
|
-
|
162
|
-
|
163
|
-
Dir['%s/typero/type/*.rb' % File.dirname(__FILE__)].each { |file| require file }
|
11
|
+
# load Sequel adapter is Sequel is available
|
12
|
+
require_relative './adapters/sequel' if defined?(Sequel)
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# Base class for schema validation
|
2
|
+
|
3
|
+
class Typero
|
4
|
+
class Params
|
5
|
+
ALLOWED = %i(name min max default allowed delimiter max_count req required type array meta desc description duplicates unique)
|
6
|
+
|
7
|
+
attr_reader :rules, :db_rules
|
8
|
+
|
9
|
+
def initialize &block
|
10
|
+
@db_rules = []
|
11
|
+
@rules = {}
|
12
|
+
instance_exec &block
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# used in dsl to define schema field options
|
18
|
+
def set field, *args
|
19
|
+
raise "Field name not given (Typero)" unless field
|
20
|
+
|
21
|
+
if args.first.is_a?(Hash)
|
22
|
+
opts = args.first || {}
|
23
|
+
else
|
24
|
+
opts = args[1] || {}
|
25
|
+
opts[:type] ||= args[0]
|
26
|
+
end
|
27
|
+
|
28
|
+
opts[:type] ||= :string
|
29
|
+
opts[:required] = true unless opts[:required].is_a?(FalseClass) || opts[:req].is_a?(FalseClass)
|
30
|
+
|
31
|
+
field = field.to_s
|
32
|
+
|
33
|
+
# name? - opional name
|
34
|
+
if field.include?('?')
|
35
|
+
field = field.sub('?', '')
|
36
|
+
opts[:required] = false
|
37
|
+
end
|
38
|
+
|
39
|
+
# array that allows duplicates
|
40
|
+
if opts[:type].is_a?(Array)
|
41
|
+
opts[:type] = opts[:type].first
|
42
|
+
opts[:array] = true
|
43
|
+
end
|
44
|
+
|
45
|
+
# no duplicates array
|
46
|
+
if opts[:type].is_a?(Set)
|
47
|
+
opts[:type] = opts[:type].to_a.first
|
48
|
+
opts[:array] = true
|
49
|
+
end
|
50
|
+
|
51
|
+
opts[:type] ||= 'string'
|
52
|
+
opts[:type] = opts[:type].to_s.downcase
|
53
|
+
|
54
|
+
opts[:description] = opts.delete(:desc) unless opts[:desc].nil?
|
55
|
+
|
56
|
+
# chek alloed params, all optional should go in meta
|
57
|
+
result = opts.keys - ALLOWED
|
58
|
+
raise ArgumentError.new('Unallowed Type params found: %s, allowed: %s' % [result.join(', '), ALLOWED]) if result.length > 0
|
59
|
+
|
60
|
+
field = field.to_sym
|
61
|
+
|
62
|
+
db :add_index, field if opts.delete(:index)
|
63
|
+
|
64
|
+
klass = Typero::Type.load opts[:type]
|
65
|
+
@rules[field] = opts
|
66
|
+
end
|
67
|
+
|
68
|
+
# pass values for db_schema only
|
69
|
+
# db :timestamps
|
70
|
+
# db :add_index, :code -> t.add_index :code
|
71
|
+
def db *args
|
72
|
+
@db_rules.push args
|
73
|
+
end
|
74
|
+
|
75
|
+
# set :age, type: :integer -> integer :age
|
76
|
+
# email :email
|
77
|
+
#
|
78
|
+
# set :emails, Array[:email]
|
79
|
+
# email Array[:emails]
|
80
|
+
def method_missing field, *args, &block
|
81
|
+
set field, *args
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# Master class
|
2
|
+
|
3
|
+
class Typero
|
4
|
+
class Type
|
5
|
+
ERRORS = {
|
6
|
+
en: {
|
7
|
+
# errors shared between various types
|
8
|
+
min_length_error: 'min lenght is %s, you have %s',
|
9
|
+
max_length_error: 'max lenght is %s, you have %s',
|
10
|
+
min_value_error: 'min is %s, got %s',
|
11
|
+
max_value_error: 'max is %s, got %s',
|
12
|
+
unallowed_characters_error: 'is having unallowed characters'
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
OPTS = {}
|
17
|
+
|
18
|
+
attr_accessor :opts
|
19
|
+
attr_accessor :value
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def load name
|
23
|
+
klass = 'Typero::%sType' % name.to_s.gsub(/[^\w]/,'').classify
|
24
|
+
|
25
|
+
if const_defined? klass
|
26
|
+
klass.constantize
|
27
|
+
else
|
28
|
+
raise ArgumentError, 'Typero type "%s" is not defined (%s)' % [name, klass]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def error locale, key, message
|
33
|
+
locale = locale.to_sym
|
34
|
+
ERRORS[locale] ||= {}
|
35
|
+
ERRORS[locale][key.to_sym] = message
|
36
|
+
end
|
37
|
+
|
38
|
+
def opts key, desc
|
39
|
+
OPTS[self] ||= {}
|
40
|
+
OPTS[self][key] = desc
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
###
|
45
|
+
|
46
|
+
def initialize value, opts={}
|
47
|
+
@value = value
|
48
|
+
@opts = opts
|
49
|
+
end
|
50
|
+
|
51
|
+
# default validation for any type
|
52
|
+
def validate
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def default
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# get error from option or the default one
|
63
|
+
def error_for name, *args
|
64
|
+
locale =
|
65
|
+
if defined?(Lux)
|
66
|
+
Lux.current.locale.to_s
|
67
|
+
elsif defined?(I18n)
|
68
|
+
I18n.locale
|
69
|
+
end
|
70
|
+
|
71
|
+
locale = :en if locale.to_s == ''
|
72
|
+
pointer = ERRORS[locale.to_sym] || ERRORS[:en]
|
73
|
+
error = @opts.dig(:meta, locale, name) || @opts.dig(:meta, name) || pointer[name]
|
74
|
+
error = error % args if args.first
|
75
|
+
|
76
|
+
raise 'Type error :%s not defined' % name unless error
|
77
|
+
raise TypeError.new(error)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# you should not use this filed for currency calculations
|
2
|
+
|
3
|
+
require_relative './float'
|
4
|
+
|
5
|
+
class Typero::CurrencyType < Typero::FloatType
|
6
|
+
|
7
|
+
def set
|
8
|
+
@value = @value.to_f.round(2)
|
9
|
+
end
|
10
|
+
|
11
|
+
def db_field
|
12
|
+
opts = {}
|
13
|
+
opts[:precision] = 8
|
14
|
+
opts[:scale] = 2
|
15
|
+
[:decimal, opts]
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Typero::EmailType < Typero::Type
|
2
|
+
error :en, :not_8_chars_error, 'is not having at least 8 characters'
|
3
|
+
error :en, :missing_monkey_error, 'is missing @'
|
4
|
+
|
5
|
+
def set
|
6
|
+
@value = @value.downcase.gsub(/\s+/,'+')
|
7
|
+
end
|
8
|
+
|
9
|
+
def validate
|
10
|
+
error_for(:not_8_chars_error) unless @value.to_s.length > 7
|
11
|
+
error_for(:missing_monkey_error) unless @value.include?('@')
|
12
|
+
end
|
13
|
+
|
14
|
+
def db_field
|
15
|
+
opts = {}
|
16
|
+
opts[:limit] = @opts[:max] || 120
|
17
|
+
opts[:null] = false if @opts[:required]
|
18
|
+
[:string, opts]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Typero::FloatType < Typero::Type
|
2
|
+
opts :min, 'Minimum value'
|
3
|
+
opts :max, 'Maximun value'
|
4
|
+
|
5
|
+
def set
|
6
|
+
@value = @value.to_f
|
7
|
+
end
|
8
|
+
|
9
|
+
def validate
|
10
|
+
error_for(:min_length_error, @opts[:min], @value) if @opts[:min] && value < @opts[:min]
|
11
|
+
error_for(:max_length_error, @opts[:max], @value) if @opts[:max] && value > @opts[:max]
|
12
|
+
end
|
13
|
+
|
14
|
+
def db_field
|
15
|
+
opts = {}
|
16
|
+
opts[:null] = false if @opts[:required]
|
17
|
+
[:float, opts]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Typero::HashType < Typero::Type
|
2
|
+
error :en, :not_hash_type_error, 'value is not hash type'
|
3
|
+
|
4
|
+
def default
|
5
|
+
{}
|
6
|
+
end
|
7
|
+
|
8
|
+
def set
|
9
|
+
@value = @value.to_h
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate
|
13
|
+
error_for(:not_hash_type_error) unless @value.is_a?(Hash)
|
14
|
+
end
|
15
|
+
|
16
|
+
def db_field
|
17
|
+
[:jsonb, {
|
18
|
+
null: false
|
19
|
+
}]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Typero::ImageType < Typero::Type
|
2
|
+
FORMATS = %w[jpg jpeg gif png svg webp]
|
3
|
+
|
4
|
+
error :en, :image_not_starting_error, 'URL is not starting with http'
|
5
|
+
error :en, :image_not_image_format, 'URL is not ending with %s' % FORMATS.join(', ')
|
6
|
+
|
7
|
+
opts :strict, 'Force image to have known extension (%s)' % FORMATS.join(', ')
|
8
|
+
|
9
|
+
def set
|
10
|
+
@value = 'https://%s' % @value unless @value.include?('://')
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate
|
14
|
+
error_for(:image_not_starting_error) unless @value =~ /^https?:\/\/./
|
15
|
+
|
16
|
+
if opts[:strict]
|
17
|
+
ext = @value.split('.').last.downcase
|
18
|
+
error_for(:image_not_image_format) unless FORMATS.include?(ext)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def db_field
|
23
|
+
opts = {}
|
24
|
+
opts[:null] = false if @opts[:required]
|
25
|
+
[:string, opts]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Typero::IntegerType < Typero::Type
|
2
|
+
opts :min, 'Minimum value'
|
3
|
+
opts :max, 'Maximun value'
|
4
|
+
|
5
|
+
def set
|
6
|
+
@value = @value.to_i
|
7
|
+
end
|
8
|
+
|
9
|
+
def validate
|
10
|
+
error_for(:min_value_error, @opts[:min], @value) if @opts[:min] && @value < @opts[:min]
|
11
|
+
error_for(:max_value_error, @opts[:max], @value) if @opts[:max] && @value > @opts[:max]
|
12
|
+
end
|
13
|
+
|
14
|
+
def db_field
|
15
|
+
opts = {}
|
16
|
+
opts[:null] = false if @opts[:required]
|
17
|
+
opts[:default] = @opts[:default]
|
18
|
+
[:integer, opts]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -4,6 +4,12 @@ class Typero::LabelType < Typero::Type
|
|
4
4
|
end
|
5
5
|
|
6
6
|
def validate
|
7
|
-
|
7
|
+
error_for(:unallowed_characters_error) unless @value =~ /^[\w\-]+$/
|
8
|
+
end
|
9
|
+
|
10
|
+
def db_field
|
11
|
+
opts = {}
|
12
|
+
opts[:limit] = 30
|
13
|
+
[:string, opts]
|
8
14
|
end
|
9
15
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Typero::OibType < Typero::Type
|
2
|
+
error :en, :not_an_oib_error, 'not in an OIB format'
|
3
|
+
|
4
|
+
def set
|
5
|
+
@value = check?(@value) ? @value.to_i : nil
|
6
|
+
end
|
7
|
+
|
8
|
+
def validate
|
9
|
+
error_for(:not_an_oib_error) unless check?(@value)
|
10
|
+
end
|
11
|
+
|
12
|
+
def db_field
|
13
|
+
opts = {}
|
14
|
+
opts[:null] = false if @opts[:required]
|
15
|
+
opts[:limit] = 11
|
16
|
+
[:string, opts]
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# http://domagoj.eu/oib/
|
22
|
+
def check? oib
|
23
|
+
oib = oib.to_s
|
24
|
+
|
25
|
+
return false unless oib.match(/^[0-9]{11}$/)
|
26
|
+
|
27
|
+
control_sum = (0..9).inject(10) do |middle, position|
|
28
|
+
middle += oib.at(position).to_i
|
29
|
+
middle %= 10
|
30
|
+
middle = 10 if middle == 0
|
31
|
+
middle *= 2
|
32
|
+
middle %= 11
|
33
|
+
end
|
34
|
+
|
35
|
+
control_sum = 11 - control_sum
|
36
|
+
control_sum = 0 if control_sum == 10
|
37
|
+
|
38
|
+
return control_sum == oib.at(10).to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# for postgres - select st_asewkt(lon_lat) || st_astext(lon_lat)
|
2
|
+
# point = @object.class.xselect("ST_AsText(#{field}) as #{field}").where(id: @object.id).first[field.to_sym]
|
3
|
+
|
4
|
+
class Typero::PointType < Typero::Type
|
5
|
+
def set
|
6
|
+
if @value.present?
|
7
|
+
if @value.include?('/@')
|
8
|
+
point = @value.split('/@', 2).last.split(',')
|
9
|
+
@value = [point[0], point[1]].join(',')
|
10
|
+
end
|
11
|
+
|
12
|
+
if !@value.include?('POINT') && @value.include?(',')
|
13
|
+
point = @value.sub(/,\s*/, ' ')
|
14
|
+
@value = 'SRID=4326;POINT(%s)' % point
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate
|
20
|
+
if @value && @value.include?(',') && !@value =~ /^SRID=4326;POINT\(/
|
21
|
+
error_for(:unallowed_characters_error)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def db_field
|
26
|
+
[:geography, {}]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Typero::StringType < Typero::Type
|
2
|
+
opts :min, 'Minimun string length'
|
3
|
+
opts :max, 'Maximun string length'
|
4
|
+
|
5
|
+
def set
|
6
|
+
@value = @value.to_s unless @value.is_a?(String)
|
7
|
+
@value = @value.downcase if @opts[:downcase]
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate
|
11
|
+
error_for(:min_length_error, @opts[:min], @value.length) if @opts[:min] && @value.length < @opts[:min]
|
12
|
+
error_for(:max_length_error, @opts[:max], @value.length) if @opts[:max] && @value.length > @opts[:max]
|
13
|
+
end
|
14
|
+
|
15
|
+
def db_field
|
16
|
+
opts = {}
|
17
|
+
opts[:limit] = @opts[:max] || 255
|
18
|
+
opts[:null] = false if @opts[:required]
|
19
|
+
opts[:default] = @opts[:default]
|
20
|
+
[:string, opts]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Typero::UrlType < Typero::Type
|
2
|
+
error :en, :url_not_starting_error, 'URL is not starting with http'
|
3
|
+
|
4
|
+
def set
|
5
|
+
@value = 'http://%s' % @value unless @value.include?('://')
|
6
|
+
end
|
7
|
+
|
8
|
+
def validate
|
9
|
+
error_for(:url_not_starting_error) unless @value =~ /^https?:\/\/./
|
10
|
+
end
|
11
|
+
|
12
|
+
def db_field
|
13
|
+
opts = {}
|
14
|
+
opts[:null] = false if @opts[:required]
|
15
|
+
[:string, opts]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# rules = Typero.new do
|
2
|
+
# set :name, String, req: true
|
3
|
+
# set :email, :email, req: true
|
4
|
+
# set :skills, [:email], min: 2
|
5
|
+
# end
|
6
|
+
#
|
7
|
+
# or
|
8
|
+
#
|
9
|
+
# rules = Typero.new do
|
10
|
+
# string :name, req: true
|
11
|
+
# email :email, req: true
|
12
|
+
# email [:skills], min: 2
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# errors = rules.validate @object
|
16
|
+
# rules.valid?
|
17
|
+
# rules.validate(@object) { |errors| ... }
|
18
|
+
|
19
|
+
class Typero
|
20
|
+
VERSION = File.read File.expand_path "../../.version", File.dirname(__FILE__)
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# check and coerce value
|
24
|
+
# Typero.set(:label, 'Foo bar') -> "foo-bar"
|
25
|
+
def set type, value, opts = {}, &block
|
26
|
+
check = Typero::Type.load(type).new value, opts
|
27
|
+
check.set
|
28
|
+
check.validate
|
29
|
+
check.value
|
30
|
+
rescue TypeError => error
|
31
|
+
if block
|
32
|
+
block.call error
|
33
|
+
false
|
34
|
+
else
|
35
|
+
raise error
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
###
|
41
|
+
|
42
|
+
# accepts dsl block to
|
43
|
+
def initialize &block
|
44
|
+
raise "Params not defined" unless block_given?
|
45
|
+
@schema = Params.new &block
|
46
|
+
end
|
47
|
+
|
48
|
+
# validates any instance object with hash variable interface
|
49
|
+
# it also coarces values
|
50
|
+
def validate object
|
51
|
+
@object = object
|
52
|
+
@errors = {}
|
53
|
+
|
54
|
+
@schema.rules.each do |field, opts|
|
55
|
+
# set value to default if value is blank and default given
|
56
|
+
@object[field] = opts[:default] if opts[:default] && @object[field].blank?
|
57
|
+
|
58
|
+
# get field value
|
59
|
+
value = @object[field]
|
60
|
+
|
61
|
+
if opts[:array]
|
62
|
+
unless value.is_a?(Array)
|
63
|
+
opts[:delimiter] ||= /\s*,\s*/
|
64
|
+
value = value.to_s.split(opts[:delimiter])
|
65
|
+
end
|
66
|
+
|
67
|
+
value = value
|
68
|
+
.map { |el| check_filed_value field, el, opts }
|
69
|
+
.map { |el| el.to_s == '' ? nil : el }
|
70
|
+
.compact
|
71
|
+
|
72
|
+
value = Set.new(value).to_a unless opts[:duplicates]
|
73
|
+
|
74
|
+
opts[:max_count] ||= 100
|
75
|
+
add_error(field, 'Max number of array elements is %d, you have %d' % [opts[:max_count], value.length], opts) if value.length > opts[:max_count]
|
76
|
+
|
77
|
+
check_required field, value.first, opts
|
78
|
+
else
|
79
|
+
value = check_filed_value field, value, opts
|
80
|
+
check_required field, value, opts
|
81
|
+
end
|
82
|
+
|
83
|
+
# if value is not list of allowed values, raise error
|
84
|
+
if opts[:allowed] && !opts[:values].include?(value)
|
85
|
+
add_error field, 'Value "%s" is not allowed' % value, opts
|
86
|
+
end
|
87
|
+
|
88
|
+
# present empty string values as nil
|
89
|
+
@object[field] = value.to_s.sub(/\s+/, '') == '' ? nil : value
|
90
|
+
end
|
91
|
+
|
92
|
+
if @errors.keys.length > 0 && block_given?
|
93
|
+
@errors.each { |k, v| yield(k, v) }
|
94
|
+
end
|
95
|
+
|
96
|
+
@errors
|
97
|
+
end
|
98
|
+
|
99
|
+
def valid? object
|
100
|
+
errors = validate object
|
101
|
+
errors.keys.length == 0
|
102
|
+
end
|
103
|
+
|
104
|
+
# returns field, db_type, db_opts
|
105
|
+
def db_schema
|
106
|
+
out = @schema.rules.inject([]) do |total, (field, opts)|
|
107
|
+
# get db filed schema
|
108
|
+
type, opts = Typero::Type.load(opts[:type]).new(nil, opts).db_field
|
109
|
+
|
110
|
+
# add array true to field it ont defined in schema
|
111
|
+
schema_opts = @schema.rules[field]
|
112
|
+
opts[:array] = true if schema_opts[:array]
|
113
|
+
|
114
|
+
total << [type, field, opts]
|
115
|
+
end
|
116
|
+
|
117
|
+
out += @schema.db_rules
|
118
|
+
|
119
|
+
out
|
120
|
+
end
|
121
|
+
|
122
|
+
# iterate trough all the ruels via block interface
|
123
|
+
# schema.rules do |field, opts|
|
124
|
+
# schema.rules(:url) do |field, opts|
|
125
|
+
def rules(filter = nil, &block)
|
126
|
+
return @schema.rules unless filter
|
127
|
+
out = @schema.rules
|
128
|
+
out = out.select { |k, v| v[:type].to_s == filter.to_s || v[:array_type].to_s == filter.to_s } if filter
|
129
|
+
return out unless block_given?
|
130
|
+
|
131
|
+
out.each { |k, v| yield k, v }
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
# adds error to array or prefixes with field name
|
137
|
+
def add_error field, msg, opts
|
138
|
+
if @errors[field]
|
139
|
+
@errors[field] += ", %s" % msg
|
140
|
+
else
|
141
|
+
if msg && msg[0, 1].downcase == msg[0, 1]
|
142
|
+
field_name = opts[:name] || field.to_s.sub(/_id$/, "").humanize
|
143
|
+
msg = "%s %s" % [field_name, msg]
|
144
|
+
end
|
145
|
+
|
146
|
+
@errors[field] = msg
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def safe_type type
|
151
|
+
type.to_s.gsub(/[^\w]/, "").classify
|
152
|
+
end
|
153
|
+
|
154
|
+
def check_required field, value, opts
|
155
|
+
return if !opts[:required] || value
|
156
|
+
msg = opts[:required].class == TrueClass ? "is required" : opts[:required]
|
157
|
+
add_error field, msg, opts
|
158
|
+
end
|
159
|
+
|
160
|
+
def check_filed_value field, value, opts
|
161
|
+
return unless value
|
162
|
+
|
163
|
+
klass = "Typero::%sType" % safe_type(opts[:type])
|
164
|
+
check = klass.constantize.new value, opts
|
165
|
+
check.value = check.default if check.value.nil?
|
166
|
+
|
167
|
+
unless check.value.nil?
|
168
|
+
begin
|
169
|
+
check.set
|
170
|
+
check.validate
|
171
|
+
check.value
|
172
|
+
rescue TypeError => e
|
173
|
+
add_error field, e.message, opts
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: typero
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dino Reic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fast_blank
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
description: Simple and fast ruby type system. Enforce types as Array, Email, Boolean
|
28
28
|
for ruby class instances
|
29
29
|
email: reic.dino@gmail.com
|
@@ -32,18 +32,26 @@ extensions: []
|
|
32
32
|
extra_rdoc_files: []
|
33
33
|
files:
|
34
34
|
- "./.version"
|
35
|
+
- "./lib/adapters/sequel.rb"
|
35
36
|
- "./lib/typero.rb"
|
36
|
-
- "./lib/typero/
|
37
|
-
- "./lib/typero/type/
|
38
|
-
- "./lib/typero/type/boolean.rb"
|
39
|
-
- "./lib/typero/type/
|
40
|
-
- "./lib/typero/type/
|
41
|
-
- "./lib/typero/type/
|
42
|
-
- "./lib/typero/type/
|
43
|
-
- "./lib/typero/type/
|
44
|
-
- "./lib/typero/type/
|
45
|
-
- "./lib/typero/type/
|
46
|
-
- "./lib/typero/type/
|
37
|
+
- "./lib/typero/params.rb"
|
38
|
+
- "./lib/typero/type/type.rb"
|
39
|
+
- "./lib/typero/type/types/boolean.rb"
|
40
|
+
- "./lib/typero/type/types/currency.rb"
|
41
|
+
- "./lib/typero/type/types/date.rb"
|
42
|
+
- "./lib/typero/type/types/datetime.rb"
|
43
|
+
- "./lib/typero/type/types/email.rb"
|
44
|
+
- "./lib/typero/type/types/float.rb"
|
45
|
+
- "./lib/typero/type/types/hash.rb"
|
46
|
+
- "./lib/typero/type/types/image.rb"
|
47
|
+
- "./lib/typero/type/types/integer.rb"
|
48
|
+
- "./lib/typero/type/types/label.rb"
|
49
|
+
- "./lib/typero/type/types/oib.rb"
|
50
|
+
- "./lib/typero/type/types/point.rb"
|
51
|
+
- "./lib/typero/type/types/string.rb"
|
52
|
+
- "./lib/typero/type/types/text.rb"
|
53
|
+
- "./lib/typero/type/types/url.rb"
|
54
|
+
- "./lib/typero/typero.rb"
|
47
55
|
homepage: https://github.com/dux/typero
|
48
56
|
licenses:
|
49
57
|
- MIT
|
@@ -63,8 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
71
|
- !ruby/object:Gem::Version
|
64
72
|
version: '0'
|
65
73
|
requirements: []
|
66
|
-
|
67
|
-
rubygems_version: 2.7.5
|
74
|
+
rubygems_version: 3.0.6
|
68
75
|
signing_key:
|
69
76
|
specification_version: 4
|
70
77
|
summary: Ruby type system
|
data/lib/typero/type.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
class Typero::Type
|
2
|
-
attr_accessor :opts
|
3
|
-
attr_accessor :value
|
4
|
-
|
5
|
-
def initialize(value, opts={})
|
6
|
-
@value = value
|
7
|
-
@opts = opts
|
8
|
-
end
|
9
|
-
|
10
|
-
# default validation for any type
|
11
|
-
def validate(what)
|
12
|
-
true
|
13
|
-
end
|
14
|
-
|
15
|
-
def get
|
16
|
-
@value
|
17
|
-
end
|
18
|
-
|
19
|
-
def set
|
20
|
-
@value
|
21
|
-
end
|
22
|
-
|
23
|
-
def default
|
24
|
-
nil
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
|
data/lib/typero/type/array.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
class Typero::ArrayType < Typero::Type
|
2
|
-
def default
|
3
|
-
[]
|
4
|
-
end
|
5
|
-
|
6
|
-
def set
|
7
|
-
unless @value.class.to_s.index('Array')
|
8
|
-
@value = @value.to_s.sub(/^\{/,'').sub(/\}$/,'').split(/\s*,\s*/)
|
9
|
-
end
|
10
|
-
|
11
|
-
@value.uniq!
|
12
|
-
@value.compact!
|
13
|
-
|
14
|
-
if type = @opts[:array_type]
|
15
|
-
@value.map! { |el|
|
16
|
-
Typero.validate(el, type) { |msg|
|
17
|
-
raise TypeError.new "'%s' %s (value in list)" % [el, msg]
|
18
|
-
}
|
19
|
-
}
|
20
|
-
end
|
21
|
-
|
22
|
-
# this converts Sequel::Postgres::PGArray to Array and fixes many problems
|
23
|
-
@value = @value.to_a if @value.class != Array
|
24
|
-
end
|
25
|
-
|
26
|
-
def validate
|
27
|
-
raise TypeError, 'Min array lenght is %s elements' % @opts[:min] if @opts[:min] && @value.length < @opts[:min]
|
28
|
-
raise TypeError, 'Max array lenght is %s elements' % @opts[:max] if @opts[:max] && @value.length > @opts[:max]
|
29
|
-
true
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
data/lib/typero/type/email.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
class Typero::EmailType < Typero::Type
|
2
|
-
|
3
|
-
def set
|
4
|
-
@value = @value.downcase.gsub(/\s+/,'+')
|
5
|
-
end
|
6
|
-
|
7
|
-
def validate
|
8
|
-
raise TypeError, 'is not having at least 8 characters' unless @value.to_s.length > 7
|
9
|
-
raise TypeError, 'is missing @' unless @value.include?('@')
|
10
|
-
# raise TypeError, "[#{@value}] is in wrong format" unless @value =~ /^[\+\w\-\.]+\@[\w\-\.]+$/i
|
11
|
-
end
|
12
|
-
|
13
|
-
end
|
14
|
-
|
data/lib/typero/type/float.rb
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
class Typero::FloatType < Typero::Type
|
2
|
-
|
3
|
-
def set
|
4
|
-
@value = @value.to_f
|
5
|
-
end
|
6
|
-
|
7
|
-
def validate
|
8
|
-
raise TypeError, "min lenght is #{@opts[:min]}" if @opts[:min] && value < @opts[:min]
|
9
|
-
raise TypeError, "max lenght is #{@opts[:max]}" if @opts[:max] && value > @opts[:max]
|
10
|
-
end
|
11
|
-
|
12
|
-
end
|
13
|
-
|
data/lib/typero/type/hash.rb
DELETED
data/lib/typero/type/integer.rb
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
class Typero::IntegerType < Typero::Type
|
2
|
-
def set
|
3
|
-
@value = @value.to_i
|
4
|
-
end
|
5
|
-
|
6
|
-
def validate
|
7
|
-
raise TypeError, 'min is %s, got %s' % [@opts[:min], @value] if @opts[:min] && @value < @opts[:min]
|
8
|
-
raise TypeError, 'max is %s, got %s' % [@opts[:max], @value] if @opts[:max] && @value > @opts[:max]
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
data/lib/typero/type/oib.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
class Typero::OibType < Typero::Type
|
2
|
-
def check? oib
|
3
|
-
oib = oib.to_s
|
4
|
-
|
5
|
-
return false if (oib =~ /\d{11}/) != 0
|
6
|
-
return false if oib.length != 11
|
7
|
-
|
8
|
-
a = 10
|
9
|
-
(0..9).each do |i|
|
10
|
-
a = (a + oib[i,1].to_i) % 10
|
11
|
-
a = 10 if a == 0
|
12
|
-
a = (a * 2) % 11
|
13
|
-
end
|
14
|
-
|
15
|
-
kontrolna = 11 - a
|
16
|
-
kontrolna = 0 if kontrolna == 10
|
17
|
-
|
18
|
-
kontrolna == oib[10,1].to_i
|
19
|
-
end
|
20
|
-
|
21
|
-
def set
|
22
|
-
@value = check?(@value) ? @value.to_i : nil
|
23
|
-
end
|
24
|
-
|
25
|
-
def validate
|
26
|
-
raise TypeError.new('Not an OIB') unless check?(@value)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
data/lib/typero/type/string.rb
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
class Typero::StringType < Typero::Type
|
2
|
-
def set
|
3
|
-
@value = @value.to_s unless @value.is_a?(String)
|
4
|
-
@value = @value.downcase if @opts[:downcase]
|
5
|
-
end
|
6
|
-
|
7
|
-
def validate
|
8
|
-
raise TypeError, 'min lenght is %s, you have %s' % [@opts[:min], @value.length] if @opts[:min] && @value.length < @opts[:min]
|
9
|
-
raise TypeError, 'max lenght is %s, you have %s' % [@opts[:max], @value.length] if @opts[:max] && @value.length > @opts[:max]
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
data/lib/typero/type/url.rb
DELETED