stannum 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +85 -21
- data/config/locales/en.rb +17 -3
- data/lib/stannum/constraints/base.rb +11 -4
- data/lib/stannum/constraints/hashes/extra_keys.rb +10 -2
- data/lib/stannum/constraints/hashes/indifferent_extra_keys.rb +47 -0
- data/lib/stannum/constraints/hashes.rb +6 -2
- data/lib/stannum/constraints/parameters/extra_arguments.rb +23 -0
- data/lib/stannum/constraints/parameters/extra_keywords.rb +29 -0
- data/lib/stannum/constraints/parameters.rb +11 -0
- data/lib/stannum/constraints/properties/base.rb +124 -0
- data/lib/stannum/constraints/properties/do_not_match_property.rb +117 -0
- data/lib/stannum/constraints/properties/match_property.rb +117 -0
- data/lib/stannum/constraints/properties/matching.rb +112 -0
- data/lib/stannum/constraints/properties.rb +17 -0
- data/lib/stannum/constraints/tuples/extra_items.rb +1 -1
- data/lib/stannum/constraints/type.rb +1 -1
- data/lib/stannum/constraints/types/hash_type.rb +6 -2
- data/lib/stannum/constraints.rb +2 -0
- data/lib/stannum/contracts/builder.rb +13 -2
- data/lib/stannum/contracts/hash_contract.rb +14 -0
- data/lib/stannum/contracts/indifferent_hash_contract.rb +13 -0
- data/lib/stannum/contracts/parameters/arguments_contract.rb +2 -7
- data/lib/stannum/contracts/parameters/keywords_contract.rb +2 -7
- data/lib/stannum/contracts/tuple_contract.rb +1 -1
- data/lib/stannum/entities/attributes.rb +218 -0
- data/lib/stannum/entities/constraints.rb +177 -0
- data/lib/stannum/entities/properties.rb +186 -0
- data/lib/stannum/entities.rb +13 -0
- data/lib/stannum/entity.rb +83 -0
- data/lib/stannum/errors.rb +3 -3
- data/lib/stannum/messages/default_loader.rb +95 -0
- data/lib/stannum/messages/default_strategy.rb +31 -50
- data/lib/stannum/messages.rb +1 -0
- data/lib/stannum/rspec/match_errors_matcher.rb +6 -6
- data/lib/stannum/rspec/validate_parameter_matcher.rb +10 -9
- data/lib/stannum/schema.rb +78 -37
- data/lib/stannum/struct.rb +12 -346
- data/lib/stannum/support/coercion.rb +19 -0
- data/lib/stannum/version.rb +1 -1
- data/lib/stannum.rb +3 -0
- metadata +29 -19
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sleeping_king_studios/tools/toolbelt'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
require 'stannum/messages'
|
7
|
+
|
8
|
+
module Stannum::Messages
|
9
|
+
# Loads and parses messages configuration files and merges configuration data.
|
10
|
+
class DefaultLoader
|
11
|
+
# @param file_paths [Array<String>] The directories from which to load the
|
12
|
+
# configuration files.
|
13
|
+
# @param locale [String] The name of the locale for which to load
|
14
|
+
# configuration.
|
15
|
+
def initialize(file_paths:, locale: 'en')
|
16
|
+
@file_paths = file_paths
|
17
|
+
@locale = locale
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Array<String>] the directories from which to load the
|
21
|
+
# configuration files.
|
22
|
+
attr_reader :file_paths
|
23
|
+
|
24
|
+
# @return [String] the name of the locale for which to load configuration.
|
25
|
+
attr_reader :locale
|
26
|
+
|
27
|
+
# Loads and parses each file, then deep merges the data from each file.
|
28
|
+
#
|
29
|
+
# The configuration file should be either a Ruby file or a YAML file, with
|
30
|
+
# the filename of the format locale.extname, e.g. en.rb or en-gb.yml, and
|
31
|
+
# located in one of the directories defined in #file_paths.
|
32
|
+
#
|
33
|
+
# The contents of each file should be either a Ruby Hash or a YAML document
|
34
|
+
# containing an associative array, with a single key equal to the locale.
|
35
|
+
# The value of the key must be a Hash or associative array, which contains
|
36
|
+
# the scoped messages to load.
|
37
|
+
#
|
38
|
+
# Each file is read in order and parsed into a Hash. Each hash is then deep
|
39
|
+
# merged in sequence, with nested hashes merged together instead of
|
40
|
+
# overwritten.
|
41
|
+
#
|
42
|
+
# @return [Hash<Symbol, Object>] the merged configuration data.
|
43
|
+
def call
|
44
|
+
file_paths.reduce({}) do |config, file_path|
|
45
|
+
loaded = load_configuration_file(file_path)
|
46
|
+
|
47
|
+
deep_update(config, loaded)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
alias load call
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def deep_update(original, target)
|
55
|
+
target.each do |key, value|
|
56
|
+
if original[key].is_a?(Hash) && value.is_a?(Hash)
|
57
|
+
deep_update(original[key], value)
|
58
|
+
else
|
59
|
+
original[key] = value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
original
|
64
|
+
end
|
65
|
+
|
66
|
+
def load_configuration_file(file_path)
|
67
|
+
ruby_file = File.join(file_path, "#{locale}.rb")
|
68
|
+
|
69
|
+
return read_ruby_file(ruby_file) if File.exist?(ruby_file)
|
70
|
+
|
71
|
+
yaml_file = File.join(file_path, "#{locale}.yml")
|
72
|
+
|
73
|
+
return read_yaml_file(yaml_file) if File.exist?(yaml_file)
|
74
|
+
|
75
|
+
{}
|
76
|
+
end
|
77
|
+
|
78
|
+
def read_ruby_file(filename)
|
79
|
+
ruby = File.read(filename)
|
80
|
+
|
81
|
+
eval(ruby, binding, filename) # rubocop:disable Security/Eval
|
82
|
+
end
|
83
|
+
|
84
|
+
def read_yaml_file(filename)
|
85
|
+
raw = File.read(filename)
|
86
|
+
yaml = YAML.safe_load(raw)
|
87
|
+
|
88
|
+
tools.hsh.convert_keys_to_symbols(yaml)
|
89
|
+
end
|
90
|
+
|
91
|
+
def tools
|
92
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -9,16 +9,28 @@ require 'stannum/messages'
|
|
9
9
|
module Stannum::Messages
|
10
10
|
# Strategy to generate error messages from gem configuration.
|
11
11
|
class DefaultStrategy
|
12
|
+
# The default directories from which to load configured error messages.
|
13
|
+
DEFAULT_LOAD_PATHS = [Stannum::Messages.locales_path].freeze
|
14
|
+
|
15
|
+
# @return [Array<String>] The directories from which to load configured
|
16
|
+
# error messages.
|
17
|
+
def self.load_paths
|
18
|
+
@load_paths ||= DEFAULT_LOAD_PATHS.dup
|
19
|
+
end
|
20
|
+
|
12
21
|
# @param configuration [Hash{Symbol, Object}] The configured messages.
|
13
|
-
# @param
|
14
|
-
#
|
15
|
-
|
16
|
-
|
22
|
+
# @param load_paths [Array<String>] The directories from which to load
|
23
|
+
# configured error messages.
|
24
|
+
# @param locale [String] The locale used to load and scope configured
|
25
|
+
# messages.
|
26
|
+
def initialize(configuration: nil, load_paths: nil, locale: 'en')
|
27
|
+
@load_paths = Array(load_paths) unless load_paths.nil?
|
28
|
+
@locale = locale
|
17
29
|
@configuration = configuration
|
18
30
|
end
|
19
31
|
|
20
|
-
# @return [
|
21
|
-
attr_reader :
|
32
|
+
# @return [String] The locale used to load and scope configured messages.
|
33
|
+
attr_reader :locale
|
22
34
|
|
23
35
|
# @param error_type [String] The qualified path to the configured error
|
24
36
|
# message.
|
@@ -34,6 +46,12 @@ module Stannum::Messages
|
|
34
46
|
interpolate_message(message, options)
|
35
47
|
end
|
36
48
|
|
49
|
+
# @return [Array<String>] The directories from which to load configured
|
50
|
+
# error messages.
|
51
|
+
def load_paths
|
52
|
+
@load_paths || self.class.load_paths
|
53
|
+
end
|
54
|
+
|
37
55
|
# Reloads the configuration from the configured load_path.
|
38
56
|
#
|
39
57
|
# This can be useful when the load_path is updated after creating the
|
@@ -52,23 +70,9 @@ module Stannum::Messages
|
|
52
70
|
@configuration ||= load_configuration
|
53
71
|
end
|
54
72
|
|
55
|
-
def deep_merge(source, target)
|
56
|
-
hsh = tools.hash_tools.deep_dup(source)
|
57
|
-
|
58
|
-
target.each do |key, value|
|
59
|
-
hsh[key] = value.is_a?(Hash) ? deep_merge(hsh[key] || {}, value) : value
|
60
|
-
end
|
61
|
-
|
62
|
-
hsh
|
63
|
-
end
|
64
|
-
|
65
|
-
def default_filename
|
66
|
-
File.join(Stannum::Messages.locales_path, 'en.rb')
|
67
|
-
end
|
68
|
-
|
69
73
|
def generate_message(error_type, options)
|
70
74
|
path = error_type.to_s.split('.').map(&:intern)
|
71
|
-
path.unshift(
|
75
|
+
path.unshift(locale.intern)
|
72
76
|
|
73
77
|
message = configuration.dig(*path)
|
74
78
|
|
@@ -90,35 +94,12 @@ module Stannum::Messages
|
|
90
94
|
end
|
91
95
|
|
92
96
|
def load_configuration
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
case File.extname(filename)
|
100
|
-
when '.rb'
|
101
|
-
read_ruby_file(filename)
|
102
|
-
when '.yml'
|
103
|
-
read_yaml_file(filename)
|
104
|
-
else
|
105
|
-
raise "unable to load configuration file #{filename} with extension" \
|
106
|
-
" #{File.extname(filename)}"
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def read_ruby_file(filename)
|
111
|
-
eval(File.read(filename), binding, filename) # rubocop:disable Security/Eval
|
112
|
-
end
|
113
|
-
|
114
|
-
def read_yaml_file(filename)
|
115
|
-
tools.hash_tools.convert_keys_to_symbols(
|
116
|
-
YAML.safe_load(File.read(filename))
|
117
|
-
)
|
118
|
-
end
|
119
|
-
|
120
|
-
def tools
|
121
|
-
SleepingKingStudios::Tools::Toolbelt.instance
|
97
|
+
Stannum::Messages::DefaultLoader
|
98
|
+
.new(
|
99
|
+
file_paths: load_paths,
|
100
|
+
locale: locale
|
101
|
+
)
|
102
|
+
.call
|
122
103
|
end
|
123
104
|
end
|
124
105
|
end
|
data/lib/stannum/messages.rb
CHANGED
@@ -5,6 +5,7 @@ require 'stannum'
|
|
5
5
|
module Stannum
|
6
6
|
# Namespace for generating messages for Stannum::Errors.
|
7
7
|
module Messages
|
8
|
+
autoload :DefaultLoader, 'stannum/messages/default_loader'
|
8
9
|
autoload :DefaultStrategy, 'stannum/messages/default_strategy'
|
9
10
|
|
10
11
|
# @return [String] the absolute path to the configured locales.
|
@@ -4,8 +4,8 @@ begin
|
|
4
4
|
require 'rspec/sleeping_king_studios/matchers/core/deep_matcher'
|
5
5
|
rescue NameError
|
6
6
|
# :nocov:
|
7
|
-
Kernel.warn 'WARNING: RSpec::SleepingKingStudios is a dependency for using' \
|
8
|
-
'
|
7
|
+
Kernel.warn 'WARNING: RSpec::SleepingKingStudios is a dependency for using ' \
|
8
|
+
'the MatchErrorsMatcher or the #match_errors method.'
|
9
9
|
# :nocov:
|
10
10
|
end
|
11
11
|
|
@@ -40,8 +40,8 @@ module Stannum::RSpec
|
|
40
40
|
# @return [String] a summary message describing a failed expectation.
|
41
41
|
def failure_message
|
42
42
|
unless errors?
|
43
|
-
return 'expected the errors to match the expected errors, but the' \
|
44
|
-
'
|
43
|
+
return 'expected the errors to match the expected errors, but the ' \
|
44
|
+
'object is not an array or Errors object'
|
45
45
|
end
|
46
46
|
|
47
47
|
equality_matcher.failure_message
|
@@ -51,8 +51,8 @@ module Stannum::RSpec
|
|
51
51
|
# expectation.
|
52
52
|
def failure_message_when_negated
|
53
53
|
unless errors?
|
54
|
-
return 'expected the errors not to match the expected errors, but
|
55
|
-
' object is not an array or Errors object'
|
54
|
+
return 'expected the errors not to match the expected errors, but ' \
|
55
|
+
'the object is not an array or Errors object'
|
56
56
|
end
|
57
57
|
|
58
58
|
equality_matcher.failure_message_when_negated
|
@@ -6,6 +6,7 @@ rescue NameError
|
|
6
6
|
# Optional dependency.
|
7
7
|
end
|
8
8
|
|
9
|
+
require 'stannum/constraints/parameters/extra_keywords'
|
9
10
|
require 'stannum/rspec'
|
10
11
|
require 'stannum/support/coercion'
|
11
12
|
|
@@ -138,11 +139,11 @@ module Stannum::RSpec
|
|
138
139
|
when :method_does_not_have_parameter
|
139
140
|
"##{method_name} does not have a #{parameter_name.inspect} parameter"
|
140
141
|
when :parameter_not_validated
|
141
|
-
"##{method_name} does not expect a #{parameter_name.inspect}" \
|
142
|
-
"
|
142
|
+
"##{method_name} does not expect a #{parameter_name.inspect} " \
|
143
|
+
"#{parameter_type}"
|
143
144
|
when :valid_parameter_value
|
144
|
-
"#{valid_value.inspect} is a valid value for the" \
|
145
|
-
"
|
145
|
+
"#{valid_value.inspect} is a valid value for the " \
|
146
|
+
"#{parameter_name.inspect} #{parameter_type}"
|
146
147
|
end
|
147
148
|
|
148
149
|
[message, reason].compact.join(', but ')
|
@@ -280,20 +281,20 @@ module Stannum::RSpec
|
|
280
281
|
unless @expected_constraint.nil?
|
281
282
|
raise RuntimeError,
|
282
283
|
'#does_not_match? with #using_constraint is not supported',
|
283
|
-
caller[1
|
284
|
+
caller[1..]
|
284
285
|
end
|
285
286
|
|
286
287
|
unless @parameters.nil?
|
287
288
|
raise RuntimeError,
|
288
289
|
'#does_not_match? with #with_parameters is not supported',
|
289
|
-
caller[1
|
290
|
+
caller[1..]
|
290
291
|
end
|
291
292
|
|
292
293
|
return if @parameter_value.nil?
|
293
294
|
|
294
295
|
raise RuntimeError,
|
295
296
|
'#does_not_match? with #with_value is not supported',
|
296
|
-
caller[1
|
297
|
+
caller[1..]
|
297
298
|
end
|
298
299
|
|
299
300
|
def equality_matcher
|
@@ -313,9 +314,9 @@ module Stannum::RSpec
|
|
313
314
|
|
314
315
|
def extra_parameter?
|
315
316
|
extra_arguments_type =
|
316
|
-
Stannum::
|
317
|
+
Stannum::Constraints::Parameters::ExtraArguments::TYPE
|
317
318
|
extra_keywords_type =
|
318
|
-
Stannum::
|
319
|
+
Stannum::Constraints::Parameters::ExtraKeywords::TYPE
|
319
320
|
|
320
321
|
return false unless scoped_errors(indexed: true).any? do |error|
|
321
322
|
error[:type] == extra_arguments_type ||
|
data/lib/stannum/schema.rb
CHANGED
@@ -18,27 +18,6 @@ module Stannum
|
|
18
18
|
@attributes = {}
|
19
19
|
end
|
20
20
|
|
21
|
-
# @!method each
|
22
|
-
# Iterates through the the attributes by name and attribute object.
|
23
|
-
#
|
24
|
-
# @yieldparam name [String] The name of the attribute.
|
25
|
-
# @yieldparam attribute [Stannum::Attribute] The attribute object.
|
26
|
-
|
27
|
-
# @!method each_key
|
28
|
-
# Iterates through the the attributes by name.
|
29
|
-
#
|
30
|
-
# @yieldparam name [String] The name of the attribute.
|
31
|
-
|
32
|
-
# @!method each_value
|
33
|
-
# Iterates through the the attributes by attribute object.
|
34
|
-
#
|
35
|
-
# @yieldparam attribute [Stannum::Attribute] The attribute object.
|
36
|
-
|
37
|
-
def_delegators :attributes,
|
38
|
-
:each,
|
39
|
-
:each_key,
|
40
|
-
:each_value
|
41
|
-
|
42
21
|
# Retrieves the named attribute object.
|
43
22
|
#
|
44
23
|
# @param key [String, Symbol] The name of the requested attribute.
|
@@ -48,9 +27,17 @@ module Stannum
|
|
48
27
|
# @raise ArgumentError if the key is invalid.
|
49
28
|
# @raise KeyError if the attribute is not defined.
|
50
29
|
def [](key)
|
51
|
-
|
30
|
+
tools.assertions.assert_name(key, as: 'key', error_class: ArgumentError)
|
31
|
+
|
32
|
+
str = -key.to_s
|
52
33
|
|
53
|
-
|
34
|
+
each_ancestor do |ancestor|
|
35
|
+
next unless ancestor.own_attributes.key?(str)
|
36
|
+
|
37
|
+
return ancestor.own_attributes[str]
|
38
|
+
end
|
39
|
+
|
40
|
+
{}.fetch(str)
|
54
41
|
end
|
55
42
|
|
56
43
|
# rubocop:disable Metrics/MethodLength
|
@@ -81,33 +68,83 @@ module Stannum
|
|
81
68
|
end
|
82
69
|
# rubocop:enable Metrics/MethodLength
|
83
70
|
|
71
|
+
# Iterates through the the attributes by name and attribute object.
|
72
|
+
#
|
73
|
+
# @yieldparam name [String] The name of the attribute.
|
74
|
+
# @yieldparam attribute [Stannum::Attribute] The attribute object.
|
75
|
+
def each(&block)
|
76
|
+
return enum_for(:each) { size } unless block_given?
|
77
|
+
|
78
|
+
each_ancestor do |ancestor|
|
79
|
+
ancestor.own_attributes.each(&block)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Iterates through the the attributes by name.
|
84
|
+
#
|
85
|
+
# @yieldparam name [String] The name of the attribute.
|
86
|
+
def each_key(&block)
|
87
|
+
return enum_for(:each_key) { size } unless block_given?
|
88
|
+
|
89
|
+
each_ancestor do |ancestor|
|
90
|
+
ancestor.own_attributes.each_key(&block)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Iterates through the the attributes by attribute object.
|
95
|
+
#
|
96
|
+
# @yieldparam attribute [Stannum::Attribute] The attribute object.
|
97
|
+
def each_value(&block)
|
98
|
+
return enum_for(:each_value) { size } unless block_given?
|
99
|
+
|
100
|
+
each_ancestor do |ancestor|
|
101
|
+
ancestor.own_attributes.each_value(&block)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
84
105
|
# Checks if the given attribute is defined.
|
85
106
|
#
|
86
107
|
# @param key [String, Symbol] the name of the attribute to check.
|
87
108
|
#
|
88
109
|
# @return [Boolean] true if the attribute is defined; otherwise false.
|
89
110
|
def key?(key)
|
90
|
-
|
111
|
+
tools.assertions.assert_name(key, as: 'key', error_class: ArgumentError)
|
91
112
|
|
92
|
-
|
113
|
+
each_ancestor.any? do |ancestor|
|
114
|
+
ancestor.own_attributes.key?(key.to_s)
|
115
|
+
end
|
93
116
|
end
|
94
117
|
alias has_key? key?
|
95
118
|
|
119
|
+
# Returns the defined attribute keys.
|
120
|
+
#
|
121
|
+
# @return [Array<String>] the attribute keys.
|
122
|
+
def keys
|
123
|
+
each_key.to_a
|
124
|
+
end
|
125
|
+
|
96
126
|
# @private
|
97
127
|
def own_attributes
|
98
128
|
@attributes
|
99
129
|
end
|
100
130
|
|
101
|
-
|
131
|
+
# @return [Integer] the number of defined attributes.
|
132
|
+
def size
|
133
|
+
each_ancestor.reduce(0) do |memo, ancestor|
|
134
|
+
memo + ancestor.own_attributes.size
|
135
|
+
end
|
136
|
+
end
|
137
|
+
alias count size
|
102
138
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
.reduce(&:merge)
|
139
|
+
# Returns the defined attribute value.
|
140
|
+
#
|
141
|
+
# @return [Array<Stannum::Attribute>] the attribute values.
|
142
|
+
def values
|
143
|
+
each_value.to_a
|
109
144
|
end
|
110
145
|
|
146
|
+
private
|
147
|
+
|
111
148
|
def define_reader(attr_name, reader_name)
|
112
149
|
define_method(reader_name) { @attributes[attr_name] }
|
113
150
|
end
|
@@ -118,14 +155,18 @@ module Stannum
|
|
118
155
|
end
|
119
156
|
end
|
120
157
|
|
121
|
-
def
|
122
|
-
|
158
|
+
def each_ancestor
|
159
|
+
return enum_for(:each_ancestor) unless block_given?
|
123
160
|
|
124
|
-
|
125
|
-
|
161
|
+
ancestors.reverse_each do |ancestor|
|
162
|
+
break unless ancestor.is_a?(Stannum::Schema)
|
163
|
+
|
164
|
+
yield ancestor
|
126
165
|
end
|
166
|
+
end
|
127
167
|
|
128
|
-
|
168
|
+
def tools
|
169
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
129
170
|
end
|
130
171
|
end
|
131
172
|
end
|