yema 0.0.1
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.
- 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
|