media_types 2.2.0 → 2.3.0

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/debian.yml +43 -43
  3. data/.github/workflows/publish-bookworm.yml +33 -0
  4. data/.github/workflows/publish-sid.yml +33 -0
  5. data/.github/workflows/ruby.yml +22 -22
  6. data/.gitignore +20 -10
  7. data/.rubocop.yml +29 -29
  8. data/CHANGELOG.md +175 -164
  9. data/Gemfile +6 -6
  10. data/LICENSE +21 -21
  11. data/README.md +666 -664
  12. data/Rakefile +12 -12
  13. data/bin/console +15 -15
  14. data/bin/setup +8 -8
  15. data/lib/media_types/constructable.rb +161 -160
  16. data/lib/media_types/dsl/errors.rb +18 -18
  17. data/lib/media_types/dsl.rb +172 -172
  18. data/lib/media_types/errors.rb +25 -19
  19. data/lib/media_types/formatter.rb +56 -56
  20. data/lib/media_types/hash.rb +21 -21
  21. data/lib/media_types/object.rb +35 -35
  22. data/lib/media_types/scheme/allow_nil.rb +30 -30
  23. data/lib/media_types/scheme/any_of.rb +41 -41
  24. data/lib/media_types/scheme/attribute.rb +46 -46
  25. data/lib/media_types/scheme/enumeration_context.rb +18 -18
  26. data/lib/media_types/scheme/enumeration_of_type.rb +80 -80
  27. data/lib/media_types/scheme/errors.rb +87 -87
  28. data/lib/media_types/scheme/links.rb +54 -54
  29. data/lib/media_types/scheme/missing_validation.rb +41 -41
  30. data/lib/media_types/scheme/not_strict.rb +15 -15
  31. data/lib/media_types/scheme/output_empty_guard.rb +45 -45
  32. data/lib/media_types/scheme/output_iterator_with_predicate.rb +66 -66
  33. data/lib/media_types/scheme/output_type_guard.rb +39 -39
  34. data/lib/media_types/scheme/rules.rb +186 -173
  35. data/lib/media_types/scheme/rules_exhausted_guard.rb +73 -73
  36. data/lib/media_types/scheme/validation_options.rb +44 -43
  37. data/lib/media_types/scheme.rb +535 -513
  38. data/lib/media_types/testing/assertions.rb +20 -20
  39. data/lib/media_types/validations.rb +118 -105
  40. data/lib/media_types/version.rb +5 -5
  41. data/lib/media_types/views.rb +12 -12
  42. data/lib/media_types.rb +73 -73
  43. data/media_types.gemspec +33 -33
  44. metadata +8 -6
@@ -1,41 +1,41 @@
1
- # frozen_string_literal: true
2
-
3
- require 'delegate'
4
-
5
- module MediaTypes
6
- class Scheme
7
- class CaseEqualityWithList < SimpleDelegator
8
-
9
- # True for Enumerable#any? {Object#===}
10
- def ===(other)
11
- any? { |it| it === other } # rubocop:disable Style/CaseEquality
12
- end
13
-
14
- def inspect
15
- "[Scheme::AnyOf(#{__getobj__})]"
16
- end
17
- end
18
-
19
- class << self
20
- # noinspection RubyClassMethodNamingConvention
21
- ##
22
- # Allows +it+ to be any of the wrapped +klazzes+
23
- #
24
- # @param [Array<Class>] klazzes the classes that are valid for +it+
25
- # @return [CaseEqualityWithList]
26
- def AnyOf(*klazzes) # rubocop:disable Naming/MethodName
27
- CaseEqualityWithList.new(klazzes)
28
- end
29
- end
30
-
31
- # noinspection RubyInstanceMethodNamingConvention
32
- ##
33
- # Allows +it+ to be any of the wrapped +klazzes+
34
- #
35
- # @param [Array<Class>] klazzes the classes that are valid for +it+
36
- # @return [CaseEqualityWithList]
37
- def AnyOf(*klazzes) # rubocop:disable Naming/MethodName
38
- self.class.AnyOf(*klazzes)
39
- end
40
- end
41
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ module MediaTypes
6
+ class Scheme
7
+ class CaseEqualityWithList < SimpleDelegator
8
+
9
+ # True for Enumerable#any? {Object#===}
10
+ def ===(other)
11
+ any? { |it| it === other } # rubocop:disable Style/CaseEquality
12
+ end
13
+
14
+ def inspect
15
+ "[Scheme::AnyOf(#{__getobj__})]"
16
+ end
17
+ end
18
+
19
+ class << self
20
+ # noinspection RubyClassMethodNamingConvention
21
+ ##
22
+ # Allows +it+ to be any of the wrapped +klazzes+
23
+ #
24
+ # @param [Array<Class>] klazzes the classes that are valid for +it+
25
+ # @return [CaseEqualityWithList]
26
+ def AnyOf(*klazzes) # rubocop:disable Naming/MethodName
27
+ CaseEqualityWithList.new(klazzes)
28
+ end
29
+ end
30
+
31
+ # noinspection RubyInstanceMethodNamingConvention
32
+ ##
33
+ # Allows +it+ to be any of the wrapped +klazzes+
34
+ #
35
+ # @param [Array<Class>] klazzes the classes that are valid for +it+
36
+ # @return [CaseEqualityWithList]
37
+ def AnyOf(*klazzes) # rubocop:disable Naming/MethodName
38
+ self.class.AnyOf(*klazzes)
39
+ end
40
+ end
41
+ end
@@ -1,46 +1,46 @@
1
- # frozen_string_literal: true
2
-
3
- require 'media_types/scheme/errors'
4
- require 'media_types/scheme/allow_nil'
5
-
6
- module MediaTypes
7
- class Scheme
8
- class Attribute
9
-
10
- ##
11
- # An attribute that expects a value of type +type+
12
- #
13
- # @see AllowNil
14
- # @see AnyOf
15
- #
16
- # @param [Class] type the class +it+ must be
17
- # @param [TrueClass, FalseClass] allow_nil if true, nil? is allowed
18
- #
19
- def initialize(type, allow_nil: false)
20
- self.type = allow_nil ? Scheme.AllowNil(type) : type
21
-
22
- freeze
23
- end
24
-
25
- def validate!(output, options, **_opts)
26
- return true if type === output # rubocop:disable Style/CaseEquality
27
- raise ValidationError,
28
- format(
29
- 'Expected %<type>s, got %<actual>s at [%<backtrace>s]',
30
- type: type,
31
- actual: output.inspect,
32
- backtrace: options.backtrace.join('->')
33
- )
34
- end
35
-
36
- def inspect
37
- "[Scheme::Attribute of #{type.inspect}]"
38
- end
39
-
40
- private
41
-
42
- attr_accessor :allow_nil, :type
43
-
44
- end
45
- end
46
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'media_types/scheme/errors'
4
+ require 'media_types/scheme/allow_nil'
5
+
6
+ module MediaTypes
7
+ class Scheme
8
+ class Attribute
9
+
10
+ ##
11
+ # An attribute that expects a value of type +type+
12
+ #
13
+ # @see AllowNil
14
+ # @see AnyOf
15
+ #
16
+ # @param [Class] type the class +it+ must be
17
+ # @param [TrueClass, FalseClass] allow_nil if true, nil? is allowed
18
+ #
19
+ def initialize(type, allow_nil: false)
20
+ self.type = allow_nil ? Scheme.AllowNil(type) : type
21
+
22
+ freeze
23
+ end
24
+
25
+ def validate!(output, options, **_opts)
26
+ return true if type === output # rubocop:disable Style/CaseEquality
27
+ raise ValidationError,
28
+ format(
29
+ 'Expected %<type>s, got %<actual>s at [%<backtrace>s]',
30
+ type: type,
31
+ actual: output.inspect,
32
+ backtrace: options.backtrace.join('->')
33
+ )
34
+ end
35
+
36
+ def inspect
37
+ "[Scheme::Attribute of #{type.inspect}]"
38
+ end
39
+
40
+ private
41
+
42
+ attr_accessor :allow_nil, :type
43
+
44
+ end
45
+ end
46
+ end
@@ -1,18 +1,18 @@
1
- # frozen_string_literal: true
2
-
3
- module MediaTypes
4
- class Scheme
5
- class EnumerationContext
6
- def initialize(rules:)
7
- self.rules = rules
8
- end
9
-
10
- def enumerate(val)
11
- self.key = val
12
- self
13
- end
14
-
15
- attr_accessor :rules, :key
16
- end
17
- end
18
- end
1
+ # frozen_string_literal: true
2
+
3
+ module MediaTypes
4
+ class Scheme
5
+ class EnumerationContext
6
+ def initialize(rules:)
7
+ self.rules = rules
8
+ end
9
+
10
+ def enumerate(val)
11
+ self.key = val
12
+ self
13
+ end
14
+
15
+ attr_accessor :rules, :key
16
+ end
17
+ end
18
+ end
@@ -1,80 +1,80 @@
1
- # frozen_string_literal: true
2
-
3
- require 'media_types/scheme/errors'
4
-
5
- module MediaTypes
6
- class Scheme
7
- class EnumerationOfType
8
-
9
- ##
10
- # An attribute that expects a value of type +enumeration_type+ and each item of type +item_type+
11
- #
12
- # @param [Class] item_type the type of each item
13
- # @param [Class] enumeration_type the type of the enumeration as a whole
14
- # @param [TrueClass, FalseClass] allow_empty if true, an empty instance of +enumeration_type+ is valid
15
- #
16
- def initialize(item_type, enumeration_type: Array, allow_empty: false)
17
- self.item_type = item_type
18
- self.enumeration_type = enumeration_type
19
- self.allow_empty = allow_empty
20
-
21
- freeze
22
- end
23
-
24
- def validate!(output, options, **_opts)
25
- validate_enumeration!(output, options) &&
26
- validate_not_empty!(output, options) &&
27
- validate_items!(output, options)
28
- end
29
-
30
- def inspect
31
- "[Scheme::EnumerationOfType #{item_type} collection=#{enumeration_type} empty=#{allow_empty}]"
32
- end
33
-
34
- private
35
-
36
- attr_accessor :allow_empty, :enumeration_type, :item_type
37
-
38
- def validate_enumeration!(output, options)
39
- return true if enumeration_type === output # rubocop:disable Style/CaseEquality
40
- raise ValidationError,
41
- format(
42
- 'Expected collection as %<type>s, got %<actual>s at [%<backtrace>s]',
43
- type: enumeration_type,
44
- actual: output.inspect,
45
- backtrace: options.backtrace.join('->')
46
- )
47
- end
48
-
49
- def validate_not_empty!(output, options)
50
- return true if allow_empty
51
- return true if output.respond_to?(:length) && output.length.positive?
52
- return true if output.respond_to?(:empty?) && !output.empty?
53
-
54
- raise EmptyOutputError,
55
- format(
56
- 'Expected collection to be not empty, got empty at [%<backtrace>s]',
57
- backtrace: options.backtrace.join('->')
58
- )
59
- end
60
-
61
- def validate_items!(output, options)
62
- output.each_with_index.all? do |item, index|
63
- next true if item_type === item # rubocop:disable Style/CaseEquality
64
- if item_type.is_a?(Scheme)
65
- item_type.validate(item, options.trace("[#{index}]"))
66
- next true
67
- end
68
- raise ValidationError,
69
- format(
70
- 'Expected collection item as %<type>s, got %<actual>s at [%<backtrace>s]',
71
- type: item_type.inspect,
72
- actual: item.inspect,
73
- backtrace: options.backtrace.join('->')
74
- )
75
- end
76
- end
77
-
78
- end
79
- end
80
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'media_types/scheme/errors'
4
+
5
+ module MediaTypes
6
+ class Scheme
7
+ class EnumerationOfType
8
+
9
+ ##
10
+ # An attribute that expects a value of type +enumeration_type+ and each item of type +item_type+
11
+ #
12
+ # @param [Class] item_type the type of each item
13
+ # @param [Class] enumeration_type the type of the enumeration as a whole
14
+ # @param [TrueClass, FalseClass] allow_empty if true, an empty instance of +enumeration_type+ is valid
15
+ #
16
+ def initialize(item_type, enumeration_type: Array, allow_empty: false)
17
+ self.item_type = item_type
18
+ self.enumeration_type = enumeration_type
19
+ self.allow_empty = allow_empty
20
+
21
+ freeze
22
+ end
23
+
24
+ def validate!(output, options, **_opts)
25
+ validate_enumeration!(output, options) &&
26
+ validate_not_empty!(output, options) &&
27
+ validate_items!(output, options)
28
+ end
29
+
30
+ def inspect
31
+ "[Scheme::EnumerationOfType #{item_type} collection=#{enumeration_type} empty=#{allow_empty}]"
32
+ end
33
+
34
+ private
35
+
36
+ attr_accessor :allow_empty, :enumeration_type, :item_type
37
+
38
+ def validate_enumeration!(output, options)
39
+ return true if enumeration_type === output # rubocop:disable Style/CaseEquality
40
+ raise ValidationError,
41
+ format(
42
+ 'Expected collection as %<type>s, got %<actual>s at [%<backtrace>s]',
43
+ type: enumeration_type,
44
+ actual: output.inspect,
45
+ backtrace: options.backtrace.join('->')
46
+ )
47
+ end
48
+
49
+ def validate_not_empty!(output, options)
50
+ return true if allow_empty
51
+ return true if output.respond_to?(:length) && output.length.positive?
52
+ return true if output.respond_to?(:empty?) && !output.empty?
53
+
54
+ raise EmptyOutputError,
55
+ format(
56
+ 'Expected collection to be not empty, got empty at [%<backtrace>s]',
57
+ backtrace: options.backtrace.join('->')
58
+ )
59
+ end
60
+
61
+ def validate_items!(output, options)
62
+ output.each_with_index.all? do |item, index|
63
+ next true if item_type === item # rubocop:disable Style/CaseEquality
64
+ if item_type.is_a?(Scheme)
65
+ item_type.validate(item, options.trace("[#{index}]"))
66
+ next true
67
+ end
68
+ raise ValidationError,
69
+ format(
70
+ 'Expected collection item as %<type>s, got %<actual>s at [%<backtrace>s]',
71
+ type: item_type.inspect,
72
+ actual: item.inspect,
73
+ backtrace: options.backtrace.join('->')
74
+ )
75
+ end
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -1,87 +1,87 @@
1
- # frozen_string_literal: true
2
-
3
- module MediaTypes
4
- class Scheme
5
- class ConflictingTypeDefinitionError < ArgumentError; end
6
-
7
- # Base class for all validations errors
8
- class ValidationError < ArgumentError; end
9
-
10
- # Raised when trying to register an attribute with a non-string key
11
- class KeyTypeError < ArgumentError; end
12
-
13
- # Raised when trying to register a key twice
14
- class DuplicateKeyError < ArgumentError; end
15
-
16
- class DuplicateSymbolKeyError < DuplicateKeyError
17
- MESSAGE_TEMPLATE = '%<rule_type>s rule with key :%<key>s has already been defined. Please remove one of the two.'
18
-
19
- def initialize(rule_type, key)
20
- super(format(MESSAGE_TEMPLATE, rule_type: rule_type, key: key))
21
- end
22
- end
23
-
24
- class DuplicateStringKeyError < DuplicateKeyError
25
- MESSAGE_TEMPLATE = '%<rule_type>s rule with key %<key>s has already been defined. Please remove one of the two.'
26
-
27
- def initialize(rule_type, key)
28
- super(format(MESSAGE_TEMPLATE, { rule_type: rule_type, key: key }))
29
- end
30
- end
31
-
32
- class StringOverwritingSymbolError < DuplicateKeyError
33
- MESSAGE_TEMPLATE = 'Trying to add %<rule_type>s rule String key %<key>s while a Symbol with the same name already exists. Please remove one of the two.'
34
-
35
- def initialize(rule_type, key)
36
- super(format(MESSAGE_TEMPLATE, { rule_type: rule_type, key: key }))
37
- end
38
- end
39
-
40
- class SymbolOverwritingStringError < DuplicateKeyError
41
- MESSAGE_TEMPLATE = 'Trying to add %<rule_type>s rule with Symbol key :%<key>s while a String key with the same name already exists. Please remove one of the two.'
42
-
43
- def initialize(rule_type, key)
44
- super(format(MESSAGE_TEMPLATE, { rule_type: rule_type, key: key }))
45
- end
46
- end
47
-
48
- # Raised when it did not expect more data, but there was more left
49
- class StrictValidationError < ValidationError; end
50
-
51
- # Raised when it expected not to be empty, but it was
52
- class EmptyOutputError < ValidationError; end
53
-
54
- # Raised when a value did not have the expected type
55
- class OutputTypeMismatch < ValidationError; end
56
-
57
- # Raised when it expected more data but there wasn't any left
58
- class ExhaustedOutputError < ValidationError; end
59
-
60
- # Raised when trying to override a non default rule scheme in the Rules Hash's default object method
61
- class OverwritingRuleError < ArgumentError; end
62
-
63
- class DuplicateAnyRuleError < OverwritingRuleError
64
- def message
65
- "An 'any' rule has already been defined. Please remove one of the two."
66
- end
67
- end
68
-
69
- class DuplicateNotStrictRuleError < OverwritingRuleError
70
- def message
71
- "The 'not_strict' rule has already been defined. Please remove one of the two."
72
- end
73
- end
74
-
75
- class NotStrictOverwritingAnyError < OverwritingRuleError
76
- def message
77
- "An 'any' rule has already been defined. Setting 'not_strict' will override that rule. Please remove one of the two."
78
- end
79
- end
80
-
81
- class AnyOverwritingNotStrictError < OverwritingRuleError
82
- def message
83
- "The 'not_strict' rule has already been defined. Setting 'any' will override that rule. Please remove one of the two."
84
- end
85
- end
86
- end
87
- end
1
+ # frozen_string_literal: true
2
+
3
+ module MediaTypes
4
+ class Scheme
5
+ class ConflictingTypeDefinitionError < ArgumentError; end
6
+
7
+ # Base class for all validations errors
8
+ class ValidationError < ArgumentError; end
9
+
10
+ # Raised when trying to register an attribute with a non-string key
11
+ class KeyTypeError < ArgumentError; end
12
+
13
+ # Raised when trying to register a key twice
14
+ class DuplicateKeyError < ArgumentError; end
15
+
16
+ class DuplicateSymbolKeyError < DuplicateKeyError
17
+ MESSAGE_TEMPLATE = '%<rule_type>s rule with key :%<key>s has already been defined. Please remove one of the two.'
18
+
19
+ def initialize(rule_type, key)
20
+ super(format(MESSAGE_TEMPLATE, rule_type: rule_type, key: key))
21
+ end
22
+ end
23
+
24
+ class DuplicateStringKeyError < DuplicateKeyError
25
+ MESSAGE_TEMPLATE = '%<rule_type>s rule with key %<key>s has already been defined. Please remove one of the two.'
26
+
27
+ def initialize(rule_type, key)
28
+ super(format(MESSAGE_TEMPLATE, { rule_type: rule_type, key: key }))
29
+ end
30
+ end
31
+
32
+ class StringOverwritingSymbolError < DuplicateKeyError
33
+ MESSAGE_TEMPLATE = 'Trying to add %<rule_type>s rule String key %<key>s while a Symbol with the same name already exists. Please remove one of the two.'
34
+
35
+ def initialize(rule_type, key)
36
+ super(format(MESSAGE_TEMPLATE, { rule_type: rule_type, key: key }))
37
+ end
38
+ end
39
+
40
+ class SymbolOverwritingStringError < DuplicateKeyError
41
+ MESSAGE_TEMPLATE = 'Trying to add %<rule_type>s rule with Symbol key :%<key>s while a String key with the same name already exists. Please remove one of the two.'
42
+
43
+ def initialize(rule_type, key)
44
+ super(format(MESSAGE_TEMPLATE, { rule_type: rule_type, key: key }))
45
+ end
46
+ end
47
+
48
+ # Raised when it did not expect more data, but there was more left
49
+ class StrictValidationError < ValidationError; end
50
+
51
+ # Raised when it expected not to be empty, but it was
52
+ class EmptyOutputError < ValidationError; end
53
+
54
+ # Raised when a value did not have the expected type
55
+ class OutputTypeMismatch < ValidationError; end
56
+
57
+ # Raised when it expected more data but there wasn't any left
58
+ class ExhaustedOutputError < ValidationError; end
59
+
60
+ # Raised when trying to override a non default rule scheme in the Rules Hash's default object method
61
+ class OverwritingRuleError < ArgumentError; end
62
+
63
+ class DuplicateAnyRuleError < OverwritingRuleError
64
+ def message
65
+ "An 'any' rule has already been defined. Please remove one of the two."
66
+ end
67
+ end
68
+
69
+ class DuplicateNotStrictRuleError < OverwritingRuleError
70
+ def message
71
+ "The 'not_strict' rule has already been defined. Please remove one of the two."
72
+ end
73
+ end
74
+
75
+ class NotStrictOverwritingAnyError < OverwritingRuleError
76
+ def message
77
+ "An 'any' rule has already been defined. Setting 'not_strict' will override that rule. Please remove one of the two."
78
+ end
79
+ end
80
+
81
+ class AnyOverwritingNotStrictError < OverwritingRuleError
82
+ def message
83
+ "The 'not_strict' rule has already been defined. Setting 'any' will override that rule. Please remove one of the two."
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,54 +1,54 @@
1
- # frozen_string_literal: true
2
-
3
- require 'media_types/scheme/rules'
4
- require 'media_types/scheme/rules_exhausted_guard'
5
-
6
- module MediaTypes
7
- class Scheme
8
- class Links
9
- def initialize
10
- self.links = Rules.new(allow_empty: false, expected_type: ::Hash)
11
- end
12
-
13
- def link(key, allow_nil: false, optional: false, &block)
14
- links.add(
15
- key,
16
- Scheme.new do
17
- attribute :href, String, allow_nil: allow_nil
18
- instance_exec(&block) if block_given?
19
- end,
20
- optional: optional
21
- )
22
-
23
- self
24
- end
25
-
26
- def validate!(output, options, **_opts)
27
- RulesExhaustedGuard.call(output, options, rules: links)
28
- end
29
-
30
- def inspect
31
- "[Scheme::Links #{links.keys}]"
32
- end
33
-
34
- def run_fixture_validations(expect_symbol_keys, backtrace = [])
35
- fixture_errors = @links.flat_map {|key, rule|
36
- if rule.is_a?(Scheme)
37
- begin
38
- rule.run_fixture_validations(expect_symbol_keys, backtrace.dup.append(key))
39
- nil
40
- rescue AssertionError => e
41
- e.fixture_errors
42
- end
43
- end
44
- }.compact
45
-
46
- raise AssertionError.new(fixture_errors) unless fixture_errors.empty?
47
- end
48
-
49
- private
50
-
51
- attr_accessor :links
52
- end
53
- end
54
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'media_types/scheme/rules'
4
+ require 'media_types/scheme/rules_exhausted_guard'
5
+
6
+ module MediaTypes
7
+ class Scheme
8
+ class Links
9
+ def initialize
10
+ self.links = Rules.new(allow_empty: false, expected_type: ::Hash)
11
+ end
12
+
13
+ def link(key, allow_nil: false, optional: false, &block)
14
+ links.add(
15
+ key,
16
+ Scheme.new do
17
+ attribute :href, String, allow_nil: allow_nil
18
+ instance_exec(&block) if block_given?
19
+ end,
20
+ optional: optional
21
+ )
22
+
23
+ self
24
+ end
25
+
26
+ def validate!(output, options, **_opts)
27
+ RulesExhaustedGuard.call(output, options, rules: links)
28
+ end
29
+
30
+ def inspect
31
+ "[Scheme::Links #{links.keys}]"
32
+ end
33
+
34
+ def run_fixture_validations(expect_symbol_keys, backtrace = [])
35
+ fixture_errors = @links.flat_map {|key, rule|
36
+ if rule.is_a?(Scheme)
37
+ begin
38
+ rule.run_fixture_validations(expect_symbol_keys, backtrace.dup.append(key))
39
+ nil
40
+ rescue AssertionError => e
41
+ e.fixture_errors
42
+ end
43
+ end
44
+ }.compact
45
+
46
+ raise AssertionError.new(fixture_errors) unless fixture_errors.empty?
47
+ end
48
+
49
+ private
50
+
51
+ attr_accessor :links
52
+ end
53
+ end
54
+ end