attestor 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.
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