params_ready 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 (83) hide show
  1. checksums.yaml +7 -0
  2. data/lib/arel/cte_name.rb +20 -0
  3. data/lib/params_ready.rb +36 -0
  4. data/lib/params_ready/builder.rb +140 -0
  5. data/lib/params_ready/error.rb +31 -0
  6. data/lib/params_ready/extensions/class_reader_writer.rb +33 -0
  7. data/lib/params_ready/extensions/collection.rb +43 -0
  8. data/lib/params_ready/extensions/delegation.rb +25 -0
  9. data/lib/params_ready/extensions/finalizer.rb +26 -0
  10. data/lib/params_ready/extensions/freezer.rb +49 -0
  11. data/lib/params_ready/extensions/hash.rb +46 -0
  12. data/lib/params_ready/extensions/late_init.rb +38 -0
  13. data/lib/params_ready/extensions/registry.rb +44 -0
  14. data/lib/params_ready/extensions/undefined.rb +15 -0
  15. data/lib/params_ready/format.rb +130 -0
  16. data/lib/params_ready/helpers/arel_builder.rb +68 -0
  17. data/lib/params_ready/helpers/conditional_block.rb +31 -0
  18. data/lib/params_ready/helpers/find_in_hash.rb +22 -0
  19. data/lib/params_ready/helpers/key_map.rb +176 -0
  20. data/lib/params_ready/helpers/memo.rb +42 -0
  21. data/lib/params_ready/helpers/options.rb +39 -0
  22. data/lib/params_ready/helpers/parameter_definer_class_methods.rb +39 -0
  23. data/lib/params_ready/helpers/parameter_storage_class_methods.rb +36 -0
  24. data/lib/params_ready/helpers/parameter_user_class_methods.rb +31 -0
  25. data/lib/params_ready/helpers/relation_builder_wrapper.rb +35 -0
  26. data/lib/params_ready/helpers/rule.rb +57 -0
  27. data/lib/params_ready/helpers/storage.rb +30 -0
  28. data/lib/params_ready/helpers/usage_rule.rb +18 -0
  29. data/lib/params_ready/input_context.rb +31 -0
  30. data/lib/params_ready/intent.rb +70 -0
  31. data/lib/params_ready/marshaller/array_marshallers.rb +132 -0
  32. data/lib/params_ready/marshaller/builder_module.rb +9 -0
  33. data/lib/params_ready/marshaller/collection.rb +165 -0
  34. data/lib/params_ready/marshaller/definition_module.rb +63 -0
  35. data/lib/params_ready/marshaller/hash_marshallers.rb +100 -0
  36. data/lib/params_ready/marshaller/hash_set_marshallers.rb +96 -0
  37. data/lib/params_ready/marshaller/parameter_module.rb +11 -0
  38. data/lib/params_ready/marshaller/polymorph_marshallers.rb +67 -0
  39. data/lib/params_ready/marshaller/tuple_marshallers.rb +103 -0
  40. data/lib/params_ready/ordering/column.rb +60 -0
  41. data/lib/params_ready/ordering/ordering.rb +276 -0
  42. data/lib/params_ready/output_parameters.rb +127 -0
  43. data/lib/params_ready/pagination/abstract_pagination.rb +18 -0
  44. data/lib/params_ready/pagination/cursor.rb +171 -0
  45. data/lib/params_ready/pagination/direction.rb +148 -0
  46. data/lib/params_ready/pagination/keyset_pagination.rb +254 -0
  47. data/lib/params_ready/pagination/keysets.rb +70 -0
  48. data/lib/params_ready/pagination/nulls.rb +31 -0
  49. data/lib/params_ready/pagination/offset_pagination.rb +130 -0
  50. data/lib/params_ready/pagination/tendency.rb +28 -0
  51. data/lib/params_ready/parameter/abstract_hash_parameter.rb +204 -0
  52. data/lib/params_ready/parameter/array_parameter.rb +197 -0
  53. data/lib/params_ready/parameter/definition.rb +264 -0
  54. data/lib/params_ready/parameter/hash_parameter.rb +63 -0
  55. data/lib/params_ready/parameter/hash_set_parameter.rb +101 -0
  56. data/lib/params_ready/parameter/parameter.rb +456 -0
  57. data/lib/params_ready/parameter/polymorph_parameter.rb +172 -0
  58. data/lib/params_ready/parameter/state.rb +132 -0
  59. data/lib/params_ready/parameter/tuple_parameter.rb +152 -0
  60. data/lib/params_ready/parameter/value_parameter.rb +182 -0
  61. data/lib/params_ready/parameter_definer.rb +14 -0
  62. data/lib/params_ready/parameter_user.rb +43 -0
  63. data/lib/params_ready/query/array_grouping.rb +68 -0
  64. data/lib/params_ready/query/custom_predicate.rb +102 -0
  65. data/lib/params_ready/query/exists_predicate.rb +103 -0
  66. data/lib/params_ready/query/fixed_operator_predicate.rb +77 -0
  67. data/lib/params_ready/query/grouping.rb +177 -0
  68. data/lib/params_ready/query/join_clause.rb +87 -0
  69. data/lib/params_ready/query/nullness_predicate.rb +71 -0
  70. data/lib/params_ready/query/polymorph_predicate.rb +77 -0
  71. data/lib/params_ready/query/predicate.rb +203 -0
  72. data/lib/params_ready/query/predicate_operator.rb +132 -0
  73. data/lib/params_ready/query/relation.rb +337 -0
  74. data/lib/params_ready/query/structured_grouping.rb +58 -0
  75. data/lib/params_ready/query/variable_operator_predicate.rb +125 -0
  76. data/lib/params_ready/query_context.rb +21 -0
  77. data/lib/params_ready/restriction.rb +252 -0
  78. data/lib/params_ready/result.rb +109 -0
  79. data/lib/params_ready/value/coder.rb +181 -0
  80. data/lib/params_ready/value/constraint.rb +198 -0
  81. data/lib/params_ready/value/custom.rb +56 -0
  82. data/lib/params_ready/value/validator.rb +68 -0
  83. metadata +181 -0
@@ -0,0 +1,63 @@
1
+ require_relative 'parameter'
2
+ require_relative '../builder'
3
+ require_relative 'abstract_hash_parameter'
4
+ require_relative '../marshaller/parameter_module'
5
+
6
+
7
+ module ParamsReady
8
+ module Parameter
9
+ class HashParameter < AbstractHashParameter
10
+ include Marshaller::ParameterModule
11
+ end
12
+
13
+ class HashParameterBuilder < Builder
14
+ include AbstractHashParameterBuilder::HashLike
15
+ include Marshaller::BuilderModule
16
+ register :hash
17
+
18
+ def self.instance(name, altn: nil)
19
+ new HashParameterDefinition.new(name, altn: altn)
20
+ end
21
+
22
+ def map(hash)
23
+ @definition.add_map(hash, **{})
24
+ end
25
+ end
26
+
27
+ class HashParameterDefinition < AbstractHashParameterDefinition
28
+ include Marshaller::DefinitionModule[Marshaller::HashMarshallers.collection]
29
+
30
+ name_for_formatter :hash
31
+ parameter_class HashParameter
32
+ freeze_variables :key_map
33
+
34
+ def ensure_canonical(hash)
35
+ context = Format.instance(:backend)
36
+
37
+ value, _validator = try_canonicalize hash, context, nil, freeze: true
38
+ return value if value.length == hash.length
39
+
40
+ extra_keys = hash.keys.select do |key|
41
+ !value.key?(key)
42
+ end.map do |key|
43
+ "'#{key.to_s}'"
44
+ end.join(", ")
45
+ raise ParamsReadyError, "extra keys found -- #{extra_keys}" if extra_keys.length > 0
46
+ value
47
+ end
48
+
49
+
50
+ def add_map(hash)
51
+ @key_map ||= Helpers::KeyMap.new
52
+ hash.each do |key, value|
53
+ @key_map.map(key, to: value)
54
+ end
55
+ end
56
+
57
+ def remap?(context)
58
+ return false if key_map.nil?
59
+ context.remap?
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,101 @@
1
+ require 'set'
2
+ require_relative 'hash_parameter'
3
+ require_relative 'value_parameter'
4
+ require_relative '../intent'
5
+ require_relative '../marshaller/hash_set_marshallers'
6
+ require_relative '../marshaller/parameter_module'
7
+
8
+ module ParamsReady
9
+ module Parameter
10
+ class HashSetParameter < AbstractHashParameter
11
+ include Marshaller::ParameterModule
12
+
13
+ def self.intent_for_set(intent)
14
+ Intent.new(
15
+ intent.format.update(
16
+ omit: [],
17
+ remap: false
18
+ ),
19
+ intent.restriction
20
+ )
21
+ end
22
+
23
+ def member?(key)
24
+ raise ParamsReadyError, "Key not defined: '#{key}'" unless definition.has_child? key
25
+
26
+ if is_definite?
27
+ bare_value[key].unwrap == true
28
+ else
29
+ false
30
+ end
31
+ end
32
+ end
33
+
34
+ class HashSetParameterBuilder < Builder
35
+ include Marshaller::BuilderModule
36
+
37
+ register :hash_set
38
+
39
+ def self.instance(name, altn: nil, type: :boolean)
40
+ new HashSetParameterDefinition.new(name, altn: altn, type: type)
41
+ end
42
+
43
+ def add(input, *args, val: nil, **opts, &block)
44
+ type = @definition.type
45
+ definition = self.class.resolve(type, input, *args, **opts, &block)
46
+ @definition.add_child definition, value: val
47
+ end
48
+
49
+
50
+ def self.resolve(type, input, *args, **opts, &block)
51
+ if input.is_a? AbstractDefinition
52
+ input
53
+ else
54
+ define_registered_parameter(type, input, *args, **opts, &block)
55
+ end
56
+ end
57
+ end
58
+
59
+ class HashSetParameterDefinition < AbstractHashParameterDefinition
60
+ attr_reader :type, :values
61
+ freeze_variable :values
62
+ name_for_formatter :hash_set
63
+ parameter_class HashSetParameter
64
+ include Marshaller::DefinitionModule[Marshaller::HashSetMarshallers.collection]
65
+
66
+ def initialize(*args, type: :boolean, **opts)
67
+ @type = type
68
+ @values = {}
69
+ super *args, **opts
70
+ end
71
+
72
+ def ensure_canonical(set)
73
+ raise ParamsReadyError, "Unexpected default type: #{set.class.name}" unless set.is_a?(Set)
74
+
75
+ context = Format.instance(:backend)
76
+ value, _validator = try_canonicalize set, context, nil, freeze: true
77
+ return value if value.length == set.length
78
+
79
+ extra_keys = set.reject do |key|
80
+ value.key?(key) || value.key?(key.to_s)
81
+ end.map do |key|
82
+ "'#{key.to_s}'"
83
+ end.join(", ")
84
+ raise ParamsReadyError, "extra elements found -- #{extra_keys}" if extra_keys.length > 0
85
+
86
+ value
87
+ end
88
+
89
+ def add_child(child, value:)
90
+ value = value.nil? ? child.name : value
91
+
92
+ if @values.key(value).nil?
93
+ @values[child.name] = value
94
+ else
95
+ raise ParamsReadyError, "Value '#{value}' already taken by '#{@values.key(value)}'"
96
+ end
97
+ super child
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,456 @@
1
+ require 'forwardable'
2
+ require_relative '../extensions/freezer'
3
+ require_relative '../error'
4
+ require_relative '../helpers/memo'
5
+ require_relative '../helpers/find_in_hash'
6
+
7
+ module ParamsReady
8
+ module Parameter
9
+ module FromHash
10
+ def set_from_hash(hash, context: nil, validator: Result.new(name))
11
+ _, input = find_in_hash hash, context
12
+ set_from_input(input, context, validator)
13
+ end
14
+ end
15
+
16
+ module ComplexParameter
17
+ def update_child(value, path)
18
+ child, child_name, child_path = child_for_update(path)
19
+ changed, updated = child.update_if_applicable(value, child_path)
20
+
21
+ if frozen? && !changed
22
+ [false, self]
23
+ else
24
+ clone = updated_clone(child_name, updated)
25
+ [true, clone]
26
+ end
27
+ end
28
+ end
29
+
30
+ module DelegatingParameter
31
+ include ComplexParameter
32
+ include FromHash
33
+
34
+ def self.included(recipient)
35
+ recipient.freeze_variable :data
36
+ end
37
+
38
+ def method_missing(name, *args)
39
+ if @data.respond_to?(name)
40
+ @data.send name, *args
41
+ else
42
+ super
43
+ end
44
+ end
45
+
46
+ def respond_to_missing?(name, include_private = false)
47
+ if @data.respond_to?(name, include_private)
48
+ true
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ def set_value(input, context = Format.instance(:backend), validator = nil)
55
+ if self.match?(input)
56
+ super input.unwrap, context, validator
57
+ else
58
+ super
59
+ end
60
+ end
61
+
62
+ def hash
63
+ [definition, data].hash
64
+ end
65
+
66
+ def ==(other)
67
+ return false unless self.match?(other)
68
+ data == other.data
69
+ end
70
+
71
+ alias_method :eql?, :==
72
+
73
+ protected
74
+
75
+ def child_for_update(path)
76
+ [@data, nil, *path]
77
+ end
78
+
79
+ def updated_clone(_child_name, updated)
80
+ clone = definition.create
81
+ clone.instance_variable_set :@data, updated
82
+ clone.freeze if frozen?
83
+ clone
84
+ end
85
+
86
+ def set_from_input(input, context, validator)
87
+ if self.match?(input)
88
+ super input.unwrap, context, validator
89
+ else
90
+ super
91
+ end
92
+ end
93
+
94
+ def populate_other(other)
95
+ data.populate_other(other.data)
96
+ end
97
+ end
98
+
99
+ class AbstractParameter
100
+ attr_reader :definition
101
+ extend Forwardable
102
+ extend Extensions::Freezer
103
+ include Extensions::Freezer::InstanceMethods
104
+ include FromHash
105
+
106
+ def_delegators :@definition, :name, :altn, :name_for_formatter
107
+
108
+ def self.intent_for_children(method, &block)
109
+ case method
110
+ when :restriction
111
+ raise ParamsReadyError, "Block unexpected for '#{method}' method" unless block.nil?
112
+ define_method :intent_for_children do |intent|
113
+ intent.for_children(self)
114
+ end
115
+ when :delegate
116
+ define_method :intent_for_children do |intent|
117
+ delegate_name, *others = self.instance_eval(&block)
118
+ intent.delegate(self, delegate_name, *others)
119
+ end
120
+ when :pass
121
+ raise ParamsReadyError, "Block unexpected for '#{method}' method" unless block.nil?
122
+ define_method :intent_for_children do |intent|
123
+ intent
124
+ end
125
+ else
126
+ raise ParamsReadyError, "Unimplemented permission method: '#{method}'"
127
+ end
128
+ end
129
+
130
+ intent_for_children :pass
131
+
132
+ def initialize(definition, **options)
133
+ raise ParamsReadyError, "Unexpected options: #{options}" unless options.empty?
134
+ @definition = definition
135
+ end
136
+
137
+ def update_in(value, path)
138
+ _, updated = update_if_applicable(value, path)
139
+ updated
140
+ end
141
+
142
+ def update_if_applicable(value, path)
143
+ if path.empty?
144
+ update_self(value)
145
+ elsif respond_to? :update_child
146
+ update_child(value, path)
147
+ else
148
+ raise ParamsReadyError, "Expected path to be terminated in '#{name}'"
149
+ end
150
+ end
151
+
152
+ def populate(context, validator)
153
+ return if definition.populator.nil?
154
+
155
+ definition.populator.call(context, self)
156
+ validator
157
+ rescue => error
158
+ populator_error = PopulatorError.new(error)
159
+ if validator.nil?
160
+ raise populator_error
161
+ else
162
+ validator.error! populator_error
163
+ end
164
+ validator
165
+ end
166
+
167
+ def match?(other)
168
+ return false unless other.instance_of?(self.class)
169
+ definition == other.definition
170
+ end
171
+
172
+ def ==(other)
173
+ return false unless self.match?(other)
174
+
175
+ bare_value == other.bare_value
176
+ rescue
177
+ false
178
+ end
179
+
180
+ def to_hash(format = Format.instance(:backend), restriction: nil, data: nil)
181
+ restriction ||= Restriction.blanket_permission
182
+ intent = Intent.new(format, restriction, data: data)
183
+ to_hash_if_eligible(intent) || {}
184
+ end
185
+
186
+ def inspect
187
+ "#{self.class.name.split("::").last} #{self.name}: { #{inspect_content} }"
188
+ end
189
+
190
+ def dup
191
+ clone = definition.create
192
+ populate_other clone
193
+ clone
194
+ end
195
+
196
+ protected
197
+
198
+ def update_self(value)
199
+ clone = definition.create
200
+ clone.set_value value
201
+ clone.freeze if frozen?
202
+ [true, clone]
203
+ end
204
+ end
205
+
206
+ class Parameter < AbstractParameter
207
+ def_delegators :@definition,
208
+ :default, :optional?, :default_defined?, :constraints, :no_output?, :local?
209
+
210
+ def initialize(definition)
211
+ @value = Extensions::Undefined
212
+ super definition
213
+ end
214
+
215
+ def set_value(input, context = Format.instance(:backend), validator = nil)
216
+ if Extensions::Undefined.value_indefinite?(input)
217
+ handle_indefinite_input(input, validator)
218
+ elsif self.match? input
219
+ @value = input.bare_value
220
+ else
221
+ begin
222
+ value, validator = definition.try_canonicalize(input, context, validator)
223
+ if validator.nil? || validator.ok?
224
+ if Extensions::Undefined.value_indefinite?(value)
225
+ handle_indefinite_input(value, validator)
226
+ else
227
+ @value = value
228
+ end
229
+ end
230
+ rescue StandardError => e
231
+ if validator.nil?
232
+ raise e
233
+ else
234
+ validator.error! e
235
+ end
236
+ end
237
+ end
238
+ validator
239
+ end
240
+
241
+ def definite_default?
242
+ default_defined? && !default.nil?
243
+ end
244
+
245
+ def nil_default?
246
+ default_defined? && default.nil?
247
+ end
248
+
249
+ def is_definite?
250
+ return true if @value != Extensions::Undefined && !@value.nil?
251
+
252
+ definite_default?
253
+ end
254
+
255
+ def is_default?
256
+ return false unless default_defined?
257
+
258
+ @value == Extensions::Undefined || @value == default
259
+ end
260
+
261
+ def is_nil?
262
+ return false if is_definite?
263
+ return true if optional?
264
+ return true if nil_default?
265
+
266
+ false
267
+ end
268
+
269
+ def is_undefined?
270
+ @value == Extensions::Undefined && !default_defined?
271
+ end
272
+
273
+ def eligible_for_output?(intent)
274
+ intent.preserve?(self)
275
+ end
276
+
277
+ def hash_key(format)
278
+ format.hash_key(self)
279
+ end
280
+
281
+ def set_from_hash(hash, context: nil, validator: Result.new(name))
282
+ if local?(context)
283
+ populate(context, validator)
284
+ else
285
+ super
286
+ end
287
+ end
288
+
289
+ def set_from_input(input, context, validator)
290
+ preprocessed = definition.preprocess(input, context, validator)
291
+ set_value preprocessed, context, validator
292
+ definition.postprocess(self, context, validator)
293
+ validator
294
+ end
295
+
296
+ def to_hash_if_eligible(intent = Intent.instance(:backend))
297
+ return nil unless eligible_for_output? intent
298
+
299
+ formatted = format_self_permitted(intent)
300
+ wrap_output(formatted, intent)
301
+ end
302
+
303
+ def format_self_permitted(intent)
304
+ intent = intent_for_children(intent)
305
+ format(intent)
306
+ end
307
+
308
+ def format(intent)
309
+ value = memo(intent)
310
+ return value if value != Extensions::Undefined
311
+
312
+ value = marshal(intent)
313
+ memo!(value, intent)
314
+ value
315
+ end
316
+
317
+ def memo(intent)
318
+ return Extensions::Undefined if @memo.nil?
319
+
320
+ @memo.cached_value(intent)
321
+ end
322
+
323
+ def memo!(value, intent)
324
+ return if @memo.nil? || !frozen?
325
+
326
+ @memo.cache_value(value, intent)
327
+ end
328
+
329
+ def wrap_output(output, intent)
330
+ name_or_path = hash_key(intent)
331
+ if name_or_path.is_a? Array
332
+ *path, name = name_or_path
333
+ result = {}
334
+ Helpers::KeyMap::Mapping::Path.store(name, output, result, path)
335
+ result
336
+ else
337
+ { name_or_path => output }
338
+ end
339
+ end
340
+
341
+ def unwrap
342
+ format(Intent.instance(:backend))
343
+ end
344
+
345
+ def unwrap_or(default)
346
+ if is_definite?
347
+ unwrap
348
+ else
349
+ default
350
+ end
351
+ rescue StandardError => _
352
+ default
353
+ end
354
+
355
+ def find_in_hash(hash, context)
356
+ Helpers::FindInHash.find_in_hash hash, hash_key(context)
357
+ end
358
+
359
+ def populate_other(other)
360
+ raise ParamsReadyError, "Not a matching param: #{other.class.name}" unless match? other
361
+ return other unless is_definite?
362
+
363
+ value = bare_value
364
+ other.populate_with(value)
365
+ other
366
+ end
367
+
368
+ def inspect_content
369
+ @value.inspect
370
+ end
371
+
372
+ freeze_variable :value
373
+
374
+ def freeze
375
+ if definition.memoize? and !frozen?
376
+ @memo = Helpers::Memo.new(definition.memoize)
377
+ end
378
+ init_for_read true
379
+ super
380
+ end
381
+
382
+ def hash
383
+ [definition, @value].hash
384
+ end
385
+
386
+ alias_method :eql?, :==
387
+
388
+ protected
389
+
390
+ def handle_indefinite_input(input, validator)
391
+ value_missing validator
392
+ if optional?
393
+ # if value_missing doesn't crash,
394
+ # and the parameter is optional
395
+ # it's safe to set to nil or Extensions::Undefined
396
+ @value = input
397
+ elsif default_defined?
398
+ # Clear possible previous state,
399
+ # will be set to default on read
400
+ @value = Extensions::Undefined
401
+ else
402
+ raise ParamsReadyError, "Unexpected state in #handle_indefinite_input" if validator.ok?
403
+ end
404
+ end
405
+
406
+ def value_missing(validator = nil)
407
+ if !nil_allowed?
408
+ e = ValueMissingError.new self.name
409
+ if validator
410
+ validator.error!(e)
411
+ else
412
+ raise e
413
+ end
414
+ end
415
+ validator
416
+ end
417
+
418
+ def nil_allowed?
419
+ optional? || default_defined?
420
+ end
421
+
422
+ def bare_value
423
+ init_for_read
424
+ return @value if is_definite?
425
+
426
+ value_missing
427
+ nil
428
+ end
429
+
430
+ def init_for_read(to_be_frozen = false)
431
+ return unless @value == Extensions::Undefined
432
+ return unless default_defined?
433
+
434
+ @value = if to_be_frozen
435
+ definition.default
436
+ else
437
+ definition.duplicate_default
438
+ end
439
+ end
440
+
441
+ def init_for_write
442
+ return if is_definite?
443
+
444
+ if default_defined? && !default.nil?
445
+ @value = definition.duplicate_default
446
+ else
447
+ init_value
448
+ end
449
+ end
450
+
451
+ def init_value
452
+ # NOOP
453
+ end
454
+ end
455
+ end
456
+ end