typero 0.9.0 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e78c338c182793ab2bffd1b4c5469d5285f57f55697402b6e80fb1881a3f7a8
4
- data.tar.gz: 18a803b38ef8a24cb3756a43e5b5704082a08f4d805be57239cb054c28e6f132
3
+ metadata.gz: 2b0ee2233c2ba484ec5ff98778a8da24a876b7b22eb4d24cc6b3ba3a8886e714
4
+ data.tar.gz: 4a96d10f9c6d0e75e8ae36965d34a6cda8853684974f799e2cf5e32d6a7003c1
5
5
  SHA512:
6
- metadata.gz: 1c02d8cec42f465c5cf34b0b69c856cf193f1c5e289898af0aefdebf1d61ed91c95148a1fa9ca1c047a07810c99bd518946dbfe323e0e53c4502b66b08910c85
7
- data.tar.gz: de185fca3ac80a4e7e9d6b87778872b3b352f89678f334e24b0047480d401cbd0abf95bb8940ba0e29d9d3f485ef62521b38335f1f6cc3a7d1f59194383f4a67
6
+ metadata.gz: 880c3e4e551ab660f85530454ad9007ae383118677ea209ffaf0efff54bcd38da220579dc42d87618bd9ea50790b04a46c63dfe3cfd6252508b2bbec6ba1d711
7
+ data.tar.gz: 6f10437b894c91f3c7f723da03b426218d60b21c8bd161111d8ae7a745bdf35565f4fba84f26552fc75aa9b40211f3902d1cc2418fc420a5e3bce912ac676bc2
data/.version CHANGED
@@ -1 +1 @@
1
- 0.9.0
1
+ 0.9.3
@@ -3,60 +3,43 @@
3
3
  module Sequel::Plugins::TyperoAttributes
4
4
  module ClassMethods
5
5
  def typero
6
- Typero.new self
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 typero! field_name=nil
13
- return unless Typero.defined?(self.class)
12
+ def validate
13
+ super
14
14
 
15
- typero = self.class.typero
15
+ schema = Typero.schema(self.class) || return
16
16
 
17
- typero.validate(self) do |name, err|
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
- typero.rules.each do |field, rule|
23
- self[field] ||= {} if rule[:type] == 'hash'
24
-
22
+ schema.rules.each do |field, rule|
25
23
  # check uniqe fields
26
- if rule[:unique]
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 = rule[:unique].class == TrueClass ? %[Value '"#{value}"' for #{field} allready exists] : rule[:unique]
30
+ error = unique.class == TrueClass ? %[Value '"#{value}"' for #{field} allready exists] : 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[:protected] && self[:id]
36
+ if prot = rule.dig(:meta, :protected) && self[:id]
39
37
  if column_changed?(field)
40
- error = rule[:protected].class == TrueClass ? "value once defined can't be overwritten." : rule[:protected]
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
 
@@ -1,8 +1,9 @@
1
+ # require 'hash_wia'
2
+
1
3
  # base libs
2
4
  require_relative 'typero/typero'
3
5
  require_relative 'typero/schema'
4
6
  require_relative 'typero/params'
5
- require_relative 'typero/exporter'
6
7
  require_relative 'typero/type/type'
7
8
 
8
9
  # checker types
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Typero
4
4
  class Params
5
- attr_reader :rules, :db_rules
5
+ attr_reader :db_rules
6
6
 
7
7
  def initialize &block
8
8
  @db_rules = []
@@ -10,10 +10,14 @@ module Typero
10
10
  instance_exec &block
11
11
  end
12
12
 
13
+ def rules
14
+ @rules.dup
15
+ end
16
+
13
17
  private
14
18
 
15
19
  # used in dsl to define schema field options
16
- def set field, *args
20
+ def set field, *args, &block
17
21
  raise "Field name not given (Typero)" unless field
18
22
 
19
23
  if args.first.is_a?(Hash)
@@ -27,13 +31,29 @@ module Typero
27
31
 
28
32
  field = field.to_s
29
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
+
30
46
  # name? - opional name
31
47
  if field.include?('?')
32
48
  field = field.sub('?', '')
33
49
  opts[:required] = false
34
50
  end
35
51
 
36
- opts[:required] = true if opts[:required].nil?
52
+ if value = opts.delete(:req)
53
+ opts[:required] = value
54
+ else
55
+ opts[:required] = true if opts[:required].nil?
56
+ end
37
57
 
38
58
  # array that allows duplicates
39
59
  if opts[:type].is_a?(Array)
@@ -47,12 +67,14 @@ module Typero
47
67
  opts[:array] = true
48
68
  end
49
69
 
70
+ opts[:type] = @block_type if @block_type
71
+
50
72
  # Boolean
51
- if opts[:type].is_a?(TrueClass)
73
+ if opts[:type].is_a?(TrueClass) || opts[:type] == :true
52
74
  opts[:required] = false
53
75
  opts[:default] = true
54
76
  opts[:type] = :boolean
55
- elsif opts[:type].is_a?(FalseClass)
77
+ elsif opts[:type].is_a?(FalseClass) || opts[:type] == :false
56
78
  opts[:required] = false
57
79
  opts[:default] = false
58
80
  opts[:type] = :boolean
@@ -61,6 +83,11 @@ module Typero
61
83
  opts[:model] = opts.delete(:schema) if opts[:schema]
62
84
  opts[:type] = :model if opts[:model]
63
85
 
86
+ if block_given?
87
+ opts[:type] = :model
88
+ opts[:model] = Typero.schema &block
89
+ end
90
+
64
91
  opts[:type] ||= 'string'
65
92
  opts[:type] = opts[:type].to_s.downcase.to_sym
66
93
 
@@ -93,7 +120,7 @@ module Typero
93
120
  # set :emails, Array[:email]
94
121
  # email Array[:emails]
95
122
  def method_missing field, *args, &block
96
- set field, *args
123
+ set field, *args, &block
97
124
  end
98
125
  end
99
126
  end
@@ -1,6 +1,7 @@
1
1
  module Typero
2
2
  class Schema
3
3
  SCHEMAS = {}
4
+ TYPES = {}
4
5
 
5
6
  # accepts dsl block to
6
7
  def initialize &block
@@ -10,11 +11,22 @@ module Typero
10
11
 
11
12
  # validates any instance object with hash variable interface
12
13
  # it also coarces values
13
- def validate object
14
- @object = object
15
- @errors = {}
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
16
24
 
17
25
  @schema.rules.each do |field, opts|
26
+ for k in opts.keys
27
+ opts[k] = @object.instance_exec(&opts[k]) if opts[k].is_a?(Proc)
28
+ end
29
+
18
30
  # set value to default if value is blank and default given
19
31
  @object[field] = opts[:default] if opts[:default] && @object[field].blank?
20
32
 
@@ -22,32 +34,36 @@ module Typero
22
34
  value = @object[field]
23
35
 
24
36
  if opts[:array]
25
- unless value.is_a?(Array)
26
- opts[:delimiter] ||= /\s*,\s*/
37
+ unless value.respond_to?(:each)
38
+ opts[:delimiter] ||= /\s*[,\n]\s*/
27
39
  value = value.to_s.split(opts[:delimiter])
28
40
  end
29
41
 
30
42
  value = value
31
- .map { |el| check_filed_value field, el, opts }
32
- .map { |el| el.to_s == '' ? nil : el }
43
+ .flatten
44
+ .map { |el| el.to_s == '' ? nil : check_filed_value(field, el, opts) }
33
45
  .compact
34
46
 
35
47
  value = Set.new(value).to_a unless opts[:duplicates]
36
48
 
37
49
  opts[:max_count] ||= 100
38
- 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]
50
+ 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]
51
+ 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]
39
52
 
40
53
  add_required_error field, value.first, opts
41
54
  else
55
+ value = nil if value.to_s == ''
56
+
57
+ # if value is not list of allowed values, raise error
58
+ allowed = opts[:allow] || opts[:allowed] || opts[:values]
59
+ if value && allowed && !allowed.include?(value)
60
+ add_error field, 'Value "%s" is not allowed' % value, opts
61
+ end
62
+
42
63
  value = check_filed_value field, value, opts
43
64
  add_required_error field, value, opts
44
65
  end
45
66
 
46
- # if value is not list of allowed values, raise error
47
- if opts[:allowed] && !opts[:values].include?(value)
48
- add_error field, 'Value "%s" is not allowed' % value, opts
49
- end
50
-
51
67
  # present empty string values as nil
52
68
  @object[field] = value.to_s.sub(/\s+/, '') == '' ? nil : value
53
69
  end
@@ -126,7 +142,13 @@ module Typero
126
142
  check = klass.constantize.new value, opts
127
143
  check.get
128
144
  rescue TypeError => e
129
- add_error field, e.message, opts
145
+ if e.message[0] == '{'
146
+ for key, msg in JSON.parse(e.message)
147
+ add_error [field, key].join('.'), msg, opts
148
+ end
149
+ else
150
+ add_error field, e.message, opts
151
+ end
130
152
  end
131
153
  end
132
154
  end
@@ -15,8 +15,23 @@ module Typero
15
15
  }
16
16
 
17
17
  # default shared allowed opts keys
18
- OPTS_KEYS = [:type, :required, :array, :max_count, :default, :name, :meta, :model]
19
18
  OPTS = {}
19
+ OPTS_KEYS = [
20
+ :allow,
21
+ :allowed,
22
+ :array,
23
+ :default,
24
+ :description,
25
+ :max_count,
26
+ :meta,
27
+ :min_count,
28
+ :model,
29
+ :name,
30
+ :req,
31
+ :required,
32
+ :type,
33
+ :values
34
+ ]
20
35
 
21
36
  attr_reader :opts
22
37
 
@@ -48,7 +63,7 @@ module Typero
48
63
  ###
49
64
 
50
65
  def initialize value, opts={}, &block
51
- value = value.sub(/^\s+/, '').sub(/\s+$/, '') if value.is_a?(String)
66
+ value = value.strip.rstrip if value.is_a?(String)
52
67
 
53
68
  @value = value
54
69
  @opts = opts
@@ -3,7 +3,10 @@ class Typero::DateType < Typero::Type
3
3
  error :en, :max_date, 'Maximal allowed date is %s'
4
4
 
5
5
  def set
6
- value { |data| DateTime.parse(data) }
6
+ unless [Date].include?(value.class)
7
+ value { |data| DateTime.parse(data) }
8
+ end
9
+
7
10
  value { |data| DateTime.new(data.year, data.month, data.day) }
8
11
 
9
12
  check_date_min_max
@@ -2,7 +2,9 @@ require_relative 'date_type'
2
2
 
3
3
  class Typero::DatetimeType < Typero::DateType
4
4
  def set
5
- value { |data| DateTime.parse(data) }
5
+ unless [Time, DateTime].include?(value.class)
6
+ value { |data| DateTime.parse(data) }
7
+ end
6
8
 
7
9
  check_date_min_max
8
10
  end
@@ -2,11 +2,17 @@ class Typero::HashType < Typero::Type
2
2
  error :en, :not_hash_type_error, 'value is not hash type'
3
3
 
4
4
  def set
5
- error_for(:not_hash_type_error) unless value.is_a?(Hash)
5
+ if value.is_a?(String) && value[0,1] == '{'
6
+ @value = JSON.load(value)
7
+ end
8
+
9
+ @value ||= {}
10
+
11
+ error_for(:not_hash_type_error) unless @value.respond_to?(:keys) && @value.respond_to?(:values)
6
12
 
7
13
  if opts[:allow]
8
- for key in value.keys
9
- value.delete(key) unless opts[:allow].include?(key)
14
+ for key in @value.keys
15
+ @value.delete(key) unless opts[:allow].include?(key)
10
16
  end
11
17
  end
12
18
  end
@@ -0,0 +1,13 @@
1
+ class Typero::LocaleType < Typero::Type
2
+ error :en, :locale_bad_format, 'Locale "%s" is in bad format (should be xx or xx-xx)'
3
+
4
+ def set
5
+ error_for(:locale_bad_format, value) unless value =~ /^[\w\-]{2,5}$/
6
+ end
7
+
8
+ def db_schema
9
+ [:string, { limit: 5 }]
10
+ end
11
+ end
12
+
13
+
@@ -2,14 +2,16 @@ class Typero::ModelType < Typero::Type
2
2
  def set
3
3
  value(&:to_h)
4
4
 
5
- errors = []
5
+ errors = {}
6
6
 
7
- schema = Typero.schema(opts[:model])
8
- schema.validate(value) do |field, error|
9
- errors.push '%s (%s)' % [error, field]
7
+ schema = opts[:model].is_a?(Typero::Schema) ? opts[:model] : Typero.schema(opts[:model])
8
+
9
+ # by default models in schems are strict true (remove undefined keys)
10
+ schema.validate value, strict: true do |field, error|
11
+ errors[field] = error
10
12
  end
11
13
 
12
- raise TypeError.new errors.join(', ') if errors.first
14
+ raise TypeError.new errors.to_json if errors.keys.first
13
15
  end
14
16
 
15
17
  def db_schema
@@ -0,0 +1,5 @@
1
+ require_relative 'datetime_type'
2
+
3
+ class Typero::TimeType < Typero::DatetimeType
4
+ end
5
+
@@ -0,0 +1,15 @@
1
+ class Typero::TimezoneType < Typero::Type
2
+ error :en, :invalid_time_zone, 'Invalid time zone'
3
+
4
+ def set
5
+ TZInfo::Timezone.get(value)
6
+ rescue TZInfo::InvalidTimezoneIdentifier
7
+ error_for :invalid_time_zone
8
+ end
9
+
10
+ def db_schema
11
+ [:string, { length: 50 }]
12
+ end
13
+
14
+ end
15
+
@@ -35,29 +35,40 @@ module Typero
35
35
  end
36
36
  end
37
37
 
38
- # load type schema
39
- def schema name=nil, &block
40
- # :user -> 'User'
41
- name = name.to_s.classify if name
38
+ # load or set type schema
39
+ # Typero.schema(:blog) { ... }
40
+ # Typero.schema(:blog, type: :model) { ... }
41
+ # Typero.schema(:blog)
42
+ # Typero.schema(type: :model)
43
+ def schema name=nil, opts=nil, &block
44
+ klass = name.to_s.classify if name && !name.is_a?(Hash)
42
45
 
43
46
  if block_given?
44
47
  Typero::Schema.new(&block).tap do |schema|
45
- Typero::Schema::SCHEMAS[name] = schema if name
46
- end
47
- else
48
- raise ArgumentErorr.new('Schema name not given') unless name
48
+ if klass
49
+ Typero::Schema::SCHEMAS[klass] = schema
49
50
 
50
- schema = Typero::Schema::SCHEMAS[name]
51
- schema ||= class_finder name, :schema
52
- schema || raise('Typero schema "%s" not defined' % name)
53
- end
54
- end
55
-
56
- def export model, opts={}, &block
57
- if block_given?
58
- Exporter::EXPORTERS[model.to_s.classify] = block
51
+ if opts && opts[:type]
52
+ Typero::Schema::TYPES[opts[:type]] ||= []
53
+ Typero::Schema::TYPES[opts[:type]].push klass unless Typero::Schema::TYPES[opts[:type]].include?(klass)
54
+ end
55
+ end
56
+ end
59
57
  else
60
- Exporter.new(model, opts).render
58
+ # Schema not given, get schema
59
+ if name.is_a?(Hash)
60
+ # Typero.schema type: :model
61
+ if type = name[:type]
62
+ Typero::Schema::TYPES[type]
63
+ end
64
+ elsif klass
65
+ # Typero.schema :user
66
+ schema = Typero::Schema::SCHEMAS[klass]
67
+ schema ||= class_finder klass, :schema
68
+ schema || nil
69
+ else
70
+ raise ArgumentError, 'Schema type not defined.'
71
+ end
61
72
  end
62
73
  end
63
74
 
@@ -78,13 +89,10 @@ module Typero
78
89
  for el in args
79
90
  for separator in ['_','/']
80
91
  klass = [name, el].join(separator).classify
81
-
82
- begin
83
- return klass.constantize
84
- rescue NameError => e
85
- nil
86
- end
92
+ return klass.constantize if const_defined? klass
87
93
  end
88
94
  end
95
+
96
+ nil
89
97
  end
90
98
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typero
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dino Reic
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-02 00:00:00.000000000 Z
11
+ date: 2021-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fast_blank
@@ -34,7 +34,6 @@ files:
34
34
  - "./.version"
35
35
  - "./lib/adapters/sequel.rb"
36
36
  - "./lib/typero.rb"
37
- - "./lib/typero/exporter.rb"
38
37
  - "./lib/typero/params.rb"
39
38
  - "./lib/typero/schema.rb"
40
39
  - "./lib/typero/type/type.rb"
@@ -48,18 +47,21 @@ files:
48
47
  - "./lib/typero/type/types/image_type.rb"
49
48
  - "./lib/typero/type/types/integer_type.rb"
50
49
  - "./lib/typero/type/types/label_type.rb"
50
+ - "./lib/typero/type/types/locale_type.rb"
51
51
  - "./lib/typero/type/types/model_type.rb"
52
52
  - "./lib/typero/type/types/oib_type.rb"
53
53
  - "./lib/typero/type/types/point_type.rb"
54
54
  - "./lib/typero/type/types/string_type.rb"
55
55
  - "./lib/typero/type/types/text_type.rb"
56
+ - "./lib/typero/type/types/time_type.rb"
57
+ - "./lib/typero/type/types/timezone_type.rb"
56
58
  - "./lib/typero/type/types/url_type.rb"
57
59
  - "./lib/typero/typero.rb"
58
60
  homepage: https://github.com/dux/typero
59
61
  licenses:
60
62
  - MIT
61
63
  metadata: {}
62
- post_install_message:
64
+ post_install_message:
63
65
  rdoc_options: []
64
66
  require_paths:
65
67
  - lib
@@ -75,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
77
  version: '0'
76
78
  requirements: []
77
79
  rubygems_version: 3.0.6
78
- signing_key:
80
+ signing_key:
79
81
  specification_version: 4
80
82
  summary: Ruby type system
81
83
  test_files: []
@@ -1,93 +0,0 @@
1
- module Typero
2
- class Exporter
3
- EXPORTERS ||= {}
4
-
5
- attr_accessor :response
6
-
7
- def initialize model, opts={}
8
- opts = { user: opts } unless opts.is_a?(Hash)
9
-
10
- opts[:exporter] ||= model.class
11
- opts[:depth] ||= 1
12
- opts[:current_depth] ||= 0
13
-
14
- exporter = opts.delete(:exporter).to_s.classify
15
-
16
- @model = model
17
- @opts = opts
18
- @block = EXPORTERS[exporter] || raise('Exporter "%s" (:%s) not found' % [block, block.underscore])
19
- @response = {}
20
- end
21
-
22
- def render
23
- instance_exec &@block
24
- @response
25
- end
26
-
27
- private
28
-
29
- def export object, opts={}
30
- if object.is_a?(Symbol)
31
- return property object, export(model.send(object))
32
- end
33
-
34
- return if @opts[:current_depth] >= @opts[:depth]
35
-
36
- @opts[:current_depth] += 1
37
- out = self.class.new(object, @opts.merge(opts)).render
38
- @opts[:current_depth] -= 1
39
- out
40
- end
41
-
42
- def property name, data=:_undefined
43
- if block_given?
44
- data = yield if data == :_undefined
45
- @response[name] = data
46
- else
47
- data = data == :_undefined ? model.send(name) : data
48
-
49
- if data.respond_to?(:export_json)
50
- data = data.export_json
51
- elsif data.respond_to?(:to_h)
52
- data = data.to_h
53
- end
54
-
55
- @response[name] = data
56
- end
57
- end
58
- alias :prop :property
59
-
60
- def hproperty name
61
- @response[name] = model[name]
62
- end
63
- alias :hprop :hproperty
64
-
65
- def namespace name, &block
66
-
67
- end
68
-
69
- def meta &block
70
- namespace :meta, &block
71
- end
72
-
73
- def model
74
- @model
75
- end
76
-
77
- # get current user from globals if globals defined
78
- def user
79
- if @opts[:user]
80
- @opts[:user]
81
- elsif defined?(User) && User.respond_to?(:current)
82
- User.current
83
- elsif defined?(Current) && Current.respond_to?(:user)
84
- Current.user
85
- elsif current_user = Thread.current[:current_user]
86
- current_user
87
- else
88
- nil
89
- end
90
- end
91
- end
92
- end
93
-