typero 0.9.0 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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
-