params_ready 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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