yas 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|