yas 0.1.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 +7 -0
- data/.gitignore +6 -0
- data/README.markdown +127 -0
- data/Rakefile +14 -0
- data/lib/yas/errors.rb +9 -0
- data/lib/yas/ext/attribute.rb +199 -0
- data/lib/yas/ext/migrate.rb +39 -0
- data/lib/yas/ext/rename.rb +43 -0
- data/lib/yas/ext/whitelist.rb +43 -0
- data/lib/yas/hash.rb +23 -0
- data/lib/yas/helper.rb +10 -0
- data/lib/yas/schema.rb +46 -0
- data/lib/yas/version.rb +5 -0
- data/lib/yas.rb +15 -0
- data/test/ext/test_attribute.rb +200 -0
- data/test/ext/test_migrate.rb +22 -0
- data/test/ext/test_rename.rb +22 -0
- data/test/ext/test_whitelist.rb +25 -0
- data/test/helper.rb +6 -0
- data/test/test_schema.rb +97 -0
- data/yas.gemspec +23 -0
- metadata +106 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2bc6f995a8dbaa33cabd77eb2a9488ee83e9af68
|
4
|
+
data.tar.gz: 9384f3846028776890fc010f4dcda77743f44812
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ec347427f0a1fa5adef59b4f49d55328d0060ac6e137faf061dde08f14f7c22617c3d0594448a92dfbf8f961ea9992eae3a0c6197539c1465c09066655b4511c
|
7
|
+
data.tar.gz: 83e5eb5b1a739e9402d195784417a20b6fcdb27863a3fc555498a50944ded1cbc84aaf1419ce3283dd9210970b52a1a0c76b671cf152fe821836b4fa14236fd6
|
data/README.markdown
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# YAS
|
2
|
+
|
3
|
+
YAS (Yet Another Schema) is an extensible hash validator for Ruby.
|
4
|
+
Using YAS, you can enforce a specific key to be required, rename them, perform automatic type conversion, and other goodies.
|
5
|
+
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
gem install yas
|
10
|
+
|
11
|
+
|
12
|
+
## Quick Start
|
13
|
+
|
14
|
+
require 'yas'
|
15
|
+
|
16
|
+
class MySchema < YAS::Schema
|
17
|
+
rename :foo => :bar
|
18
|
+
end
|
19
|
+
|
20
|
+
h = { bar: "value" }
|
21
|
+
h.validate! MySchema
|
22
|
+
puts h[:foo] # "value"
|
23
|
+
puts h[:bar] # nil
|
24
|
+
|
25
|
+
|
26
|
+
## Extensions
|
27
|
+
|
28
|
+
You can extend the behavior of YAS by writing your own custom extensions, but `YAS::Schema` already comes with a set of awesome default extensions that you can immediately use:
|
29
|
+
|
30
|
+
* `attribute name, &block` or `key name, &block`
|
31
|
+
|
32
|
+
Declares an attribute/key with various requirements.
|
33
|
+
|
34
|
+
* `rename`
|
35
|
+
|
36
|
+
Rename keys.
|
37
|
+
|
38
|
+
* `migrate`
|
39
|
+
|
40
|
+
Migrate the value of a key.
|
41
|
+
|
42
|
+
* `whitelist`
|
43
|
+
|
44
|
+
Provide a set of keys that you only care to see.
|
45
|
+
|
46
|
+
|
47
|
+
### Attribute Extension
|
48
|
+
|
49
|
+
The Attribute extension allows you to specify certain requirements/restrictions for a given key:
|
50
|
+
|
51
|
+
class MySchema < YAS::Schema
|
52
|
+
key :email do
|
53
|
+
required
|
54
|
+
end
|
55
|
+
|
56
|
+
key :first_name do
|
57
|
+
type String
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
hash = { :first_name => 'John' }
|
62
|
+
hash.validate! MySchema # raises YAS::ValidationError "Key 'email' is missing"
|
63
|
+
hash.merge!(:email => 'john@email.com')
|
64
|
+
hash.validate! MySchema # Success!
|
65
|
+
|
66
|
+
List of directives you can use:
|
67
|
+
|
68
|
+
* `required`
|
69
|
+
|
70
|
+
Sets this key as required. Will raise an error if key is missing.
|
71
|
+
|
72
|
+
* `type(T)`
|
73
|
+
|
74
|
+
Sets the type of this key. Will perform type check if specified. Can be nested if type is a YAS::Schema!
|
75
|
+
|
76
|
+
* `auto_convert`
|
77
|
+
|
78
|
+
Enables auto-conversion to the specified type. This gets ignored if `type` is not specified.
|
79
|
+
|
80
|
+
* `default(&block)`
|
81
|
+
|
82
|
+
Runs the block to set the default value for this key, if it's missing or nil.
|
83
|
+
|
84
|
+
* `validate_value(&block)`
|
85
|
+
|
86
|
+
Custom validation method to check the value of a key. This is useful in cases where you only want certain values to be stored (e.g a number between 1-10 only)
|
87
|
+
|
88
|
+
|
89
|
+
### Rename Extension
|
90
|
+
|
91
|
+
Using `rename` to rename keys. Useful to maintain hash integrity.
|
92
|
+
|
93
|
+
class UserSchema < YAS::Schema
|
94
|
+
rename :username => :nickname
|
95
|
+
end
|
96
|
+
hash = { :username => 'jdoe' } )
|
97
|
+
hash.validate!(UserSchema)
|
98
|
+
hash[:nickname] # 'jdoe'
|
99
|
+
|
100
|
+
|
101
|
+
### Migrate Extension
|
102
|
+
|
103
|
+
You can also migrate the values. This is useful if you have hash whose values are in the old format and you want to convert them to the new format.
|
104
|
+
|
105
|
+
class UserSchema < YAS::Schema
|
106
|
+
migrate :nicknames do |v|
|
107
|
+
v.class == String ? [v] : v
|
108
|
+
end
|
109
|
+
end
|
110
|
+
hash = { :nicknames => 'jdoe' }
|
111
|
+
hash.validate!(UserSchema)
|
112
|
+
hash[:nicknames] # ['jdoe']
|
113
|
+
|
114
|
+
|
115
|
+
### Whitelist Extension
|
116
|
+
|
117
|
+
Whitelist allows you to remove unneeded keys.
|
118
|
+
|
119
|
+
class UserSchema < YAS::Schema
|
120
|
+
whitelist :name, :address
|
121
|
+
end
|
122
|
+
hash = { :name => 'jdoe', :address => '123 Main St', :phone => '9990000000', :comment => 'JDoe is cool' }
|
123
|
+
hash.validate!(UserSchema)
|
124
|
+
hash[:name] # ['jdoe']
|
125
|
+
hash[:address] # ['123 Main St']
|
126
|
+
hash[:phone] # nil
|
127
|
+
hash[:comment] # nil
|
data/Rakefile
ADDED
data/lib/yas/errors.rb
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
# Defines specific rules for keys
|
2
|
+
#
|
3
|
+
|
4
|
+
class YAS::AttributeExt
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
|
8
|
+
def key attr, &block
|
9
|
+
attr = attr.to_s.to_sym
|
10
|
+
new_attr = Attribute.new(attr)
|
11
|
+
new_attr.instance_eval &block if block
|
12
|
+
attributes[attr] = new_attr
|
13
|
+
new_attr
|
14
|
+
end
|
15
|
+
alias_method :attribute, :key
|
16
|
+
|
17
|
+
|
18
|
+
def attributes
|
19
|
+
@attributes ||= {}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def self.when_used schema
|
25
|
+
schema.extend ClassMethods
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def self.when_schema_inherited superschema, subschema
|
30
|
+
superschema.attributes.each do |key, attr|
|
31
|
+
subschema.attributes[key] = attr
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def self.apply schema, hash
|
37
|
+
schema.attributes.each do |key, attr|
|
38
|
+
raise YAS::ValidationError, "Key #{key} is required" if attr.required? && !hash.has_key?(key)
|
39
|
+
hash.has_key?(key) and
|
40
|
+
hash[key] = attr.validate(hash[key])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
class Attribute
|
46
|
+
|
47
|
+
attr_reader :name
|
48
|
+
|
49
|
+
@required = false
|
50
|
+
@type = nil
|
51
|
+
@auto_convert = false
|
52
|
+
@default_block = nil
|
53
|
+
@check_value_block = nil
|
54
|
+
|
55
|
+
|
56
|
+
def initialize name
|
57
|
+
@name = name.to_s.to_sym
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
# Directive to mark this Attribute as required.
|
62
|
+
#
|
63
|
+
def required
|
64
|
+
@required = true
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def required?
|
70
|
+
@required
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
# Directive to enforce the data type of this Attribute.
|
75
|
+
#
|
76
|
+
def type t
|
77
|
+
@type = t
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# Attempts to auto convert values to the type specified by the {type}
|
83
|
+
# directive if the value is not of the same type.
|
84
|
+
# Has no effect if {type} is not specified.
|
85
|
+
#
|
86
|
+
def auto_convert
|
87
|
+
@auto_convert = true
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# Directive to set the default value of this Attribute. Once specified, if
|
93
|
+
# this Attribute does not exist during the Document initialization, whether
|
94
|
+
# that's from {Document.get} or {Document#initialize}, the value of the
|
95
|
+
# Attribute will be set to the value returned by the block.
|
96
|
+
#
|
97
|
+
# @yield Sets the value of this Attribute to the value returned by the
|
98
|
+
# block.
|
99
|
+
#
|
100
|
+
def default &block
|
101
|
+
@default_block = block
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
def has_default?
|
107
|
+
@default_block != nil
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
# Perform a validation over the value of this Attribute.
|
112
|
+
#
|
113
|
+
# @yield [v] Value of the Attribute.
|
114
|
+
#
|
115
|
+
# @example
|
116
|
+
# validate_value { |v| v == "John" }
|
117
|
+
#
|
118
|
+
def validate_value &block
|
119
|
+
@check_value_block = block
|
120
|
+
self
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
# Check the default value.
|
125
|
+
#
|
126
|
+
# @param value The value of this attribute to validate.
|
127
|
+
#
|
128
|
+
def trigger_default_directive
|
129
|
+
@default_block.call if has_default?
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
# Check value.
|
134
|
+
#
|
135
|
+
def trigger_content_directive value
|
136
|
+
if @check_value_block
|
137
|
+
raise YAS::ValidationError, "Content validation error: #{value}" unless
|
138
|
+
@check_value_block.call(value)
|
139
|
+
end
|
140
|
+
value
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
# Check type.
|
145
|
+
#
|
146
|
+
def trigger_type_directive value
|
147
|
+
if @type
|
148
|
+
# Run auto-conversion first.
|
149
|
+
value = trigger_auto_convert_directive(value)
|
150
|
+
|
151
|
+
msg = "Type mismatch for attribute #{@name}"
|
152
|
+
if @type <= YAS::Schema
|
153
|
+
value = @type.validate(value)
|
154
|
+
elsif @type == TrueClass || @type == FalseClass
|
155
|
+
raise YAS::ValidationError, msg unless !!value == value
|
156
|
+
else
|
157
|
+
raise YAS::ValidationError, msg unless value.is_a?(@type)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
value
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
# Auto convert the value
|
166
|
+
#
|
167
|
+
def trigger_auto_convert_directive value
|
168
|
+
if @auto_convert
|
169
|
+
if @type == Integer
|
170
|
+
value = Integer(value)
|
171
|
+
elsif @type == Float
|
172
|
+
value = Float(value)
|
173
|
+
elsif @type == String
|
174
|
+
value = String(value)
|
175
|
+
elsif @type == Array
|
176
|
+
value = Array(value)
|
177
|
+
elsif @type == Time && !value.is_a?(Time)
|
178
|
+
value = Time.parse(value)
|
179
|
+
elsif @type == TrueClass || @type == FalseClass
|
180
|
+
value = value == "1" || value == 1 || value.to_s.downcase == "true" || value == true ? true : false
|
181
|
+
end
|
182
|
+
end
|
183
|
+
value
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
# Validates attribute, except the required directive.
|
188
|
+
# First it executes the {default} directive, then {auto_convert} to type,
|
189
|
+
# then {type} validation, then finally the {validate} directive.
|
190
|
+
#
|
191
|
+
# @return The final value after the validation.
|
192
|
+
#
|
193
|
+
def validate value
|
194
|
+
value = trigger_default_directive if value.nil?
|
195
|
+
trigger_content_directive(trigger_type_directive(value))
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Migrate the value of a key
|
2
|
+
#
|
3
|
+
|
4
|
+
class YAS::MigrateExt
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
|
8
|
+
def migrate key, &block
|
9
|
+
migrate_keys[key] = block
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def migrate_keys
|
14
|
+
@migrate_keys ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def self.when_used schema
|
21
|
+
schema.extend ClassMethods
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def self.when_schema_inherited superschema, subschema
|
26
|
+
superschema.migrate_keys.each do |key, pr|
|
27
|
+
subschema.migrate_keys[key] = pr
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def self.apply schema, hash
|
33
|
+
schema.migrate_keys.each do |key, pr|
|
34
|
+
hash.has_key?(key) and
|
35
|
+
hash[key] = pr.call(hash[key])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Rename keys from one to another
|
2
|
+
#
|
3
|
+
# Usage:
|
4
|
+
#
|
5
|
+
# To rename `fname` to `first_name`, and `lname` to `last_name`
|
6
|
+
# rename :fname => :first_name, :lname => :last_name
|
7
|
+
#
|
8
|
+
|
9
|
+
class YAS::RenameExt
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def rename map
|
13
|
+
smap = YAS.symbolize(map)
|
14
|
+
rename_keys.merge!(smap)
|
15
|
+
end
|
16
|
+
|
17
|
+
def rename_keys
|
18
|
+
@rename_keys ||= {}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def self.when_used schema
|
24
|
+
schema.extend ClassMethods
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def self.when_schema_inherited superschema, subschema
|
29
|
+
superschema.rename_keys.each do |from, value|
|
30
|
+
subschema.rename_keys[key] = value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def self.apply schema, hash
|
36
|
+
schema.rename_keys.each do |from, to|
|
37
|
+
hash.has_key?(from) and
|
38
|
+
hash[to] = hash[from] and
|
39
|
+
hash.delete(from)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Whitelist hash to only contain a set of keys
|
2
|
+
#
|
3
|
+
|
4
|
+
class YAS::WhitelistExt
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def whitelist *keys
|
8
|
+
keys = Array(keys)
|
9
|
+
keys.flatten!
|
10
|
+
keys.uniq!
|
11
|
+
keys.compact!
|
12
|
+
keys.each do |k| whitelist_keys << k; end
|
13
|
+
whitelist_keys.flatten!
|
14
|
+
whitelist_keys.uniq!
|
15
|
+
whitelist_keys.compact!
|
16
|
+
end
|
17
|
+
|
18
|
+
def whitelist_keys
|
19
|
+
@whitelist_keys ||= []
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def self.when_used schema
|
25
|
+
schema.extend ClassMethods
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def self.when_schema_inherited superschema, subschema
|
30
|
+
superschema.whitelist_keys.each do |key|
|
31
|
+
subschema.whitelist_keys << key
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def self.apply schema, hash
|
37
|
+
hash.delete_if do |k, v|
|
38
|
+
!schema.whitelist_keys.include?(k)
|
39
|
+
end unless schema.whitelist_keys.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
end
|
data/lib/yas/hash.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
def validate schema
|
4
|
+
copy = Marshal.load(Marshal.dump(self))
|
5
|
+
schema.validate(copy) if schema.respond_to?(:validate)
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
def validate! schema
|
10
|
+
schema.validate(self) if schema.respond_to?(:validate)
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def slice *keys
|
15
|
+
keys.flatten!
|
16
|
+
data = keys.inject({}) do |memo, key|
|
17
|
+
memo[key] = self[key] if self[key]
|
18
|
+
memo
|
19
|
+
end
|
20
|
+
data
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/lib/yas/helper.rb
ADDED
data/lib/yas/schema.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
class YAS::SchemaBase
|
2
|
+
|
3
|
+
def self.use ext
|
4
|
+
raise YAS::ExtensionError, "Duplicate Extension" if exts.any? { |e| e == ext }
|
5
|
+
raise YAS::ExtensionError, "Wrong Extension Format" unless ext.respond_to?(:apply) && ext.respond_to?(:when_used)
|
6
|
+
exts << ext
|
7
|
+
ext.when_used(self)
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def self.exts
|
12
|
+
@exts ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def self.validate hash
|
17
|
+
exts.each do |ext|
|
18
|
+
ext.apply(self, hash)
|
19
|
+
end
|
20
|
+
hash
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def self.inspect
|
25
|
+
exts.inspect
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
# Inherited hook to inherit all extensions.
|
30
|
+
def self.inherited subclass
|
31
|
+
exts.each do |ext|
|
32
|
+
subclass.use(ext)
|
33
|
+
ext.when_schema_inherited(self, subclass) if ext.respond_to?(:when_schema_inherited)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class YAS::Schema < YAS::SchemaBase
|
40
|
+
|
41
|
+
use YAS::RenameExt
|
42
|
+
use YAS::WhitelistExt
|
43
|
+
use YAS::MigrateExt
|
44
|
+
use YAS::AttributeExt
|
45
|
+
|
46
|
+
end
|
data/lib/yas/version.rb
ADDED
data/lib/yas.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'json'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
require 'yas/version'
|
6
|
+
require 'yas/errors'
|
7
|
+
require 'yas/helper'
|
8
|
+
|
9
|
+
require 'yas/ext/rename'
|
10
|
+
require 'yas/ext/attribute'
|
11
|
+
require 'yas/ext/migrate'
|
12
|
+
require 'yas/ext/whitelist'
|
13
|
+
|
14
|
+
require 'yas/schema'
|
15
|
+
require 'yas/hash'
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require './test/helper.rb'
|
2
|
+
|
3
|
+
class TestAttribute < Minitest::Test
|
4
|
+
|
5
|
+
Attribute = YAS::AttributeExt::Attribute
|
6
|
+
|
7
|
+
def test_required
|
8
|
+
attr = Attribute.new(:batman).required
|
9
|
+
assert_equal true, attr.required?
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def test_type
|
14
|
+
attr = Attribute.new(:batman).type(String)
|
15
|
+
assert_equal "hello", attr.validate("hello")
|
16
|
+
assert_raises YAS::ValidationError do
|
17
|
+
attr.validate(1)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def test_auto_convert_integer
|
23
|
+
attr = Attribute.new(:batman).type(Integer).auto_convert
|
24
|
+
assert_equal 1000, attr.validate(1000)
|
25
|
+
assert_equal 1000, attr.validate("1000")
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def test_auto_convert_float
|
30
|
+
attr = Attribute.new(:batman).type(Float).auto_convert
|
31
|
+
assert_equal 1000.01, attr.validate(1000.01)
|
32
|
+
assert_equal 1000.01, attr.validate("1000.01")
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def test_auto_convert_string
|
37
|
+
attr = Attribute.new(:batman).type(String).auto_convert
|
38
|
+
assert_equal "hello", attr.validate("hello")
|
39
|
+
assert_equal "1", attr.validate(1)
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def test_auto_convert_array
|
44
|
+
attr = Attribute.new(:batman).type(Array).auto_convert
|
45
|
+
assert_equal ["hello"], attr.validate(["hello"])
|
46
|
+
assert_equal [1], attr.validate(1)
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def test_auto_convert_time
|
51
|
+
attr = Attribute.new(:batman).type(Time).auto_convert
|
52
|
+
time = attr.validate("2014/7/14")
|
53
|
+
assert time.is_a?(Time)
|
54
|
+
assert_equal 2014, time.year
|
55
|
+
assert_equal 7, time.month
|
56
|
+
assert_equal 14, time.day
|
57
|
+
|
58
|
+
time = attr.validate(Time.now.utc)
|
59
|
+
assert time
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def test_auto_convert_boolean
|
64
|
+
attr = Attribute.new(:batman).type(TrueClass).auto_convert
|
65
|
+
assert_equal false, attr.validate("false")
|
66
|
+
assert_equal false, attr.validate("123")
|
67
|
+
assert_equal true, attr.validate(1)
|
68
|
+
assert_equal true, attr.validate("true")
|
69
|
+
assert_equal true, attr.validate("1")
|
70
|
+
assert_equal false, attr.validate(0)
|
71
|
+
assert_equal false, attr.validate(nil)
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
def test_default
|
76
|
+
attr = Attribute.new(:batman).default { "John" }
|
77
|
+
assert_equal "John", attr.validate(nil)
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def test_has_default
|
82
|
+
attr = Attribute.new(:batman).default { "John" }
|
83
|
+
assert_equal true, attr.has_default?
|
84
|
+
|
85
|
+
no_default_attr = Attribute.new(:batman)
|
86
|
+
assert_equal false, no_default_attr.has_default?
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
def test_content
|
91
|
+
attr = Attribute.new(:batman).validate_value { |v| v == "John" }
|
92
|
+
assert_raises YAS::ValidationError do
|
93
|
+
attr.validate("Jim")
|
94
|
+
end
|
95
|
+
attr.validate("John")
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
def test_all
|
100
|
+
attr = Attribute.new(:batman)
|
101
|
+
.required
|
102
|
+
.type(Integer)
|
103
|
+
.default { 0 }
|
104
|
+
.validate_value { |v| v >= 0 }
|
105
|
+
|
106
|
+
assert_equal true, attr.required?
|
107
|
+
assert_equal 0, attr.validate(nil)
|
108
|
+
assert_equal 100, attr.validate(100)
|
109
|
+
assert_raises YAS::ValidationError do
|
110
|
+
attr.validate(-100)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
def test_validate_maintain_value_if_type_not_specified
|
116
|
+
attr = Attribute.new(:batman)
|
117
|
+
[true, false, :symbol, 1, 0, -1, 10.0, "string", 1293.1, 0x90, Time.now, nil].each do |v|
|
118
|
+
assert_equal v, attr.validate(v)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
class AttributeSchema < YAS::Schema
|
126
|
+
|
127
|
+
key :name do
|
128
|
+
required
|
129
|
+
type String
|
130
|
+
auto_convert
|
131
|
+
default { "joe" }
|
132
|
+
validate_value { |v| v.length < 10 }
|
133
|
+
end
|
134
|
+
|
135
|
+
key :number do
|
136
|
+
required
|
137
|
+
type Integer
|
138
|
+
auto_convert
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
class NestedAttributeSchema < YAS::Schema
|
145
|
+
|
146
|
+
key :personal_info do
|
147
|
+
required
|
148
|
+
type AttributeSchema
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
class TestAttributeExt < Minitest::Test
|
155
|
+
|
156
|
+
def test_attribute_ext
|
157
|
+
hash = {
|
158
|
+
name: "someone",
|
159
|
+
number: 10,
|
160
|
+
}
|
161
|
+
hash.validate! AttributeSchema
|
162
|
+
assert_equal "someone", hash[:name]
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
def test_attribute_ext_validate_value
|
167
|
+
hash = {
|
168
|
+
name: "someone with long name",
|
169
|
+
number: 99,
|
170
|
+
}
|
171
|
+
assert_raises YAS::ValidationError do
|
172
|
+
hash.validate! AttributeSchema
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
def test_attribute_ext_autoconvert_and_nil_attribute
|
178
|
+
hash = {
|
179
|
+
name: nil,
|
180
|
+
number: "10"
|
181
|
+
}
|
182
|
+
hash.validate! AttributeSchema
|
183
|
+
assert_equal 10, hash[:number]
|
184
|
+
assert_equal "joe", hash[:name]
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def test_nested_attribute_schema
|
189
|
+
hash = {
|
190
|
+
personal_info: {
|
191
|
+
name: nil,
|
192
|
+
number: "10"
|
193
|
+
}
|
194
|
+
}
|
195
|
+
hash.validate! NestedAttributeSchema
|
196
|
+
assert_equal 10, hash[:personal_info][:number]
|
197
|
+
assert_equal "joe", hash[:personal_info][:name]
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require './test/helper.rb'
|
2
|
+
|
3
|
+
|
4
|
+
class MigrateSchema < YAS::Schema
|
5
|
+
migrate :names do |v|
|
6
|
+
v = Array(v) if !v.is_a?(Array)
|
7
|
+
v
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
class TestMigrateExt < Minitest::Test
|
13
|
+
|
14
|
+
def test_migrate_ext
|
15
|
+
hash = {
|
16
|
+
names: "john"
|
17
|
+
}
|
18
|
+
hash.validate! MigrateSchema
|
19
|
+
assert_equal ["john"], hash[:names]
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require './test/helper.rb'
|
2
|
+
|
3
|
+
class RenameSchema < YAS::Schema
|
4
|
+
rename :addr => :address
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
class TestRenameExt < Minitest::Test
|
9
|
+
|
10
|
+
def test_rename_ext
|
11
|
+
hash = {
|
12
|
+
addr: "Some address",
|
13
|
+
untouched: "Nothing"
|
14
|
+
}
|
15
|
+
hash.validate! RenameSchema
|
16
|
+
assert_equal "Some address", hash[:address]
|
17
|
+
assert_equal "Nothing", hash[:untouched]
|
18
|
+
assert_nil hash[:addr]
|
19
|
+
assert_equal 2, hash.length
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require './test/helper.rb'
|
2
|
+
|
3
|
+
|
4
|
+
class WhitelistSchema < YAS::Schema
|
5
|
+
whitelist :first_name, :last_name, :address
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
class TestWhitelistExt < Minitest::Test
|
10
|
+
|
11
|
+
def test_whitelist_ext
|
12
|
+
hash = {
|
13
|
+
first_name: "john",
|
14
|
+
last_name: "doe",
|
15
|
+
address: "123 Main St",
|
16
|
+
invalid: "will get removed",
|
17
|
+
}
|
18
|
+
hash.validate! WhitelistSchema
|
19
|
+
assert_equal "john", hash[:first_name]
|
20
|
+
assert_equal "doe", hash[:last_name]
|
21
|
+
assert_equal "123 Main St", hash[:address]
|
22
|
+
assert_nil hash[:invalid]
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/test/helper.rb
ADDED
data/test/test_schema.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require './test/helper.rb'
|
2
|
+
|
3
|
+
|
4
|
+
class MigrateSchema < YAS::Schema
|
5
|
+
migrate :names do |v|
|
6
|
+
v = Array(v) if !v.is_a?(Array)
|
7
|
+
v
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
class InheritedSchema < MigrateSchema
|
13
|
+
rename :book => :books
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
class ComplexSchema < MigrateSchema
|
18
|
+
|
19
|
+
whitelist :name, :contact_name, :contact_email, :redirect_uris, :active
|
20
|
+
|
21
|
+
attribute :name do
|
22
|
+
required
|
23
|
+
type String
|
24
|
+
end
|
25
|
+
|
26
|
+
attribute :contact_name do
|
27
|
+
required
|
28
|
+
type String
|
29
|
+
end
|
30
|
+
|
31
|
+
rename :email => :contact_email
|
32
|
+
attribute :contact_email do
|
33
|
+
required
|
34
|
+
type String
|
35
|
+
end
|
36
|
+
|
37
|
+
rename :redirect_uri => :redirect_uris
|
38
|
+
attribute :redirect_uris do
|
39
|
+
required
|
40
|
+
type Array
|
41
|
+
auto_convert
|
42
|
+
end
|
43
|
+
migrate :redirect_uris do |v|
|
44
|
+
v = Array(v) if !v.is_a?(Array)
|
45
|
+
v
|
46
|
+
end
|
47
|
+
|
48
|
+
attribute :active do
|
49
|
+
required
|
50
|
+
type TrueClass
|
51
|
+
auto_convert
|
52
|
+
default { true }
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
class TestSchema < Minitest::Test
|
59
|
+
|
60
|
+
def test_validate_not_bang_does_not_overwrite_original
|
61
|
+
hash = {
|
62
|
+
names: "john",
|
63
|
+
}
|
64
|
+
result = hash.validate MigrateSchema
|
65
|
+
assert_equal ["john"], result[:names]
|
66
|
+
assert_equal "john", hash[:names]
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_inherited_schema_should_inherit_extensions
|
70
|
+
hash = {
|
71
|
+
names: "john",
|
72
|
+
book: "Dolly",
|
73
|
+
}
|
74
|
+
hash.validate! InheritedSchema
|
75
|
+
assert_equal ["john"], hash[:names]
|
76
|
+
assert_equal "Dolly", hash[:books]
|
77
|
+
assert_nil hash[:book]
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_complex_schema
|
81
|
+
hash = {
|
82
|
+
name: "john",
|
83
|
+
contact_name: "someone else",
|
84
|
+
email: "jdoe@example.com",
|
85
|
+
redirect_uri: "jdoe.com",
|
86
|
+
active: 1,
|
87
|
+
}
|
88
|
+
hash.validate! ComplexSchema
|
89
|
+
assert_equal "john", hash[:name]
|
90
|
+
assert_equal "someone else", hash[:contact_name]
|
91
|
+
assert_equal "jdoe@example.com", hash[:contact_email]
|
92
|
+
assert_nil hash[:email]
|
93
|
+
assert_equal ["jdoe.com"], hash[:redirect_uris]
|
94
|
+
assert_equal true, hash[:active]
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
data/yas.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require 'yas/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "#{YAS::GEM_NAME}"
|
6
|
+
s.version = YAS::VERSION
|
7
|
+
s.authors = ["Albert Tedja"]
|
8
|
+
s.email = "nicho_tedja@yahoo.com"
|
9
|
+
s.homepage = "https://github.com/atedja/yahs"
|
10
|
+
s.summary = "Yet Another Schema for Ruby."
|
11
|
+
s.description = "#{YAS::NAME} is a Ruby hash schema and validator."
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test}/*`.split("\n")
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
|
17
|
+
s.required_ruby_version = '~> 2.0'
|
18
|
+
s.add_development_dependency 'minitest', '~> 5.3'
|
19
|
+
s.add_development_dependency 'mocha', '~> 1.1'
|
20
|
+
s.add_development_dependency 'rake', '~> 10.3'
|
21
|
+
|
22
|
+
s.license = "Apache"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yas
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Albert Tedja
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mocha
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.1'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.3'
|
55
|
+
description: YAS is a Ruby hash schema and validator.
|
56
|
+
email: nicho_tedja@yahoo.com
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- ".gitignore"
|
62
|
+
- README.markdown
|
63
|
+
- Rakefile
|
64
|
+
- lib/yas.rb
|
65
|
+
- lib/yas/errors.rb
|
66
|
+
- lib/yas/ext/attribute.rb
|
67
|
+
- lib/yas/ext/migrate.rb
|
68
|
+
- lib/yas/ext/rename.rb
|
69
|
+
- lib/yas/ext/whitelist.rb
|
70
|
+
- lib/yas/hash.rb
|
71
|
+
- lib/yas/helper.rb
|
72
|
+
- lib/yas/schema.rb
|
73
|
+
- lib/yas/version.rb
|
74
|
+
- test/ext/test_attribute.rb
|
75
|
+
- test/ext/test_migrate.rb
|
76
|
+
- test/ext/test_rename.rb
|
77
|
+
- test/ext/test_whitelist.rb
|
78
|
+
- test/helper.rb
|
79
|
+
- test/test_schema.rb
|
80
|
+
- yas.gemspec
|
81
|
+
homepage: https://github.com/atedja/yahs
|
82
|
+
licenses:
|
83
|
+
- Apache
|
84
|
+
metadata: {}
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - "~>"
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '2.0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 2.4.8
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: Yet Another Schema for Ruby.
|
105
|
+
test_files: []
|
106
|
+
has_rdoc:
|