attestor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +9 -0
  4. data/.metrics +9 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +2 -0
  7. data/.travis.yml +10 -0
  8. data/.yardopts +3 -0
  9. data/Gemfile +5 -0
  10. data/Guardfile +15 -0
  11. data/LICENSE +21 -0
  12. data/README.md +308 -0
  13. data/Rakefile +22 -0
  14. data/attestor.gemspec +24 -0
  15. data/config/metrics/STYLEGUIDE +230 -0
  16. data/config/metrics/cane.yml +5 -0
  17. data/config/metrics/churn.yml +6 -0
  18. data/config/metrics/flay.yml +2 -0
  19. data/config/metrics/metric_fu.yml +15 -0
  20. data/config/metrics/reek.yml +1 -0
  21. data/config/metrics/roodi.yml +24 -0
  22. data/config/metrics/rubocop.yml +75 -0
  23. data/config/metrics/saikuro.yml +3 -0
  24. data/config/metrics/simplecov.yml +6 -0
  25. data/config/metrics/yardstick.yml +37 -0
  26. data/lib/attestor/invalid_error.rb +44 -0
  27. data/lib/attestor/policy/and.rb +36 -0
  28. data/lib/attestor/policy/factory.rb +88 -0
  29. data/lib/attestor/policy/negator.rb +53 -0
  30. data/lib/attestor/policy/node.rb +58 -0
  31. data/lib/attestor/policy/not.rb +48 -0
  32. data/lib/attestor/policy/or.rb +36 -0
  33. data/lib/attestor/policy/xor.rb +36 -0
  34. data/lib/attestor/policy.rb +121 -0
  35. data/lib/attestor/validations/collection.rb +73 -0
  36. data/lib/attestor/validations/item.rb +87 -0
  37. data/lib/attestor/validations/message.rb +55 -0
  38. data/lib/attestor/validations.rb +81 -0
  39. data/lib/attestor/version.rb +9 -0
  40. data/lib/attestor.rb +26 -0
  41. data/spec/spec_helper.rb +14 -0
  42. data/spec/support/policies.rb +49 -0
  43. data/spec/tests/invalid_error_spec.rb +57 -0
  44. data/spec/tests/policy/and_spec.rb +40 -0
  45. data/spec/tests/policy/factory_spec.rb +100 -0
  46. data/spec/tests/policy/negator_spec.rb +57 -0
  47. data/spec/tests/policy/node_spec.rb +44 -0
  48. data/spec/tests/policy/not_spec.rb +40 -0
  49. data/spec/tests/policy/or_spec.rb +40 -0
  50. data/spec/tests/policy/xor_spec.rb +48 -0
  51. data/spec/tests/policy_spec.rb +111 -0
  52. data/spec/tests/validations/collection_spec.rb +100 -0
  53. data/spec/tests/validations/item_spec.rb +153 -0
  54. data/spec/tests/validations/message_spec.rb +71 -0
  55. data/spec/tests/validations_spec.rb +126 -0
  56. metadata +143 -0
@@ -0,0 +1,73 @@
1
+ # encoding: utf-8
2
+
3
+ module Attestor
4
+
5
+ module Validations
6
+
7
+ # The collection of validations used by class instances
8
+ #
9
+ # @api private
10
+ class Collection
11
+ include Enumerable
12
+
13
+ # @!scope class
14
+ # @!method new(items = [])
15
+ # Creates an immutable collection with optional list of items
16
+ #
17
+ # @param [Array<Attestor::Collection::Item>] items
18
+ #
19
+ # @return [Attestor::Collection]
20
+
21
+ # @private
22
+ def initialize(items = [])
23
+ @items = items
24
+ freeze
25
+ end
26
+
27
+ # Iterates through the collection
28
+ #
29
+ # @yield the block
30
+ # @yieldparam [Attestor::Collection::Item] item
31
+ # items from the collection
32
+ #
33
+ # @return [Enumerator]
34
+ def each
35
+ return to_enum unless block_given?
36
+ items.map(&:name).uniq.each { |item| yield(item) }
37
+ end
38
+
39
+ # Returns the collection, updated with new item
40
+ #
41
+ # @param [#to_sym] name
42
+ # @param [Hash] options
43
+ # @option options [Array<#to_sym>] :except
44
+ # @option options [Array<#to_sym>] :only
45
+ #
46
+ # @return [Attestor::Collection]
47
+ def add(name, options = {})
48
+ item = Item.new(name, options)
49
+ return self if items.include? item
50
+
51
+ self.class.new(items + [item])
52
+ end
53
+
54
+ # Returns the collection of items used in given context
55
+ #
56
+ # @param [#to_sym] context
57
+ #
58
+ # @return [Attestor::Collection]
59
+ def set(context)
60
+ collection = items.select { |item| item.used_in_context? context }
61
+
62
+ self.class.new(collection)
63
+ end
64
+
65
+ private
66
+
67
+ attr_reader :items
68
+
69
+ end # class Collection
70
+
71
+ end # module Validations
72
+
73
+ end # module Attestor
@@ -0,0 +1,87 @@
1
+ # encoding: utf-8
2
+
3
+ module Attestor
4
+
5
+ module Validations
6
+
7
+ # Describes an item of validations' collection
8
+ #
9
+ # @api private
10
+ class Item
11
+
12
+ # @!scope class
13
+ # @!method new(name, except: [], only: [])
14
+ # Creates a named item with blacklist or whitelist of contexts
15
+ #
16
+ # @param [#to_sym] name
17
+ # @option [#to_sym, Array<#to_sym>] :except
18
+ # @option [#to_sym, Array<#to_sym>] :only
19
+ #
20
+ # @return [Attestor::Collection::Item]
21
+
22
+ # @private
23
+ def initialize(name, except: nil, only: nil)
24
+ @name = name.to_sym
25
+ @whitelist = normalize(only)
26
+ @blacklist = normalize(except)
27
+ generate_id
28
+ freeze
29
+ end
30
+
31
+ # @!attribute [r] name
32
+ # The name of the item
33
+ # @return [Symbol]
34
+ attr_reader :name
35
+
36
+ # Compares an item to another one
37
+ #
38
+ # @param [Object] other
39
+ #
40
+ # @return [Boolean]
41
+ def ==(other)
42
+ other.instance_of?(self.class) ? id.equal?(other.id) : false
43
+ end
44
+
45
+ # Checks if the item should be used in given context
46
+ #
47
+ # @param [#to_sym] context
48
+ #
49
+ # @return [Boolean]
50
+ def used_in_context?(context)
51
+ symbol = context.to_sym
52
+ whitelisted?(symbol) && !blacklisted?(symbol)
53
+ end
54
+
55
+ protected
56
+
57
+ # @!attribute [r] id
58
+ # The item's identity
59
+ #
60
+ # @return [String]
61
+ attr_reader :id
62
+
63
+ private
64
+
65
+ attr_reader :whitelist, :blacklist
66
+
67
+ def whitelisted?(symbol)
68
+ whitelist.empty? || whitelist.include?(symbol)
69
+ end
70
+
71
+ def blacklisted?(symbol)
72
+ blacklist.include? symbol
73
+ end
74
+
75
+ def generate_id
76
+ @id = [name, whitelist, blacklist].hash
77
+ end
78
+
79
+ def normalize(list)
80
+ Array(list).map(&:to_sym).uniq
81
+ end
82
+
83
+ end # class Item
84
+
85
+ end # module Validations
86
+
87
+ end # module Attestor
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ module Attestor
4
+
5
+ module Validations
6
+
7
+ # Bulder for error messages
8
+ #
9
+ # @api private
10
+ class Message < String
11
+
12
+ # @!scope class
13
+ # @!method new(value, object, options = {})
14
+ # Builds a string from value
15
+ #
16
+ # @param [#to_s] value
17
+ # @param [Object] object
18
+ # @param [Hash] options
19
+ # options for translating symbolic value
20
+ #
21
+ # @return [String]
22
+ # either translation of symbolic value or stringified value argument
23
+
24
+ # @private
25
+ def initialize(value, object, options = {})
26
+ @value = value
27
+ @object = object
28
+ @options = options
29
+ super(@value.instance_of?(Symbol) ? translation : @value.to_s)
30
+ freeze
31
+ end
32
+
33
+ private
34
+
35
+ def translation
36
+ I18n.t @value, @options.merge(scope: scope, default: default)
37
+ end
38
+
39
+ def scope
40
+ %W(attestor errors #{ class_scope })
41
+ end
42
+
43
+ def class_scope
44
+ @object.class.to_s.split("::").map(&:snake_case).join("/")
45
+ end
46
+
47
+ def default
48
+ "#{ @object } is invalid (#{ @value })"
49
+ end
50
+
51
+ end # class Message
52
+
53
+ end # module Validations
54
+
55
+ end # module Attestor
@@ -0,0 +1,81 @@
1
+ # encoding: utf-8
2
+
3
+ module Attestor
4
+
5
+ # API for objects to be validated
6
+ module Validations
7
+
8
+ # Calls all validations used in the selected context
9
+ #
10
+ # @raise [Attestor::Validations::InvalidError] if validations fail
11
+ # @raise [NoMethodError] if some of validations are not implemented
12
+ #
13
+ # @return [undefined]
14
+ def validate(context = :all)
15
+ self.class.validations.set(context).each(&method(:__send__))
16
+ end
17
+
18
+ # Raises InvalidError with a corresponding message
19
+ #
20
+ # @overload invalid(name, options = {})
21
+ #
22
+ # @param [Symbol] name
23
+ # the name of the error
24
+ # @param [Hash] options
25
+ # the options for symbolic name translation
26
+ #
27
+ # @return [String]
28
+ # translation of symbolic name in the current object's scope
29
+ #
30
+ # @overload invalid(name)
31
+ #
32
+ # @param [#to_s] name
33
+ # the error message (not a symbol)
34
+ #
35
+ # @return [String]
36
+ # the name converted to string
37
+ def invalid(name, options = {})
38
+ message = Message.new(name, self, options)
39
+ fail InvalidError.new self, [message]
40
+ end
41
+
42
+ # @private
43
+ module ClassMethods
44
+
45
+ # Returns a collection of items describing applied validations
46
+ #
47
+ # @return [Attestor::Collection]
48
+ #
49
+ # @api private
50
+ def validations
51
+ @validations ||= Collection.new
52
+ end
53
+
54
+ # Adds an item to {#validations}
55
+ #
56
+ # Mutates the class by changing its {#validations} attribute!
57
+ #
58
+ # @param [#to_sym] name
59
+ # @param [Hash] options
60
+ # @option options [#to_sym, Array<#to_sym>] :except
61
+ # the black list of contexts for validation
62
+ # @option options [#to_sym, Array<#to_sym>] :only
63
+ # the white list of contexts for validation
64
+ #
65
+ # @return [Attestor::Collection] the updated collection
66
+ def validate(name, options = {})
67
+ @validations = validations.add(name, options)
68
+ end
69
+
70
+ end # module ClassMethods
71
+
72
+ # @private
73
+ def self.included(klass)
74
+ klass.instance_eval { extend ClassMethods }
75
+ end
76
+
77
+ # @!parse extend Attestor::Validations::ClassMethods
78
+
79
+ end # module Validations
80
+
81
+ end # module Attestor
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ module Attestor
4
+
5
+ # The semantic version of the module.
6
+ # @see http://semver.org/ Semantic versioning 2.0
7
+ VERSION = "0.0.1".freeze
8
+
9
+ end # module Attestor
data/lib/attestor.rb ADDED
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ require "extlib"
4
+
5
+ require_relative "attestor/version"
6
+
7
+ require_relative "attestor/invalid_error"
8
+
9
+ require_relative "attestor/validations"
10
+ require_relative "attestor/validations/item"
11
+ require_relative "attestor/validations/collection"
12
+ require_relative "attestor/validations/message"
13
+
14
+ require_relative "attestor/policy/factory"
15
+ require_relative "attestor/policy"
16
+ require_relative "attestor/policy/node"
17
+ require_relative "attestor/policy/and"
18
+ require_relative "attestor/policy/or"
19
+ require_relative "attestor/policy/xor"
20
+ require_relative "attestor/policy/not"
21
+ require_relative "attestor/policy/negator"
22
+
23
+ # Namespace for the code of the 'attestor' gem
24
+ module Attestor
25
+
26
+ end # module Attestor
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ require "hexx-rspec"
3
+
4
+ begin
5
+ require "hexx-suit"
6
+ rescue LoadError
7
+ false
8
+ end
9
+
10
+ # Loads runtime metrics
11
+ Hexx::RSpec.load_metrics_for(self)
12
+
13
+ # Loads the code under test
14
+ require "attestor"
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ # Definitions for testing compound policies
4
+
5
+ def valid_policy
6
+ double valid?: true, invalid?: false
7
+ end
8
+
9
+ def invalid_policy
10
+ double valid?: false, invalid?: true
11
+ end
12
+
13
+ shared_examples "creating a node" do
14
+
15
+ it { is_expected.to be_kind_of Attestor::Policy::Node }
16
+
17
+ end # shared examples
18
+
19
+ shared_examples "creating an immutable object" do
20
+
21
+ it "[freezes a policy]" do
22
+ expect(subject).to be_frozen
23
+ end
24
+
25
+ end # shared examples
26
+
27
+ shared_examples "failing validation" do
28
+
29
+ it "[raises exception]" do
30
+ expect { subject.validate }.to raise_error Attestor::InvalidError
31
+ end
32
+
33
+ it "[adds itself to exception]" do
34
+ begin
35
+ subject.validate
36
+ rescue => error
37
+ expect(error.object).to eq subject
38
+ end
39
+ end
40
+
41
+ end # shared examples
42
+
43
+ shared_examples "passing validation" do
44
+
45
+ it "[raises exception]" do
46
+ expect { subject.validate }.not_to raise_error
47
+ end
48
+
49
+ end # shared examples
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+
3
+ describe Attestor::InvalidError do
4
+
5
+ let(:object) { double :object }
6
+ subject { described_class.new object }
7
+
8
+ describe ".new" do
9
+
10
+ it "creates a RuntimeError" do
11
+ expect(subject).to be_kind_of RuntimeError
12
+ end
13
+
14
+ it "creates immutable object" do
15
+ expect(subject).to be_frozen
16
+ end
17
+
18
+ it "doesn't freeze object" do
19
+ subject
20
+ expect(object).not_to be_frozen
21
+ end
22
+
23
+ it "doesn't freeze messages" do
24
+ messages = %i(foo bar)
25
+ described_class.new object, messages
26
+
27
+ expect(messages).not_to be_frozen
28
+ end
29
+
30
+ end # describe .new
31
+
32
+ describe "#object" do
33
+
34
+ it "is initialized" do
35
+ expect(subject.object).to be_eql object
36
+ end
37
+
38
+ end # describe #object
39
+
40
+ describe "#messages" do
41
+
42
+ it "returns an empty array" do
43
+ expect(subject.messages).to eq []
44
+ end
45
+
46
+ it "can be initialized" do
47
+ subject = described_class.new(object, %w(cad cam))
48
+ expect(subject.messages).to eq %w(cad cam)
49
+ end
50
+
51
+ it "is immutable" do
52
+ expect(subject.messages).to be_frozen
53
+ end
54
+
55
+ end # describe #messages
56
+
57
+ end # describe Attestor::ValidError
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+
3
+ # describe #valid_policy and #invalid_policy builders
4
+ # also describes shared examples for all policies
5
+ require "support/policies"
6
+
7
+ describe Attestor::Policy::And do
8
+
9
+ subject { described_class.new items }
10
+
11
+ describe ".new" do
12
+
13
+ let(:items) { [valid_policy] }
14
+
15
+ it_behaves_like "creating a node"
16
+ it_behaves_like "creating an immutable object"
17
+
18
+ end # context
19
+
20
+ describe "#validate" do
21
+
22
+ context "when all the parts are valid" do
23
+
24
+ let(:items) { 3.times.map { valid_policy } }
25
+
26
+ it_behaves_like "passing validation"
27
+
28
+ end # context
29
+
30
+ context "when a part is invalid" do
31
+
32
+ let(:items) { [valid_policy, valid_policy, invalid_policy] }
33
+
34
+ it_behaves_like "failing validation"
35
+
36
+ end # context
37
+
38
+ end # describe #validate
39
+
40
+ end # describe Policy::Base::Not
@@ -0,0 +1,100 @@
1
+ # encoding: utf-8
2
+
3
+ describe Attestor::Policy::Factory do
4
+
5
+ let(:test_class) { Class.new.send :include, described_class }
6
+ let(:subject) { test_class.new }
7
+ let(:policy) { double :policy }
8
+ let(:others) { 2.times.map { double } }
9
+
10
+ shared_examples "creating a node" do |composer|
11
+
12
+ it "[creates a Node]" do
13
+ expect(result).to be_kind_of composer
14
+ end
15
+
16
+ it "[sets policies]" do
17
+ expect(result.branches).to match_array [policy, *others]
18
+ end
19
+
20
+ end # shared examples
21
+
22
+ shared_examples "creating a negator" do |composer|
23
+
24
+ it "[creates a Negator]" do
25
+ expect(result).to be_kind_of Attestor::Policy::Negator
26
+ end
27
+
28
+ it "[sets a policy]" do
29
+ expect(result.policy).to eq policy
30
+ end
31
+
32
+ it "[sets a composer]" do
33
+ expect(result.composer).to eq composer
34
+ end
35
+
36
+ end # shared examples
37
+
38
+ describe "#and" do
39
+
40
+ context "with one argument" do
41
+
42
+ let(:result) { subject.and(policy, []) }
43
+ it_behaves_like "creating a negator", Attestor::Policy::And
44
+
45
+ end # context
46
+
47
+ context "with several arguments" do
48
+
49
+ let(:result) { subject.and(policy, others) }
50
+ it_behaves_like "creating a node", Attestor::Policy::And
51
+
52
+ end # context
53
+
54
+ end # describe #and
55
+
56
+ describe "#or" do
57
+
58
+ context "with one argument" do
59
+
60
+ let(:result) { subject.or(policy, []) }
61
+ it_behaves_like "creating a negator", Attestor::Policy::Or
62
+
63
+ end # context
64
+
65
+ context "with several arguments" do
66
+
67
+ let(:result) { subject.or(policy, others) }
68
+ it_behaves_like "creating a node", Attestor::Policy::Or
69
+
70
+ end # context
71
+
72
+ end # describe #or
73
+
74
+ describe "#xor" do
75
+
76
+ context "with one argument" do
77
+
78
+ let(:result) { subject.xor(policy, []) }
79
+ it_behaves_like "creating a negator", Attestor::Policy::Xor
80
+
81
+ end # context
82
+
83
+ context "with several arguments" do
84
+
85
+ let(:result) { subject.xor(policy, others) }
86
+ it_behaves_like "creating a node", Attestor::Policy::Xor
87
+
88
+ end # context
89
+
90
+ end # describe #or
91
+
92
+ describe "#not" do
93
+
94
+ let(:others) { [] }
95
+ let(:result) { subject.not(policy) }
96
+ it_behaves_like "creating a node", Attestor::Policy::Not
97
+
98
+ end # describe #or
99
+
100
+ end # describe Attestor::Policy::Factory
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+
3
+ describe Attestor::Policy::Negator do
4
+
5
+ let(:composer) { Attestor::Policy::Node }
6
+ let(:not_class) { Attestor::Policy::Not }
7
+ let(:policy) { double :policy }
8
+
9
+ subject { described_class.new composer, policy }
10
+
11
+ describe ".new" do
12
+
13
+ it "creates immutable object" do
14
+ expect(subject).to be_frozen
15
+ end
16
+
17
+ end
18
+
19
+ describe "#policy" do
20
+
21
+ it "is initialized" do
22
+ expect(subject.policy).to eq policy
23
+ end
24
+
25
+ end # describe #policy
26
+
27
+ describe "#composer" do
28
+
29
+ it "is initialized" do
30
+ expect(subject.composer).to eq composer
31
+ end
32
+
33
+ end # describe #composer
34
+
35
+ describe "#not" do
36
+
37
+ let(:another) { double :another }
38
+ let(:result) { subject.not(another) }
39
+
40
+ it "creates a composer object" do
41
+ expect(result).to be_kind_of composer
42
+ end
43
+
44
+ it "sends its policy to the composer" do
45
+ expect(result.branches).to include policy
46
+ end
47
+
48
+ it "sends the negated arguments to the composer" do
49
+ negation = double :negation
50
+ expect(not_class).to receive(:new).with(another).and_return(negation)
51
+
52
+ expect(result.branches).to include negation
53
+ end
54
+
55
+ end # describe #not
56
+
57
+ end # describe Policy::Base::Negator
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ describe Attestor::Policy::Node do
4
+
5
+ let(:policy_class) { Attestor::Policy }
6
+ let(:invalid_error) { Attestor::InvalidError }
7
+
8
+ describe ".new" do
9
+
10
+ it "creates a policy" do
11
+ expect(subject).to be_kind_of policy_class
12
+ end
13
+
14
+ it "creates immutable object" do
15
+ expect(subject).to be_frozen
16
+ end
17
+
18
+ end # describe .new
19
+
20
+ describe "#branches" do
21
+
22
+ let(:branches) { 3.times.map { double } }
23
+
24
+ it "are initialized from list" do
25
+ subject = described_class.new(*branches)
26
+ expect(subject.branches).to match_array branches
27
+ end
28
+
29
+ it "are initialized from array" do
30
+ subject = described_class.new(branches)
31
+ expect(subject.branches).to match_array branches
32
+ end
33
+
34
+ end # describe #branches
35
+
36
+ describe "#validate" do
37
+
38
+ it "raises InvalidError" do
39
+ expect { subject.validate }.to raise_error invalid_error
40
+ end
41
+
42
+ end # describe #validate
43
+
44
+ end # describe Attestor::Policy::Node