dry-struct 0.6.0 → 1.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +242 -70
- data/LICENSE +17 -17
- data/README.md +17 -13
- data/dry-struct.gemspec +26 -27
- data/lib/dry/struct/class_interface.rb +195 -111
- data/lib/dry/struct/compiler.rb +22 -0
- data/lib/dry/struct/constructor.rb +4 -24
- data/lib/dry/struct/errors.rb +13 -3
- data/lib/dry/struct/extensions/pretty_print.rb +7 -5
- data/lib/dry/struct/extensions.rb +3 -1
- data/lib/dry/struct/hashify.rb +6 -6
- data/lib/dry/struct/printer.rb +23 -0
- data/lib/dry/struct/struct_builder.rb +48 -25
- data/lib/dry/struct/sum.rb +16 -2
- data/lib/dry/struct/value.rb +13 -4
- data/lib/dry/struct/version.rb +3 -1
- data/lib/dry/struct.rb +62 -37
- data/lib/dry-struct.rb +3 -1
- metadata +38 -62
- data/.gitignore +0 -12
- data/.rspec +0 -2
- data/.travis.yml +0 -28
- data/.yardopts +0 -4
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -28
- data/Rakefile +0 -10
- data/benchmarks/basic.rb +0 -57
- data/benchmarks/constrained.rb +0 -37
- data/bin/console +0 -12
- data/bin/setup +0 -7
- data/log/.gitkeep +0 -0
@@ -1,10 +1,13 @@
|
|
1
|
-
|
2
|
-
require 'dry/core/inflector'
|
3
|
-
require 'dry/core/descendants_tracker'
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
3
|
+
require "weakref"
|
4
|
+
require "dry/core/class_attributes"
|
5
|
+
require "dry/core/inflector"
|
6
|
+
require "dry/core/descendants_tracker"
|
7
|
+
|
8
|
+
require "dry/struct/errors"
|
9
|
+
require "dry/struct/constructor"
|
10
|
+
require "dry/struct/sum"
|
8
11
|
|
9
12
|
module Dry
|
10
13
|
class Struct
|
@@ -12,21 +15,15 @@ module Dry
|
|
12
15
|
module ClassInterface
|
13
16
|
include Core::ClassAttributes
|
14
17
|
|
15
|
-
include
|
16
|
-
include
|
18
|
+
include Types::Type
|
19
|
+
include Types::Builder
|
17
20
|
|
18
21
|
# @param [Class] klass
|
19
22
|
def inherited(klass)
|
20
23
|
super
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
klass.class_eval do
|
25
|
-
@meta = base.meta
|
26
|
-
|
27
|
-
unless equal?(Value)
|
28
|
-
extend Dry::Core::DescendantsTracker
|
29
|
-
end
|
25
|
+
unless klass.name.eql?("Dry::Struct::Value")
|
26
|
+
klass.extend(Core::DescendantsTracker)
|
30
27
|
end
|
31
28
|
end
|
32
29
|
|
@@ -34,7 +31,7 @@ module Dry
|
|
34
31
|
# and modifies {.schema} accordingly.
|
35
32
|
#
|
36
33
|
# @param [Symbol] name name of the defined attribute
|
37
|
-
# @param [Dry::Types::
|
34
|
+
# @param [Dry::Types::Type, nil] type or superclass of nested type
|
38
35
|
# @return [Dry::Struct]
|
39
36
|
# @yield
|
40
37
|
# If a block is given, it will be evaluated in the context of
|
@@ -53,10 +50,7 @@ module Dry
|
|
53
50
|
# end
|
54
51
|
#
|
55
52
|
# Language.schema
|
56
|
-
#
|
57
|
-
# :name=>#<Dry::Types::Definition primitive=String options={} meta={}>,
|
58
|
-
# :details=>Language::Details
|
59
|
-
# }
|
53
|
+
# # => #<Dry::Types[Constructor<Schema<keys={name: Constrained<Nominal<String> rule=[type?(String)]> details: Language::Details}> fn=Kernel.Hash>]>
|
60
54
|
#
|
61
55
|
# ruby = Language.new(name: 'Ruby', details: { type: 'OO' })
|
62
56
|
# ruby.name #=> 'Ruby'
|
@@ -66,19 +60,19 @@ module Dry
|
|
66
60
|
# @example with a nested array of structs
|
67
61
|
# class Language < Dry::Struct
|
68
62
|
# attribute :name, Types::String
|
69
|
-
#
|
70
|
-
#
|
63
|
+
# attribute :versions, Types::Array.of(Types::String)
|
64
|
+
# attribute :celebrities, Types::Array.of(Dry::Struct) do
|
71
65
|
# attribute :name, Types::String
|
72
66
|
# attribute :pseudonym, Types::String
|
73
67
|
# end
|
74
68
|
# end
|
75
69
|
#
|
76
70
|
# Language.schema
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
71
|
+
# => #<Dry::Types[Constructor<Schema<keys={
|
72
|
+
# name: Constrained<Nominal<String> rule=[type?(String)]>
|
73
|
+
# versions: Constrained<Array<Constrained<Nominal<String> rule=[type?(String)]>> rule=[type?(Array)]>
|
74
|
+
# celebrities: Constrained<Array<Language::Celebrity> rule=[type?(Array)]>
|
75
|
+
# }> fn=Kernel.Hash>]>
|
82
76
|
#
|
83
77
|
# ruby = Language.new(
|
84
78
|
# name: 'Ruby',
|
@@ -99,41 +93,76 @@ module Dry
|
|
99
93
|
# ruby.celebrities[0].pseudonym #=> 'Matz'
|
100
94
|
# ruby.celebrities[1].name #=> 'Aaron Patterson'
|
101
95
|
# ruby.celebrities[1].pseudonym #=> 'tenderlove'
|
102
|
-
def attribute(name, type =
|
96
|
+
def attribute(name, type = Undefined, &block)
|
103
97
|
attributes(name => build_type(name, type, &block))
|
104
98
|
end
|
105
99
|
|
100
|
+
# Add atributes from another struct
|
101
|
+
#
|
102
|
+
# @example
|
103
|
+
# class Address < Dry::Struct
|
104
|
+
# attribute :city, Types::String
|
105
|
+
# attribute :country, Types::String
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# class User < Dry::Struct
|
109
|
+
# attribute :name, Types::String
|
110
|
+
# attributes_from Address
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# User.new(name: 'Quispe', city: 'La Paz', country: 'Bolivia')
|
114
|
+
#
|
115
|
+
# @example with nested structs
|
116
|
+
# class User < Dry::Struct
|
117
|
+
# attribute :name, Types::String
|
118
|
+
# attribute :address do
|
119
|
+
# attributes_from Address
|
120
|
+
# end
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
# @param struct [Dry::Struct]
|
124
|
+
def attributes_from(struct)
|
125
|
+
extracted_schema = struct.schema.keys.map { |key|
|
126
|
+
if key.required?
|
127
|
+
[key.name, key.type]
|
128
|
+
else
|
129
|
+
[:"#{key.name}?", key.type]
|
130
|
+
end
|
131
|
+
}.to_h
|
132
|
+
attributes(extracted_schema)
|
133
|
+
end
|
134
|
+
|
106
135
|
# Adds an omittable (key is not required on initialization) attribute for this {Struct}
|
107
136
|
#
|
108
137
|
# @example
|
109
138
|
# class User < Dry::Struct
|
110
|
-
# attribute :name, Types::
|
111
|
-
# attribute? :email, Types::
|
139
|
+
# attribute :name, Types::String
|
140
|
+
# attribute? :email, Types::String
|
112
141
|
# end
|
113
142
|
#
|
114
|
-
# User.new(name: 'John') # => #<User name="John">
|
143
|
+
# User.new(name: 'John') # => #<User name="John" email=nil>
|
115
144
|
#
|
116
145
|
# @param [Symbol] name name of the defined attribute
|
117
|
-
# @param [Dry::Types::
|
146
|
+
# @param [Dry::Types::Type, nil] type or superclass of nested type
|
118
147
|
# @return [Dry::Struct]
|
119
148
|
#
|
120
149
|
def attribute?(*args, &block)
|
121
150
|
if args.size == 1 && block.nil?
|
122
|
-
|
123
|
-
|
124
|
-
|
151
|
+
Core::Deprecations.warn(
|
152
|
+
"Dry::Struct.attribute? is deprecated for checking attribute presence, "\
|
153
|
+
"use has_attribute? instead",
|
125
154
|
tag: :'dry-struct'
|
126
155
|
)
|
127
156
|
|
128
157
|
has_attribute?(args[0])
|
129
158
|
else
|
130
|
-
name,
|
159
|
+
name, * = args
|
131
160
|
|
132
|
-
attribute(name, build_type(
|
161
|
+
attribute(:"#{name}?", build_type(*args, &block))
|
133
162
|
end
|
134
163
|
end
|
135
164
|
|
136
|
-
# @param [Hash{Symbol => Dry::Types::
|
165
|
+
# @param [Hash{Symbol => Dry::Types::Type}] new_schema
|
137
166
|
# @return [Dry::Struct]
|
138
167
|
# @raise [RepeatedAttributeError] when trying to define attribute with the
|
139
168
|
# same name as previously defined one
|
@@ -147,27 +176,25 @@ module Dry
|
|
147
176
|
# end
|
148
177
|
#
|
149
178
|
# Book.schema
|
150
|
-
#
|
151
|
-
#
|
179
|
+
# # => #<Dry::Types[Constructor<Schema<keys={
|
180
|
+
# # title: Constrained<Nominal<String> rule=[type?(String)]>
|
181
|
+
# # author: Constrained<Nominal<String> rule=[type?(String)]>
|
182
|
+
# # }> fn=Kernel.Hash>]>
|
152
183
|
def attributes(new_schema)
|
153
|
-
|
184
|
+
keys = new_schema.keys.map { |k| k.to_s.chomp("?").to_sym }
|
185
|
+
check_schema_duplication(keys)
|
154
186
|
|
155
|
-
|
187
|
+
schema schema.schema(new_schema)
|
156
188
|
|
157
|
-
|
158
|
-
next if instance_methods.include?(key)
|
159
|
-
class_eval(<<-RUBY)
|
160
|
-
def #{ key }
|
161
|
-
@attributes[#{ key.inspect }]
|
162
|
-
end
|
163
|
-
RUBY
|
164
|
-
end
|
189
|
+
define_accessors(keys)
|
165
190
|
|
166
191
|
@attribute_names = nil
|
167
192
|
|
168
|
-
descendants.
|
169
|
-
|
170
|
-
|
193
|
+
direct_descendants = descendants.select { |d| d.superclass == self }
|
194
|
+
direct_descendants.each do |d|
|
195
|
+
inherited_attrs = new_schema.reject { |k, _| d.has_attribute?(k.to_s.chomp("?").to_sym) }
|
196
|
+
d.attributes(inherited_attrs)
|
197
|
+
end
|
171
198
|
|
172
199
|
self
|
173
200
|
end
|
@@ -180,13 +207,13 @@ module Dry
|
|
180
207
|
# class Book < Dry::Struct
|
181
208
|
# transform_types { |t| t.meta(struct: :Book) }
|
182
209
|
#
|
183
|
-
# attribute :title, Types::
|
210
|
+
# attribute :title, Types::String
|
184
211
|
# end
|
185
212
|
#
|
186
|
-
# Book.schema
|
213
|
+
# Book.schema.key(:title).meta # => { struct: :Book }
|
187
214
|
#
|
188
215
|
def transform_types(proc = nil, &block)
|
189
|
-
|
216
|
+
schema schema.with_type_transform(proc || block)
|
190
217
|
end
|
191
218
|
|
192
219
|
# Add an arbitrary transformation for input hash keys.
|
@@ -197,54 +224,80 @@ module Dry
|
|
197
224
|
# class Book < Dry::Struct
|
198
225
|
# transform_keys(&:to_sym)
|
199
226
|
#
|
200
|
-
# attribute :title, Types::
|
227
|
+
# attribute :title, Types::String
|
201
228
|
# end
|
202
229
|
#
|
203
230
|
# Book.new('title' => "The Old Man and the Sea")
|
204
231
|
# # => #<Book title="The Old Man and the Sea">
|
205
232
|
def transform_keys(proc = nil, &block)
|
206
|
-
|
233
|
+
schema schema.with_key_transform(proc || block)
|
207
234
|
end
|
208
235
|
|
209
|
-
# @param [Hash{Symbol => Dry::Types::
|
236
|
+
# @param [Hash{Symbol => Dry::Types::Type, Dry::Struct}] new_keys
|
210
237
|
# @raise [RepeatedAttributeError] when trying to define attribute with the
|
211
238
|
# same name as previously defined one
|
212
|
-
def check_schema_duplication(
|
213
|
-
|
239
|
+
def check_schema_duplication(new_keys)
|
240
|
+
overlapping_keys = new_keys & (attribute_names - superclass.attribute_names)
|
214
241
|
|
215
|
-
|
242
|
+
if overlapping_keys.any?
|
243
|
+
raise RepeatedAttributeError, overlapping_keys.first
|
244
|
+
end
|
216
245
|
end
|
217
246
|
private :check_schema_duplication
|
218
247
|
|
219
248
|
# @param [Hash{Symbol => Object},Dry::Struct] attributes
|
220
249
|
# @raise [Struct::Error] if the given attributes don't conform {#schema}
|
221
|
-
def new(attributes = default_attributes)
|
222
|
-
if attributes.
|
223
|
-
attributes
|
250
|
+
def new(attributes = default_attributes, safe = false, &block)
|
251
|
+
if attributes.is_a?(Struct)
|
252
|
+
if equal?(attributes.class)
|
253
|
+
attributes
|
254
|
+
else
|
255
|
+
# This implicit coercion is arguable but makes sense overall
|
256
|
+
# in cases there you pass child struct to the base struct constructor
|
257
|
+
# User.new(super_user)
|
258
|
+
#
|
259
|
+
# We may deprecate this behavior in future forcing people to be explicit
|
260
|
+
new(attributes.to_h, safe, &block)
|
261
|
+
end
|
262
|
+
elsif safe
|
263
|
+
load(schema.call_safe(attributes) { |output = attributes| return yield output })
|
224
264
|
else
|
225
|
-
|
265
|
+
load(schema.call_unsafe(attributes))
|
226
266
|
end
|
227
|
-
rescue Types::
|
228
|
-
raise
|
267
|
+
rescue Types::CoercionError => e
|
268
|
+
raise Error, "[#{self}.new] #{e}", e.backtrace
|
229
269
|
end
|
230
270
|
|
231
|
-
#
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
271
|
+
# @api private
|
272
|
+
def call_safe(input, &block)
|
273
|
+
if input.is_a?(self)
|
274
|
+
input
|
275
|
+
else
|
276
|
+
new(input, true, &block)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
# @api private
|
281
|
+
def call_unsafe(input)
|
282
|
+
if input.is_a?(self)
|
283
|
+
input
|
284
|
+
else
|
285
|
+
new(input)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# @api private
|
290
|
+
def load(attributes)
|
291
|
+
struct = allocate
|
292
|
+
struct.__send__(:initialize, attributes)
|
293
|
+
struct
|
239
294
|
end
|
240
|
-
alias_method :[], :call
|
241
295
|
|
242
296
|
# @param [#call,nil] constructor
|
243
|
-
# @param [Hash] _options
|
244
297
|
# @param [#call,nil] block
|
245
298
|
# @return [Dry::Struct::Constructor]
|
246
|
-
def constructor(constructor = nil,
|
247
|
-
|
299
|
+
def constructor(constructor = nil, **, &block)
|
300
|
+
Constructor[self, fn: constructor || block]
|
248
301
|
end
|
249
302
|
|
250
303
|
# @param [Hash{Symbol => Object},Dry::Struct] input
|
@@ -252,10 +305,10 @@ module Dry
|
|
252
305
|
# @yieldreturn [Dry::Types::ResultResult]
|
253
306
|
# @return [Dry::Types::Result]
|
254
307
|
def try(input)
|
255
|
-
|
256
|
-
rescue
|
257
|
-
|
258
|
-
block_given? ? yield(
|
308
|
+
success(self[input])
|
309
|
+
rescue Error => e
|
310
|
+
failure_result = failure(input, e.message)
|
311
|
+
block_given? ? yield(failure_result) : failure_result
|
259
312
|
end
|
260
313
|
|
261
314
|
# @param [Hash{Symbol => Object},Dry::Struct] input
|
@@ -263,7 +316,7 @@ module Dry
|
|
263
316
|
# @private
|
264
317
|
def try_struct(input)
|
265
318
|
if input.is_a?(self)
|
266
|
-
|
319
|
+
input
|
267
320
|
else
|
268
321
|
yield
|
269
322
|
end
|
@@ -278,7 +331,7 @@ module Dry
|
|
278
331
|
# @param [({Symbol => Object})] args
|
279
332
|
# @return [Dry::Types::Result::Failure]
|
280
333
|
def failure(*args)
|
281
|
-
result(
|
334
|
+
result(Types::Result::Failure, *args)
|
282
335
|
end
|
283
336
|
|
284
337
|
# @param [Class] klass
|
@@ -292,11 +345,12 @@ module Dry
|
|
292
345
|
false
|
293
346
|
end
|
294
347
|
|
295
|
-
# @param [Object, Dry::Struct]
|
348
|
+
# @param [Object, Dry::Struct] other
|
296
349
|
# @return [Boolean]
|
297
|
-
def
|
298
|
-
self
|
350
|
+
def ===(other)
|
351
|
+
other.is_a?(self)
|
299
352
|
end
|
353
|
+
alias_method :primitive?, :===
|
300
354
|
|
301
355
|
# @return [true]
|
302
356
|
def constrained?
|
@@ -313,6 +367,11 @@ module Dry
|
|
313
367
|
false
|
314
368
|
end
|
315
369
|
|
370
|
+
# @return [Proc]
|
371
|
+
def to_proc
|
372
|
+
@to_proc ||= proc { |input| call(input) }
|
373
|
+
end
|
374
|
+
|
316
375
|
# Checks if this {Struct} has the given attribute
|
317
376
|
#
|
318
377
|
# @param [Symbol] key Attribute name
|
@@ -321,25 +380,22 @@ module Dry
|
|
321
380
|
schema.key?(key)
|
322
381
|
end
|
323
382
|
|
324
|
-
# @return [Hash{Symbol => Dry::Types::Definition, Dry::Struct}]
|
325
|
-
def schema
|
326
|
-
input.member_types
|
327
|
-
end
|
328
|
-
|
329
383
|
# Gets the list of attribute names
|
330
384
|
#
|
331
385
|
# @return [Array<Symbol>]
|
332
386
|
def attribute_names
|
333
|
-
@attribute_names ||= schema.
|
387
|
+
@attribute_names ||= schema.map(&:name)
|
334
388
|
end
|
335
389
|
|
336
390
|
# @return [{Symbol => Object}]
|
337
391
|
def meta(meta = Undefined)
|
338
392
|
if meta.equal?(Undefined)
|
339
|
-
|
393
|
+
schema.meta
|
394
|
+
elsif meta.empty?
|
395
|
+
self
|
340
396
|
else
|
341
|
-
Class.new(self) do
|
342
|
-
|
397
|
+
::Class.new(self) do
|
398
|
+
schema schema.meta(meta) unless meta.empty?
|
343
399
|
end
|
344
400
|
end
|
345
401
|
end
|
@@ -348,13 +404,28 @@ module Dry
|
|
348
404
|
# @param [Dry::Types::Type] type
|
349
405
|
# @return [Dry::Types::Sum]
|
350
406
|
def |(type)
|
351
|
-
if type.is_a?(Class) && type <= Struct
|
352
|
-
|
407
|
+
if type.is_a?(::Class) && type <= Struct
|
408
|
+
Sum.new(self, type)
|
353
409
|
else
|
354
410
|
super
|
355
411
|
end
|
356
412
|
end
|
357
413
|
|
414
|
+
# Make the struct abstract. This class will be used as a default
|
415
|
+
# parent class for nested structs
|
416
|
+
def abstract
|
417
|
+
abstract_class self
|
418
|
+
end
|
419
|
+
|
420
|
+
# Dump to the AST
|
421
|
+
#
|
422
|
+
# @return [Array]
|
423
|
+
#
|
424
|
+
# @api public
|
425
|
+
def to_ast(meta: true)
|
426
|
+
[:struct, [::WeakRef.new(self), schema.to_ast(meta: meta)]]
|
427
|
+
end
|
428
|
+
|
358
429
|
# Stores an object for building nested struct classes
|
359
430
|
# @return [StructBuilder]
|
360
431
|
def struct_builder
|
@@ -367,32 +438,32 @@ module Dry
|
|
367
438
|
#
|
368
439
|
# @return [Hash{Symbol => Object}]
|
369
440
|
def default_attributes(default_schema = schema)
|
370
|
-
default_schema.each_with_object({}) do |
|
371
|
-
result[name] = default_attributes(
|
441
|
+
default_schema.each_with_object({}) do |key, result|
|
442
|
+
result[key.name] = default_attributes(key.schema) if struct?(key.type)
|
372
443
|
end
|
373
444
|
end
|
374
445
|
private :default_attributes
|
375
446
|
|
376
447
|
# Checks if the given type is a Dry::Struct
|
377
448
|
#
|
378
|
-
# @param [Dry::Types::
|
449
|
+
# @param [Dry::Types::Type] type
|
379
450
|
# @return [Boolean]
|
380
451
|
def struct?(type)
|
381
|
-
type.is_a?(Class) && type <= Struct
|
452
|
+
type.is_a?(::Class) && type <= Struct
|
382
453
|
end
|
383
454
|
private :struct?
|
384
455
|
|
385
456
|
# Constructs a type
|
386
457
|
#
|
387
458
|
# @return [Dry::Types::Type, Dry::Struct]
|
388
|
-
def build_type(name, type, &block)
|
459
|
+
def build_type(name, type = Undefined, &block)
|
389
460
|
type_object =
|
390
|
-
if type.is_a?(String)
|
391
|
-
|
392
|
-
elsif block.nil? &&
|
461
|
+
if type.is_a?(::String)
|
462
|
+
Types[type]
|
463
|
+
elsif block.nil? && Undefined.equal?(type)
|
393
464
|
raise(
|
394
|
-
ArgumentError,
|
395
|
-
|
465
|
+
::ArgumentError,
|
466
|
+
"you must supply a type or a block to `Dry::Struct.attribute`"
|
396
467
|
)
|
397
468
|
else
|
398
469
|
type
|
@@ -405,6 +476,19 @@ module Dry
|
|
405
476
|
end
|
406
477
|
end
|
407
478
|
private :build_type
|
479
|
+
|
480
|
+
def define_accessors(keys)
|
481
|
+
keys.each do |key|
|
482
|
+
next if instance_methods.include?(key)
|
483
|
+
|
484
|
+
class_eval(<<-RUBY)
|
485
|
+
def #{key}
|
486
|
+
@attributes[#{key.inspect}]
|
487
|
+
end
|
488
|
+
RUBY
|
489
|
+
end
|
490
|
+
end
|
491
|
+
private :define_accessors
|
408
492
|
end
|
409
493
|
end
|
410
494
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "weakref"
|
4
|
+
require "dry/types/compiler"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
class Struct
|
8
|
+
class Compiler < Types::Compiler
|
9
|
+
def visit_struct(node)
|
10
|
+
struct, _ = node
|
11
|
+
|
12
|
+
struct.__getobj__
|
13
|
+
rescue ::WeakRef::RefError
|
14
|
+
if struct.weakref_alive?
|
15
|
+
raise
|
16
|
+
else
|
17
|
+
raise RecycledStructError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,29 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
class Struct
|
3
|
-
class Constructor
|
4
|
-
|
5
|
-
include Dry::Types::Type
|
6
|
-
|
7
|
-
# @return [#call]
|
8
|
-
attr_reader :fn
|
9
|
-
|
10
|
-
# @return [#call]
|
11
|
-
attr_reader :type
|
12
|
-
|
13
|
-
# @param [Struct] type
|
14
|
-
# @param [Hash] options
|
15
|
-
# @param [#call, nil] block
|
16
|
-
def initialize(type, options = {}, &block)
|
17
|
-
@type = type
|
18
|
-
@fn = options.fetch(:fn, block)
|
19
|
-
end
|
20
|
-
|
21
|
-
# @param [Object] input
|
22
|
-
# @return [Object]
|
23
|
-
def call(input)
|
24
|
-
type[fn[input]]
|
25
|
-
end
|
26
|
-
alias_method :[], :call
|
5
|
+
class Constructor < Types::Constructor
|
6
|
+
alias_method :primitive, :type
|
27
7
|
end
|
28
8
|
end
|
29
9
|
end
|
data/lib/dry/struct/errors.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
class Struct
|
3
5
|
# Raised when given input doesn't conform schema and constructor type
|
4
6
|
Error = Class.new(TypeError)
|
5
7
|
|
6
8
|
# Raised when defining duplicate attributes
|
7
|
-
class RepeatedAttributeError < ArgumentError
|
9
|
+
class RepeatedAttributeError < ::ArgumentError
|
8
10
|
# @param [Symbol] key
|
9
11
|
# attribute name that is the same as previously defined one
|
10
12
|
def initialize(key)
|
@@ -13,9 +15,17 @@ module Dry
|
|
13
15
|
end
|
14
16
|
|
15
17
|
# Raised when a struct doesn't have an attribute
|
16
|
-
class MissingAttributeError < KeyError
|
18
|
+
class MissingAttributeError < ::KeyError
|
17
19
|
def initialize(key)
|
18
|
-
super("Missing attribute: #{
|
20
|
+
super("Missing attribute: #{key.inspect}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# When struct class stored in ast was garbage collected because no alive objects exists
|
25
|
+
# This shouldn't happen in a working application
|
26
|
+
class RecycledStructError < ::RuntimeError
|
27
|
+
def initialize
|
28
|
+
super("Reference to struct class was garbage collected")
|
19
29
|
end
|
20
30
|
end
|
21
31
|
end
|
@@ -1,16 +1,18 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pp"
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
class Struct
|
5
7
|
def pretty_print(pp)
|
6
8
|
klass = self.class
|
7
|
-
pp.group(1, "#<#{
|
8
|
-
pp.seplist(@attributes.keys, proc { pp.text
|
9
|
+
pp.group(1, "#<#{klass.name || klass.inspect}", ">") do
|
10
|
+
pp.seplist(@attributes.keys, proc { pp.text "," }) do |column_name|
|
9
11
|
column_value = @attributes[column_name]
|
10
|
-
pp.breakable
|
12
|
+
pp.breakable " "
|
11
13
|
pp.group(1) do
|
12
14
|
pp.text column_name
|
13
|
-
pp.text
|
15
|
+
pp.text "="
|
14
16
|
pp.pp column_value
|
15
17
|
end
|
16
18
|
end
|
data/lib/dry/struct/hashify.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
class Struct
|
3
5
|
# Helper for {Struct#to_hash} implementation
|
@@ -6,12 +8,10 @@ module Dry
|
|
6
8
|
# @param [#to_hash, #map, Object] value
|
7
9
|
# @return [Hash, Array]
|
8
10
|
def self.[](value)
|
9
|
-
if value.
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
value.to_hash.each_with_object({}) { |(k, v), h| h[k] = self[v] }
|
14
|
-
end
|
11
|
+
if value.is_a?(Struct)
|
12
|
+
value.to_h.transform_values { |current| self[current] }
|
13
|
+
elsif value.respond_to?(:to_hash)
|
14
|
+
value.to_hash.transform_values { |current| self[current] }
|
15
15
|
elsif value.respond_to?(:to_ary)
|
16
16
|
value.to_ary.map { |item| self[item] }
|
17
17
|
else
|