yema 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +70 -0
- data/Rakefile +1 -0
- data/lib/yema.rb +33 -0
- data/lib/yema/error.rb +11 -0
- data/lib/yema/exceptions.rb +3 -0
- data/lib/yema/registry/rule.rb +26 -0
- data/lib/yema/rule.rb +32 -0
- data/lib/yema/rule/format.rb +29 -0
- data/lib/yema/rule/inclusion.rb +29 -0
- data/lib/yema/rule/length.rb +34 -0
- data/lib/yema/rule/required.rb +17 -0
- data/lib/yema/rule/strong_type.rb +58 -0
- data/lib/yema/validations.rb +30 -0
- data/lib/yema/validator.rb +25 -0
- data/lib/yema/version.rb +3 -0
- data/lib/yema/virtus/attribute.rb +48 -0
- data/lib/yema/virtus/attribute/array.rb +12 -0
- data/lib/yema/virtus/attribute/numeric.rb +12 -0
- data/lib/yema/virtus/attribute/string.rb +16 -0
- data/lib/yema/virtus/builder.rb +17 -0
- data/lib/yema/virtus/builder/format.rb +13 -0
- data/lib/yema/virtus/builder/inclusion.rb +13 -0
- data/lib/yema/virtus/builder/length.rb +13 -0
- data/lib/yema/virtus/builder/required.rb +13 -0
- data/lib/yema/virtus/builder/strong_type.rb +18 -0
- data/lib/yema/virtus/validations.rb +28 -0
- data/spec/integration/rule/format_spec.rb +23 -0
- data/spec/integration/rule/inclusion_spec.rb +28 -0
- data/spec/integration/rule/length_spec.rb +40 -0
- data/spec/integration/rule/required_spec.rb +22 -0
- data/spec/integration/rule/strong_type_spec.rb +27 -0
- data/spec/integration/virtus/combination_spec.rb +58 -0
- data/spec/integration/virtus/format_spec.rb +22 -0
- data/spec/integration/virtus/inclusion_spec.rb +26 -0
- data/spec/integration/virtus/length_spec.rb +36 -0
- data/spec/integration/virtus/required_spec.rb +21 -0
- data/spec/integration/virtus/strong_type_spec.rb +40 -0
- data/spec/shared/invalid_options.rb +6 -0
- data/spec/shared/invalid_resource.rb +6 -0
- data/spec/shared/valid_options.rb +6 -0
- data/spec/shared/valid_resource.rb +6 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/unit/rule/format/valid_options_spec.rb +13 -0
- data/spec/unit/rule/inclusion/valid_options_spec.rb +15 -0
- data/spec/unit/rule/length/valid_options_spec.rb +16 -0
- data/spec/unit/rule/required_options_spec.rb +18 -0
- data/spec/unit/rule/strong_type/valid_options_spec.rb +13 -0
- data/spec/unit/validations/rules_spec.rb +15 -0
- data/spec/unit/validations/valid_spec.rb +41 -0
- data/spec/unit/validator/errors_spec.rb +26 -0
- data/spec/unit/validator/valid_predicate_spec.rb +31 -0
- data/spec/unit/virtus/not_supported_type_spec.rb +11 -0
- data/yema.gemspec +26 -0
- metadata +210 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module Yema
|
2
|
+
class Validator
|
3
|
+
|
4
|
+
attr_reader :rule, :resource
|
5
|
+
|
6
|
+
def initialize(rule, resource)
|
7
|
+
@rule, @resource = rule, resource
|
8
|
+
end
|
9
|
+
|
10
|
+
def value
|
11
|
+
resource.public_send(rule.attribute_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid?
|
15
|
+
rule.matches?(value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def errors
|
19
|
+
errors = []
|
20
|
+
errors << Error.new(rule, resource) unless valid?
|
21
|
+
errors.to_set
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/lib/yema/version.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Yema
|
2
|
+
module Virtus
|
3
|
+
class Attribute
|
4
|
+
|
5
|
+
attr_reader :attribute
|
6
|
+
DEFAULT_BUILDERS = [ Builder::StrongType, Builder::Required ].freeze
|
7
|
+
|
8
|
+
def initialize(attribute)
|
9
|
+
@attribute = attribute
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.specific_builders
|
13
|
+
[].freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.builders
|
17
|
+
DEFAULT_BUILDERS + specific_builders
|
18
|
+
end
|
19
|
+
|
20
|
+
def rules
|
21
|
+
rules = []
|
22
|
+
self.class.builders.each do |builder|
|
23
|
+
rule = builder.new(attribute).build
|
24
|
+
rules << rule if rule
|
25
|
+
end
|
26
|
+
rules.to_set
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.build(attribute)
|
30
|
+
klass = determine_type(attribute)
|
31
|
+
klass.new(attribute)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# TODO: find a better way for mapping virtus type
|
37
|
+
def self.determine_type(attribute)
|
38
|
+
if attribute.kind_of?(::Virtus::Attribute::String); self::String
|
39
|
+
elsif attribute.kind_of?(::Virtus::Attribute::Numeric); self::Numeric
|
40
|
+
elsif attribute.kind_of?(::Virtus::Attribute::Array); self::Array
|
41
|
+
elsif attribute.kind_of?(::Virtus::Attribute::Boolean); self
|
42
|
+
else
|
43
|
+
raise TypeError, "#{attribute.class} is not supported"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Yema
|
2
|
+
module Virtus
|
3
|
+
class Builder
|
4
|
+
class StrongType < self
|
5
|
+
|
6
|
+
def build
|
7
|
+
strict = options.fetch(:strict, :high)
|
8
|
+
unless strict == :none
|
9
|
+
Yema::Rule::StrongType.new(attribute.name,
|
10
|
+
type: attribute.class.primitive,
|
11
|
+
member_type: attribute.options[:member_type],
|
12
|
+
strict: strict)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Yema
|
2
|
+
module Virtus
|
3
|
+
module Validations
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.instance_eval do
|
7
|
+
include ::Virtus::ValueObject
|
8
|
+
include Yema::Validations
|
9
|
+
extend ClassMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def virtus_add_attribute(attribute)
|
18
|
+
super
|
19
|
+
build_validation(attribute)
|
20
|
+
end
|
21
|
+
|
22
|
+
def build_validation(attribute)
|
23
|
+
rules.merge(Yema::Virtus::Attribute.build(attribute).rules)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Yema::Rule::Format, '#errors' do
|
4
|
+
|
5
|
+
subject { rule.errors(resource) }
|
6
|
+
|
7
|
+
let(:rule) { described_class.new(attribute, options) }
|
8
|
+
let(:resource) { stub('resource', attribute => value) }
|
9
|
+
let(:attribute) { :foo }
|
10
|
+
let(:options) { { format: /foo/ } }
|
11
|
+
|
12
|
+
[
|
13
|
+
'foo',
|
14
|
+
'afoo',
|
15
|
+
'fooa',
|
16
|
+
'afooa',
|
17
|
+
].each do |value|
|
18
|
+
it_behaves_like "valid resource", value
|
19
|
+
end
|
20
|
+
|
21
|
+
it_behaves_like "invalid resource", 'abcdds'
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Yema::Rule::Inclusion, '#errors' do
|
4
|
+
|
5
|
+
subject { rule.errors(resource) }
|
6
|
+
|
7
|
+
let(:rule) { described_class.new(attribute, options) }
|
8
|
+
let(:resource) { stub('resource', attribute => value) }
|
9
|
+
let(:attribute) { :foo }
|
10
|
+
|
11
|
+
context "with range options" do
|
12
|
+
let(:options) { { within: 3..6 } }
|
13
|
+
|
14
|
+
[3, 5].each do |value|
|
15
|
+
it_behaves_like "valid resource", value
|
16
|
+
end
|
17
|
+
|
18
|
+
it_behaves_like "invalid resource", 2
|
19
|
+
end
|
20
|
+
|
21
|
+
context "with array options" do
|
22
|
+
let(:options) { { within: ["a", "b"] } }
|
23
|
+
|
24
|
+
it_behaves_like "valid resource", "a"
|
25
|
+
it_behaves_like "invalid resource", "c"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Yema::Rule::Length, '#errors' do
|
4
|
+
|
5
|
+
subject { rule.errors(resource) }
|
6
|
+
|
7
|
+
let(:rule) { described_class.new(attribute, options) }
|
8
|
+
let(:resource) { stub('resource', attribute => value) }
|
9
|
+
let(:attribute) { :foo }
|
10
|
+
let(:options) { { length: 5 } }
|
11
|
+
|
12
|
+
it_behaves_like "valid resource", "asdfg"
|
13
|
+
it_behaves_like "invalid resource", "longer_string"
|
14
|
+
it_behaves_like "invalid resource", ""
|
15
|
+
|
16
|
+
[
|
17
|
+
nil,
|
18
|
+
false,
|
19
|
+
true,
|
20
|
+
10000,
|
21
|
+
].each do |value|
|
22
|
+
context "with invalid value: #{value.inspect}" do
|
23
|
+
let(:value) { value }
|
24
|
+
specify { expect{subject}.to raise_error(ArgumentError) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when options is a range" do
|
29
|
+
let(:options) { { length: 3..6 } }
|
30
|
+
|
31
|
+
[
|
32
|
+
"abc",
|
33
|
+
"abcabc",
|
34
|
+
].each do |value|
|
35
|
+
it_behaves_like "valid resource", value
|
36
|
+
end
|
37
|
+
it_behaves_like "invalid resource", "abcabca"
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Yema::Rule::Required, '#errors' do
|
4
|
+
|
5
|
+
subject { rule.errors(resource) }
|
6
|
+
|
7
|
+
let(:rule) { described_class.new(attribute, options) }
|
8
|
+
let(:resource) { stub('resource', attribute => value) }
|
9
|
+
let(:attribute) { :foo }
|
10
|
+
let(:options) { {} }
|
11
|
+
|
12
|
+
[
|
13
|
+
nil,
|
14
|
+
"",
|
15
|
+
{},
|
16
|
+
[],
|
17
|
+
].each do |value|
|
18
|
+
it_behaves_like "invalid resource", value
|
19
|
+
end
|
20
|
+
|
21
|
+
it_behaves_like "valid resource", "bar"
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Yema::Rule::StrongType, '#errors' do
|
4
|
+
|
5
|
+
subject { rule.errors(resource) }
|
6
|
+
|
7
|
+
let(:rule) { described_class.new(attribute, options) }
|
8
|
+
let(:resource) { stub('resource', attribute => value) }
|
9
|
+
let(:attribute) { :foo }
|
10
|
+
let(:options) { {type: String}.merge(mode) }
|
11
|
+
let(:mode) { {} }
|
12
|
+
|
13
|
+
context "allow_nil mode" do
|
14
|
+
let(:mode) { {strict: :allow_nil} }
|
15
|
+
|
16
|
+
it_behaves_like "valid resource", nil
|
17
|
+
it_behaves_like "invalid resource", 1
|
18
|
+
it_behaves_like "valid resource", 'string'
|
19
|
+
end
|
20
|
+
|
21
|
+
context "default mode" do
|
22
|
+
it_behaves_like "invalid resource", nil
|
23
|
+
it_behaves_like "invalid resource", 1
|
24
|
+
it_behaves_like "valid resource", 'string'
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Combination' do
|
4
|
+
|
5
|
+
subject { described_class.new(value).errors }
|
6
|
+
|
7
|
+
let(:described_class) do
|
8
|
+
Class.new do
|
9
|
+
include Yema::Virtus::Validations
|
10
|
+
attribute :name, String
|
11
|
+
attribute :username, String, required: true, format: /langit/
|
12
|
+
attribute :salutation, String, within: ['mr', 'ms']
|
13
|
+
attribute :address, String, strict: :allow_nil
|
14
|
+
attribute :age, Integer, within: 1..26
|
15
|
+
attribute :height, Integer, required: false, default: 173
|
16
|
+
attribute :active, Virtus::Attribute::Boolean
|
17
|
+
attribute :pages, Array[String], length: 2..3
|
18
|
+
attribute :phones, Array
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it_behaves_like "valid resource", {
|
24
|
+
name: 'handi',
|
25
|
+
username: 'langitbiru',
|
26
|
+
salutation: 'mr',
|
27
|
+
age: '23',
|
28
|
+
active: false,
|
29
|
+
pages: [1, 2, '3a'],
|
30
|
+
phones: [1, 2, '3a'],
|
31
|
+
}
|
32
|
+
|
33
|
+
describe "check value" do
|
34
|
+
subject { described_class.new(value) }
|
35
|
+
|
36
|
+
let(:value) do
|
37
|
+
{
|
38
|
+
name: 'handi',
|
39
|
+
username: 'langitbiru',
|
40
|
+
salutation: 'mr',
|
41
|
+
age: '23',
|
42
|
+
pages: [1, 2, 3],
|
43
|
+
phones: [1, 2, 3],
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
its(:name) { should eql('handi') }
|
48
|
+
its(:username) { should eql('langitbiru') }
|
49
|
+
its(:salutation) { should eql('mr') }
|
50
|
+
its(:address) { should be_nil }
|
51
|
+
its(:age) { should eql(23) }
|
52
|
+
its(:height) { should eql(173) }
|
53
|
+
its(:active) { should be_false }
|
54
|
+
specify { subject.pages.count.should eql(3) }
|
55
|
+
specify { subject.phones.count.should eql(3) }
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|