stannum 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +85 -21
  4. data/config/locales/en.rb +17 -3
  5. data/lib/stannum/constraints/base.rb +11 -4
  6. data/lib/stannum/constraints/hashes/extra_keys.rb +10 -2
  7. data/lib/stannum/constraints/hashes/indifferent_extra_keys.rb +47 -0
  8. data/lib/stannum/constraints/hashes.rb +6 -2
  9. data/lib/stannum/constraints/parameters/extra_arguments.rb +23 -0
  10. data/lib/stannum/constraints/parameters/extra_keywords.rb +29 -0
  11. data/lib/stannum/constraints/parameters.rb +11 -0
  12. data/lib/stannum/constraints/properties/base.rb +124 -0
  13. data/lib/stannum/constraints/properties/do_not_match_property.rb +117 -0
  14. data/lib/stannum/constraints/properties/match_property.rb +117 -0
  15. data/lib/stannum/constraints/properties/matching.rb +112 -0
  16. data/lib/stannum/constraints/properties.rb +17 -0
  17. data/lib/stannum/constraints/tuples/extra_items.rb +1 -1
  18. data/lib/stannum/constraints/type.rb +1 -1
  19. data/lib/stannum/constraints/types/hash_type.rb +6 -2
  20. data/lib/stannum/constraints.rb +2 -0
  21. data/lib/stannum/contracts/builder.rb +13 -2
  22. data/lib/stannum/contracts/hash_contract.rb +14 -0
  23. data/lib/stannum/contracts/indifferent_hash_contract.rb +13 -0
  24. data/lib/stannum/contracts/parameters/arguments_contract.rb +2 -7
  25. data/lib/stannum/contracts/parameters/keywords_contract.rb +2 -7
  26. data/lib/stannum/contracts/tuple_contract.rb +1 -1
  27. data/lib/stannum/entities/attributes.rb +218 -0
  28. data/lib/stannum/entities/constraints.rb +177 -0
  29. data/lib/stannum/entities/properties.rb +186 -0
  30. data/lib/stannum/entities.rb +13 -0
  31. data/lib/stannum/entity.rb +83 -0
  32. data/lib/stannum/errors.rb +3 -3
  33. data/lib/stannum/messages/default_loader.rb +95 -0
  34. data/lib/stannum/messages/default_strategy.rb +31 -50
  35. data/lib/stannum/messages.rb +1 -0
  36. data/lib/stannum/rspec/match_errors_matcher.rb +6 -6
  37. data/lib/stannum/rspec/validate_parameter_matcher.rb +10 -9
  38. data/lib/stannum/schema.rb +78 -37
  39. data/lib/stannum/struct.rb +12 -346
  40. data/lib/stannum/support/coercion.rb +19 -0
  41. data/lib/stannum/version.rb +1 -1
  42. data/lib/stannum.rb +3 -0
  43. metadata +29 -19
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'sleeping_king_studios/tools/toolbox/mixin'
4
-
5
- require 'stannum/schema'
3
+ require 'stannum/entities/attributes'
4
+ require 'stannum/entities/constraints'
5
+ require 'stannum/entities/properties'
6
6
 
7
7
  module Stannum
8
8
  # Abstract class for defining objects with structured attributes.
@@ -74,200 +74,10 @@ module Stannum
74
74
  # name: 'Diode',
75
75
  # description: 'A low budget Diode',
76
76
  # ) #=> true
77
- module Struct # rubocop:disable Metrics/ModuleLength
78
- extend SleepingKingStudios::Tools::Toolbox::Mixin
79
-
80
- # Class methods to extend the class when including Stannum::Struct.
81
- module ClassMethods
82
- # rubocop:disable Metrics/MethodLength
83
-
84
- # Defines an attribute on the struct.
85
- #
86
- # When an attribute is defined, each of the following steps is executed:
87
- #
88
- # - Adds the attribute to ::Attributes and the .attributes class method.
89
- # - Adds the attribute to #attributes and the associated methods, such as
90
- # #assign_attributes, #[] and #[]=.
91
- # - Defines reader and writer methods.
92
- # - Adds a type constraint to ::Attributes::Contract, and indirectly to
93
- # ::Contract.
94
- #
95
- # @param attr_name [String, Symbol] The name of the attribute. Must be a
96
- # non-empty String or Symbol.
97
- # @param attr_type [Class, String] The type of the attribute. Must be a
98
- # Class or Module, or the name of a class or module.
99
- # @param options [Hash] Additional options for the attribute.
100
- #
101
- # @option options [Object] :default The default value for the attribute.
102
- # Defaults to nil.
103
- #
104
- # @return [Symbol] The attribute name as a symbol.
105
- def attribute(attr_name, attr_type, **options)
106
- attribute = attributes.define_attribute(
107
- name: attr_name,
108
- type: attr_type,
109
- options: options
110
- )
111
- constraint = Stannum::Constraints::Type.new(
112
- attribute.type,
113
- required: attribute.required?
114
- )
115
-
116
- self::Contract.add_constraint(
117
- constraint,
118
- property: attribute.reader_name
119
- )
120
-
121
- attr_name.intern
122
- end
123
- # rubocop:enable Metrics/MethodLength
124
-
125
- # @return [Stannum::Schema] The Schema object for the Struct.
126
- def attributes
127
- self::Attributes
128
- end
129
-
130
- # Defines a constraint on the struct or one of its properties.
131
- #
132
- # @overload constraint()
133
- # Defines a constraint on the struct.
134
- #
135
- # A new Stannum::Constraint instance will be generated, passing the
136
- # block from .constraint to the new constraint. This constraint will be
137
- # added to the contract.
138
- #
139
- # @yieldparam struct [Stannum::Struct] The struct at the time the
140
- # constraint is evaluated.
141
- #
142
- # @overload constraint(constraint)
143
- # Defines a constraint on the struct.
144
- #
145
- # The given constraint is added to the contract. When the contract is
146
- # evaluated, this constraint will be matched against the struct.
147
- #
148
- # @param constraint [Stannum::Constraints::Base] The constraint to add.
149
- #
150
- # @overload constraint(attr_name)
151
- # Defines a constraint on the given attribute or property.
152
- #
153
- # A new Stannum::Constraint instance will be generated, passing the
154
- # block from .constraint to the new constraint. This constraint will be
155
- # added to the contract.
156
- #
157
- # @param attr_name [String, Symbol] The name of the attribute or
158
- # property to constrain.
159
- #
160
- # @yieldparam value [Object] The value of the attribute or property of
161
- # the struct at the time the constraint is evaluated.
162
- #
163
- # @overload constraint(attr_name, constraint)
164
- # Defines a constraint on the given attribute or property.
165
- #
166
- # The given constraint is added to the contract. When the contract is
167
- # evaluated, this constraint will be matched against the value of the
168
- # attribute or property.
169
- #
170
- # @param attr_name [String, Symbol] The name of the attribute or
171
- # property to constrain.
172
- # @param constraint [Stannum::Constraints::Base] The constraint to add.
173
- def constraint(attr_name = nil, constraint = nil, &block)
174
- attr_name, constraint = resolve_constraint(attr_name, constraint)
175
-
176
- if block_given?
177
- constraint = Stannum::Constraint.new(&block)
178
- else
179
- validate_constraint(constraint)
180
- end
181
-
182
- contract.add_constraint(constraint, property: attr_name)
183
- end
184
-
185
- # @return [Stannum::Contract] The Contract object for the Struct.
186
- def contract
187
- self::Contract
188
- end
189
-
190
- private
191
-
192
- # @api private
193
- #
194
- # Hook to execute when a struct class is subclassed.
195
- def inherited(other)
196
- super
197
-
198
- Struct.build(other) if other.is_a?(Class)
199
- end
200
-
201
- def resolve_constraint(attr_name, constraint)
202
- return [nil, attr_name] if attr_name.is_a?(Stannum::Constraints::Base)
203
-
204
- validate_attribute_name(attr_name)
205
-
206
- [attr_name.nil? ? attr_name : attr_name.intern, constraint]
207
- end
208
-
209
- def validate_attribute_name(name)
210
- return if name.nil?
211
-
212
- unless name.is_a?(String) || name.is_a?(Symbol)
213
- raise ArgumentError, 'attribute must be a String or Symbol'
214
- end
215
-
216
- raise ArgumentError, "attribute can't be blank" if name.empty?
217
- end
218
-
219
- def validate_constraint(constraint)
220
- raise ArgumentError, "constraint can't be blank" if constraint.nil?
221
-
222
- return if constraint.is_a?(Stannum::Constraints::Base)
223
-
224
- raise ArgumentError, 'constraint must be a Stannum::Constraints::Base'
225
- end
226
- end
227
-
228
- class << self
229
- # @private
230
- def build(struct_class)
231
- return if struct_class?(struct_class)
232
-
233
- initialize_attributes(struct_class)
234
- initialize_contract(struct_class)
235
- end
236
-
237
- private
238
-
239
- def included(other)
240
- super
241
-
242
- Struct.build(other) if other.is_a?(Class)
243
- end
244
-
245
- def initialize_attributes(struct_class)
246
- attributes = Stannum::Schema.new
247
-
248
- struct_class.const_set(:Attributes, attributes)
249
-
250
- if struct_class?(struct_class.superclass)
251
- attributes.include(struct_class.superclass::Attributes)
252
- end
253
-
254
- struct_class.include(attributes)
255
- end
256
-
257
- def initialize_contract(struct_class)
258
- contract = Stannum::Contract.new
259
-
260
- struct_class.const_set(:Contract, contract)
261
-
262
- return unless struct_class?(struct_class.superclass)
263
-
264
- contract.concat(struct_class.superclass::Contract)
265
- end
266
-
267
- def struct_class?(struct_class)
268
- struct_class.const_defined?(:Attributes, false)
269
- end
270
- end
77
+ module Struct
78
+ include Stannum::Entities::Properties
79
+ include Stannum::Entities::Attributes
80
+ include Stannum::Entities::Constraints
271
81
 
272
82
  # Initializes the struct with the given attributes.
273
83
  #
@@ -285,160 +95,16 @@ module Stannum
285
95
  #
286
96
  # @raise ArgumentError if given an invalid attributes hash.
287
97
  def initialize(attributes = {})
288
- @attributes = {}
289
-
290
- self.attributes = attributes
291
- end
292
-
293
- # Compares the struct with the other object.
294
- #
295
- # The other object must be an instance of the current class. In addition,
296
- # the attributes hashes of the two objects must be equal.
297
- #
298
- # @return true if the object is a matching struct.
299
- def ==(other)
300
- return false unless other.class == self.class
301
-
302
- raw_attributes == other.raw_attributes
303
- end
304
-
305
- # Retrieves the attribute with the given key.
306
- #
307
- # @param key [String, Symbol] The attribute key.
308
- #
309
- # @return [Object] the value of the attribute.
310
- #
311
- # @raise ArgumentError if the key is not a valid attribute.
312
- def [](key)
313
- validate_attribute_key(key)
314
-
315
- send(self.class::Attributes[key].reader_name)
316
- end
317
-
318
- # Sets the given attribute to the given value.
319
- #
320
- # @param key [String, Symbol] The attribute key.
321
- # @param value [Object] The value for the attribute.
322
- #
323
- # @raise ArgumentError if the key is not a valid attribute.
324
- def []=(key, value)
325
- validate_attribute_key(key)
326
-
327
- send(self.class::Attributes[key].writer_name, value)
328
- end
329
-
330
- # Updates the struct's attributes with the given values.
331
- #
332
- # This method is used to update some (but not all) of the attributes of the
333
- # struct. For each key in the hash, it calls the corresponding writer method
334
- # with the value for that attribute. If the value is nil, this will set the
335
- # attribute value to the default for that attribute.
336
- #
337
- # Any attributes that are not in the given hash are unchanged.
338
- #
339
- # If the attributes hash includes any keys that do not correspond to an
340
- # attribute, the struct will raise an error.
341
- #
342
- # @param attributes [Hash] The initial attributes for the struct.
343
- #
344
- # @raise ArgumentError if the key is not a valid attribute.
345
- #
346
- # @see #attributes=
347
- def assign_attributes(attributes)
348
- unless attributes.is_a?(Hash)
349
- raise ArgumentError, 'attributes must be a Hash'
350
- end
351
-
352
- attributes.each do |attr_name, value|
353
- validate_attribute_key(attr_name)
354
-
355
- attribute = self.class.attributes[attr_name]
356
-
357
- send(attribute.writer_name, value)
358
- end
359
- end
360
- alias assign assign_attributes
361
-
362
- # @return [Hash] the current attributes of the struct.
363
- def attributes
364
- tools.hash_tools.deep_dup(@attributes)
365
- end
366
- alias to_h attributes
367
-
368
- # Replaces the struct's attributes with the given values.
369
- #
370
- # This method is used to update all of the attributes of the struct. For
371
- # each attribute, the writer method is called with the value from the hash,
372
- # or nil if the corresponding key is not present in the hash. Any nil or
373
- # missing keys set the attribute value to the attribute's default value.
374
- #
375
- # If the attributes hash includes any keys that do not correspond to an
376
- # attribute, the struct will raise an error.
377
- #
378
- # @param attributes [Hash] The initial attributes for the struct.
379
- #
380
- # @raise ArgumentError if the key is not a valid attribute.
381
- #
382
- # @see #assign_attributes
383
- def attributes=(attributes) # rubocop:disable Metrics/MethodLength
384
98
  unless attributes.is_a?(Hash)
385
99
  raise ArgumentError, 'attributes must be a Hash'
386
100
  end
387
101
 
388
- attributes.each_key { |attr_name| validate_attribute_key(attr_name) }
389
-
390
- self.class::Attributes.each_value do |attribute|
391
- send(
392
- attribute.writer_name,
393
- attributes.fetch(
394
- attribute.name,
395
- attributes.fetch(attribute.name.intern, attribute.default)
396
- )
397
- )
398
- end
399
- end
400
-
401
- # @return [String] a string representation of the struct and its attributes.
402
- def inspect # rubocop:disable Metrics/AbcSize
403
- if self.class.attributes.each_key.size.zero?
404
- return "#<#{self.class.name}>"
405
- end
406
-
407
- buffer = +"#<#{self.class.name}"
408
-
409
- self.class.attributes.each_key.with_index \
410
- do |attribute, index|
411
- buffer << ',' unless index.zero?
412
- buffer << " #{attribute}: #{@attributes[attribute].inspect}"
413
- end
414
-
415
- buffer << '>'
416
- end
417
-
418
- protected
419
-
420
- def raw_attributes
421
- @attributes
422
- end
423
-
424
- private
425
-
426
- def tools
427
- SleepingKingStudios::Tools::Toolbelt.instance
428
- end
429
-
430
- def validate_attribute_key(key)
431
- raise ArgumentError, "attribute can't be blank" if key.nil?
432
-
433
- unless key.is_a?(String) || key.is_a?(Symbol)
434
- raise ArgumentError, 'attribute must be a String or Symbol'
435
- end
436
-
437
- raise ArgumentError, "attribute can't be blank" if key.empty?
438
-
439
- return if self.class::Attributes.key?(key.to_s)
102
+ super(**attributes)
440
103
 
441
- raise ArgumentError, "unknown attribute #{key.inspect}"
104
+ SleepingKingStudios::Tools::CoreTools.deprecate(
105
+ 'Stannum::Struct',
106
+ 'use Stannum::Entity instead'
107
+ )
442
108
  end
443
109
  end
444
110
  end
@@ -1,11 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  require 'stannum/support'
4
6
 
5
7
  module Stannum::Support
6
8
  # Shared functionality for coercing values to and from constraints.
7
9
  module Coercion
8
10
  class << self
11
+ ERROR_KEY_TYPES = Set.new([Integer, String, Symbol]).freeze
12
+ private_constant :ERROR_KEY_TYPES
13
+
14
+ # Coerces an arbitrary object into a valid Stannum::Errors key.
15
+ #
16
+ # If the value is an Integer, a String, or a Symbol, returns the value.
17
+ # Otherwise, returns the result of calling #inspect on the value.
18
+ #
19
+ # @param value [Object] The value to coerce.
20
+ #
21
+ # @return [Integer, String, Symbol] the value or result of #inspect.
22
+ def error_key(value)
23
+ return value if ERROR_KEY_TYPES.include?(value.class)
24
+
25
+ value.inspect
26
+ end
27
+
9
28
  # Coerce a Boolean value to a Presence constraint.
10
29
  #
11
30
  # @param present [true, false, Stannum::Constraints::Base, nil] The
@@ -10,7 +10,7 @@ module Stannum
10
10
  # Major version.
11
11
  MAJOR = 0
12
12
  # Minor version.
13
- MINOR = 1
13
+ MINOR = 3
14
14
  # Patch version.
15
15
  PATCH = 0
16
16
  # Prerelease version.
data/lib/stannum.rb CHANGED
@@ -8,9 +8,12 @@ module Stannum
8
8
  autoload :Constraints, 'stannum/constraints'
9
9
  autoload :Contract, 'stannum/contract'
10
10
  autoload :Contracts, 'stannum/contracts'
11
+ autoload :Entities, 'stannum/entities'
12
+ autoload :Entity, 'stannum/entity'
11
13
  autoload :Errors, 'stannum/errors'
12
14
  autoload :Messages, 'stannum/messages'
13
15
  autoload :ParameterValidation, 'stannum/parameter_validation'
16
+ autoload :Schema, 'stannum/schema'
14
17
  autoload :Struct, 'stannum/struct'
15
18
 
16
19
  # @return [String] the absolute path to the gem directory.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stannum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob "Merlin" Smith
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-08 00:00:00.000000000 Z
11
+ date: 2023-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sleeping_king_studios-tools
@@ -16,20 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.0'
20
- - - ">="
21
- - !ruby/object:Gem::Version
22
- version: 1.0.2
19
+ version: 1.1.0
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
24
  - - "~>"
28
25
  - !ruby/object:Gem::Version
29
- version: '1.0'
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: 1.0.2
26
+ version: 1.1.0
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: rspec
35
29
  requirement: !ruby/object:Gem::Requirement
@@ -64,28 +58,28 @@ dependencies:
64
58
  requirements:
65
59
  - - "~>"
66
60
  - !ruby/object:Gem::Version
67
- version: '1.18'
61
+ version: '1.48'
68
62
  type: :development
69
63
  prerelease: false
70
64
  version_requirements: !ruby/object:Gem::Requirement
71
65
  requirements:
72
66
  - - "~>"
73
67
  - !ruby/object:Gem::Version
74
- version: '1.18'
68
+ version: '1.48'
75
69
  - !ruby/object:Gem::Dependency
76
70
  name: rubocop-rspec
77
71
  requirement: !ruby/object:Gem::Requirement
78
72
  requirements:
79
73
  - - "~>"
80
74
  - !ruby/object:Gem::Version
81
- version: '2.4'
75
+ version: '2.19'
82
76
  type: :development
83
77
  prerelease: false
84
78
  version_requirements: !ruby/object:Gem::Requirement
85
79
  requirements:
86
80
  - - "~>"
87
81
  - !ruby/object:Gem::Version
88
- version: '2.4'
82
+ version: '2.19'
89
83
  - !ruby/object:Gem::Dependency
90
84
  name: simplecov
91
85
  requirement: !ruby/object:Gem::Requirement
@@ -130,10 +124,19 @@ files:
130
124
  - lib/stannum/constraints/equality.rb
131
125
  - lib/stannum/constraints/hashes.rb
132
126
  - lib/stannum/constraints/hashes/extra_keys.rb
127
+ - lib/stannum/constraints/hashes/indifferent_extra_keys.rb
133
128
  - lib/stannum/constraints/hashes/indifferent_key.rb
134
129
  - lib/stannum/constraints/identity.rb
135
130
  - lib/stannum/constraints/nothing.rb
131
+ - lib/stannum/constraints/parameters.rb
132
+ - lib/stannum/constraints/parameters/extra_arguments.rb
133
+ - lib/stannum/constraints/parameters/extra_keywords.rb
136
134
  - lib/stannum/constraints/presence.rb
135
+ - lib/stannum/constraints/properties.rb
136
+ - lib/stannum/constraints/properties/base.rb
137
+ - lib/stannum/constraints/properties/do_not_match_property.rb
138
+ - lib/stannum/constraints/properties/match_property.rb
139
+ - lib/stannum/constraints/properties/matching.rb
137
140
  - lib/stannum/constraints/signature.rb
138
141
  - lib/stannum/constraints/signatures.rb
139
142
  - lib/stannum/constraints/signatures/map.rb
@@ -173,8 +176,14 @@ files:
173
176
  - lib/stannum/contracts/parameters/signature_contract.rb
174
177
  - lib/stannum/contracts/parameters_contract.rb
175
178
  - lib/stannum/contracts/tuple_contract.rb
179
+ - lib/stannum/entities.rb
180
+ - lib/stannum/entities/attributes.rb
181
+ - lib/stannum/entities/constraints.rb
182
+ - lib/stannum/entities/properties.rb
183
+ - lib/stannum/entity.rb
176
184
  - lib/stannum/errors.rb
177
185
  - lib/stannum/messages.rb
186
+ - lib/stannum/messages/default_loader.rb
178
187
  - lib/stannum/messages/default_strategy.rb
179
188
  - lib/stannum/parameter_validation.rb
180
189
  - lib/stannum/rspec.rb
@@ -194,7 +203,8 @@ licenses:
194
203
  metadata:
195
204
  bug_tracker_uri: https://github.com/sleepingkingstudios/stannum/issues
196
205
  source_code_uri: https://github.com/sleepingkingstudios/stannum
197
- post_install_message:
206
+ rubygems_mfa_required: 'true'
207
+ post_install_message:
198
208
  rdoc_options: []
199
209
  require_paths:
200
210
  - lib
@@ -202,15 +212,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
202
212
  requirements:
203
213
  - - ">="
204
214
  - !ruby/object:Gem::Version
205
- version: '2.6'
215
+ version: '2.7'
206
216
  required_rubygems_version: !ruby/object:Gem::Requirement
207
217
  requirements:
208
218
  - - ">="
209
219
  - !ruby/object:Gem::Version
210
220
  version: '0'
211
221
  requirements: []
212
- rubygems_version: 3.1.4
213
- signing_key:
222
+ rubygems_version: 3.4.1
223
+ signing_key:
214
224
  specification_version: 4
215
225
  summary: A library for specifying and validating data structures.
216
226
  test_files: []