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.
- checksums.yaml +7 -0
- data/lib/arel/cte_name.rb +20 -0
- data/lib/params_ready.rb +36 -0
- data/lib/params_ready/builder.rb +140 -0
- data/lib/params_ready/error.rb +31 -0
- data/lib/params_ready/extensions/class_reader_writer.rb +33 -0
- data/lib/params_ready/extensions/collection.rb +43 -0
- data/lib/params_ready/extensions/delegation.rb +25 -0
- data/lib/params_ready/extensions/finalizer.rb +26 -0
- data/lib/params_ready/extensions/freezer.rb +49 -0
- data/lib/params_ready/extensions/hash.rb +46 -0
- data/lib/params_ready/extensions/late_init.rb +38 -0
- data/lib/params_ready/extensions/registry.rb +44 -0
- data/lib/params_ready/extensions/undefined.rb +15 -0
- data/lib/params_ready/format.rb +130 -0
- data/lib/params_ready/helpers/arel_builder.rb +68 -0
- data/lib/params_ready/helpers/conditional_block.rb +31 -0
- data/lib/params_ready/helpers/find_in_hash.rb +22 -0
- data/lib/params_ready/helpers/key_map.rb +176 -0
- data/lib/params_ready/helpers/memo.rb +42 -0
- data/lib/params_ready/helpers/options.rb +39 -0
- data/lib/params_ready/helpers/parameter_definer_class_methods.rb +39 -0
- data/lib/params_ready/helpers/parameter_storage_class_methods.rb +36 -0
- data/lib/params_ready/helpers/parameter_user_class_methods.rb +31 -0
- data/lib/params_ready/helpers/relation_builder_wrapper.rb +35 -0
- data/lib/params_ready/helpers/rule.rb +57 -0
- data/lib/params_ready/helpers/storage.rb +30 -0
- data/lib/params_ready/helpers/usage_rule.rb +18 -0
- data/lib/params_ready/input_context.rb +31 -0
- data/lib/params_ready/intent.rb +70 -0
- data/lib/params_ready/marshaller/array_marshallers.rb +132 -0
- data/lib/params_ready/marshaller/builder_module.rb +9 -0
- data/lib/params_ready/marshaller/collection.rb +165 -0
- data/lib/params_ready/marshaller/definition_module.rb +63 -0
- data/lib/params_ready/marshaller/hash_marshallers.rb +100 -0
- data/lib/params_ready/marshaller/hash_set_marshallers.rb +96 -0
- data/lib/params_ready/marshaller/parameter_module.rb +11 -0
- data/lib/params_ready/marshaller/polymorph_marshallers.rb +67 -0
- data/lib/params_ready/marshaller/tuple_marshallers.rb +103 -0
- data/lib/params_ready/ordering/column.rb +60 -0
- data/lib/params_ready/ordering/ordering.rb +276 -0
- data/lib/params_ready/output_parameters.rb +127 -0
- data/lib/params_ready/pagination/abstract_pagination.rb +18 -0
- data/lib/params_ready/pagination/cursor.rb +171 -0
- data/lib/params_ready/pagination/direction.rb +148 -0
- data/lib/params_ready/pagination/keyset_pagination.rb +254 -0
- data/lib/params_ready/pagination/keysets.rb +70 -0
- data/lib/params_ready/pagination/nulls.rb +31 -0
- data/lib/params_ready/pagination/offset_pagination.rb +130 -0
- data/lib/params_ready/pagination/tendency.rb +28 -0
- data/lib/params_ready/parameter/abstract_hash_parameter.rb +204 -0
- data/lib/params_ready/parameter/array_parameter.rb +197 -0
- data/lib/params_ready/parameter/definition.rb +264 -0
- data/lib/params_ready/parameter/hash_parameter.rb +63 -0
- data/lib/params_ready/parameter/hash_set_parameter.rb +101 -0
- data/lib/params_ready/parameter/parameter.rb +456 -0
- data/lib/params_ready/parameter/polymorph_parameter.rb +172 -0
- data/lib/params_ready/parameter/state.rb +132 -0
- data/lib/params_ready/parameter/tuple_parameter.rb +152 -0
- data/lib/params_ready/parameter/value_parameter.rb +182 -0
- data/lib/params_ready/parameter_definer.rb +14 -0
- data/lib/params_ready/parameter_user.rb +43 -0
- data/lib/params_ready/query/array_grouping.rb +68 -0
- data/lib/params_ready/query/custom_predicate.rb +102 -0
- data/lib/params_ready/query/exists_predicate.rb +103 -0
- data/lib/params_ready/query/fixed_operator_predicate.rb +77 -0
- data/lib/params_ready/query/grouping.rb +177 -0
- data/lib/params_ready/query/join_clause.rb +87 -0
- data/lib/params_ready/query/nullness_predicate.rb +71 -0
- data/lib/params_ready/query/polymorph_predicate.rb +77 -0
- data/lib/params_ready/query/predicate.rb +203 -0
- data/lib/params_ready/query/predicate_operator.rb +132 -0
- data/lib/params_ready/query/relation.rb +337 -0
- data/lib/params_ready/query/structured_grouping.rb +58 -0
- data/lib/params_ready/query/variable_operator_predicate.rb +125 -0
- data/lib/params_ready/query_context.rb +21 -0
- data/lib/params_ready/restriction.rb +252 -0
- data/lib/params_ready/result.rb +109 -0
- data/lib/params_ready/value/coder.rb +181 -0
- data/lib/params_ready/value/constraint.rb +198 -0
- data/lib/params_ready/value/custom.rb +56 -0
- data/lib/params_ready/value/validator.rb +68 -0
- metadata +181 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
require_relative 'error'
|
2
|
+
|
3
|
+
module ParamsReady
|
4
|
+
class AbstractReporter
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name.to_s.freeze
|
9
|
+
end
|
10
|
+
|
11
|
+
def error!(err)
|
12
|
+
report_error(nil, err)
|
13
|
+
end
|
14
|
+
|
15
|
+
def full_path(path)
|
16
|
+
return [name] if path.nil? || path.empty?
|
17
|
+
[name, *path]
|
18
|
+
end
|
19
|
+
|
20
|
+
def for_child(name)
|
21
|
+
Reporter.new name, self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Result < AbstractReporter
|
26
|
+
class Error < ParamsReadyError; end
|
27
|
+
|
28
|
+
def initialize(name)
|
29
|
+
super
|
30
|
+
@errors = []
|
31
|
+
@children = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def full_scope(scope)
|
35
|
+
return name if scope.empty?
|
36
|
+
|
37
|
+
"#{scope}.#{name}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def errors(scope = '')
|
41
|
+
scope = full_scope(scope)
|
42
|
+
proper = @errors.empty? ? {} : { scope => @errors }
|
43
|
+
@children.values.reduce(proper) do |result, child|
|
44
|
+
result.merge(child.errors(scope))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def report_error(path, err)
|
49
|
+
raise ParamsReadyError, "Is not Error: #{err}" unless err.is_a? StandardError
|
50
|
+
|
51
|
+
name, *path = path
|
52
|
+
if name.nil?
|
53
|
+
@errors << err
|
54
|
+
else
|
55
|
+
@children[name] ||= Result.new(name)
|
56
|
+
@children[name].report_error(path, err)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def ok?
|
61
|
+
return false unless @errors.empty?
|
62
|
+
|
63
|
+
@children.values.all? do |child|
|
64
|
+
child.ok?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def child_ok?(path)
|
69
|
+
name, *path = path
|
70
|
+
return ok? if name.nil?
|
71
|
+
return true unless @children.key? name
|
72
|
+
|
73
|
+
@children[name].child_ok?(path)
|
74
|
+
end
|
75
|
+
|
76
|
+
def error
|
77
|
+
return nil if ok?
|
78
|
+
|
79
|
+
Result::Error.new(error_messages(' -- '))
|
80
|
+
end
|
81
|
+
|
82
|
+
def error_messages(separator = "\n")
|
83
|
+
errors.flat_map do |scope, errors|
|
84
|
+
["errors for #{scope}"] + errors.map { |err| err.message }
|
85
|
+
end.join(separator)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Reporter < AbstractReporter
|
90
|
+
attr_reader :name
|
91
|
+
|
92
|
+
def initialize(name, parent)
|
93
|
+
super name
|
94
|
+
@parent = parent
|
95
|
+
end
|
96
|
+
|
97
|
+
def ok?
|
98
|
+
child_ok?(nil)
|
99
|
+
end
|
100
|
+
|
101
|
+
def child_ok?(path)
|
102
|
+
@parent.child_ok?(full_path(path))
|
103
|
+
end
|
104
|
+
|
105
|
+
def report_error(path, err)
|
106
|
+
@parent.report_error(full_path(path), err)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'date'
|
2
|
+
require_relative '../error'
|
3
|
+
require_relative '../extensions/late_init'
|
4
|
+
require_relative '../extensions/finalizer'
|
5
|
+
require_relative '../extensions/class_reader_writer'
|
6
|
+
|
7
|
+
module ParamsReady
|
8
|
+
module Value
|
9
|
+
class Coder
|
10
|
+
extend Extensions::ClassReaderWriter
|
11
|
+
|
12
|
+
class_reader_writer :type_identifier
|
13
|
+
type_identifier :value
|
14
|
+
|
15
|
+
def self.value_class_name
|
16
|
+
last = self.name.split("::").last
|
17
|
+
last.remove('Coder')
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.try_coerce(input, context)
|
21
|
+
coerce input, context
|
22
|
+
rescue => _error
|
23
|
+
raise CoercionError.new(input, value_class_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.strict_default?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class GenericCoder
|
32
|
+
extend Extensions::LateInit
|
33
|
+
extend Extensions::Finalizer
|
34
|
+
include Extensions::Finalizer::InstanceMethods
|
35
|
+
|
36
|
+
def initialize(name)
|
37
|
+
@name = name
|
38
|
+
@coerce = nil
|
39
|
+
@format = nil
|
40
|
+
@type_identifier = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def strict_default?; true; end
|
44
|
+
|
45
|
+
late_init(:coerce, getter: false)
|
46
|
+
late_init(:format, getter: false)
|
47
|
+
late_init(:type_identifier, obligatory: false)
|
48
|
+
|
49
|
+
def value_class_name
|
50
|
+
@name
|
51
|
+
end
|
52
|
+
|
53
|
+
def try_coerce(input, context)
|
54
|
+
@coerce[input, context]
|
55
|
+
rescue => _error
|
56
|
+
raise CoercionError.new(input, @name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def format(value, format)
|
60
|
+
@format[value, format]
|
61
|
+
end
|
62
|
+
|
63
|
+
def finish
|
64
|
+
super
|
65
|
+
freeze
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class IntegerCoder < Coder
|
70
|
+
type_identifier :number
|
71
|
+
|
72
|
+
def self.coerce(input, _)
|
73
|
+
return nil if input.nil? || input == ''
|
74
|
+
Integer(input)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.format(value, format)
|
78
|
+
value.to_s
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class DecimalCoder < Coder
|
83
|
+
type_identifier :number
|
84
|
+
|
85
|
+
def self.coerce(input, _)
|
86
|
+
return nil if input.nil? || input == ''
|
87
|
+
BigDecimal(input)
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.format(value, format)
|
91
|
+
value.to_s('F')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class BooleanCoder < Coder
|
96
|
+
type_identifier :boolean
|
97
|
+
|
98
|
+
def self.coerce(input, _)
|
99
|
+
return nil if input.nil? || input == ''
|
100
|
+
return input if input.is_a?(TrueClass) || input.is_a?(FalseClass)
|
101
|
+
str = input.to_s
|
102
|
+
case str
|
103
|
+
when 'true', 'TRUE', 't', 'T', '1'
|
104
|
+
true
|
105
|
+
when 'false', 'FALSE', 'f', 'F', '0'
|
106
|
+
false
|
107
|
+
else
|
108
|
+
raise
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.format(value, format)
|
113
|
+
value.to_s
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class StringCoder < Coder
|
118
|
+
def self.coerce(input, _)
|
119
|
+
input.to_s
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.format(value, _)
|
123
|
+
value
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class SymbolCoder < Coder
|
128
|
+
type_identifier :symbol
|
129
|
+
|
130
|
+
def self.coerce(input, _)
|
131
|
+
input.to_sym
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.format(value, format)
|
135
|
+
value.to_s
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class DateCoder < Coder
|
140
|
+
type_identifier :date
|
141
|
+
|
142
|
+
def self.coerce(input, _)
|
143
|
+
return nil if input.nil? || input == ''
|
144
|
+
if input.is_a?(Numeric)
|
145
|
+
Time.at(input).to_date
|
146
|
+
elsif input.is_a?(String)
|
147
|
+
Date.parse(input)
|
148
|
+
elsif input.respond_to?(:to_date)
|
149
|
+
input.to_date
|
150
|
+
else
|
151
|
+
raise ParamsReadyError, "Unimplemented for type #{input.class.name}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.format(value, format)
|
156
|
+
value.to_s
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class DateTimeCoder < Coder
|
161
|
+
type_identifier :date
|
162
|
+
|
163
|
+
def self.coerce(input, _)
|
164
|
+
return nil if input.nil? || input == ''
|
165
|
+
if input.is_a?(Numeric)
|
166
|
+
Time.at(input).to_datetime
|
167
|
+
elsif input.is_a?(String)
|
168
|
+
DateTime.parse(input)
|
169
|
+
elsif input.respond_to?(:to_datetime)
|
170
|
+
input.to_datetime
|
171
|
+
else
|
172
|
+
raise ParamsReadyError, "Unimplemented for type #{input.class.name}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.format(value, format)
|
177
|
+
value.to_s
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'set'
|
2
|
+
require_relative '../extensions/undefined'
|
3
|
+
require_relative '../extensions/registry'
|
4
|
+
require_relative '../error'
|
5
|
+
|
6
|
+
module ParamsReady
|
7
|
+
module Value
|
8
|
+
|
9
|
+
class Constraint
|
10
|
+
class Error < ParamsReadyError; end
|
11
|
+
|
12
|
+
extend Extensions::Registry
|
13
|
+
registry :constraint_types, as: :constraint_type, getter: true
|
14
|
+
|
15
|
+
def self.register(name)
|
16
|
+
Constraint.register_constraint_type(name, self)
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :condition
|
20
|
+
|
21
|
+
def initialize(cond)
|
22
|
+
@condition = cond.freeze
|
23
|
+
freeze
|
24
|
+
end
|
25
|
+
|
26
|
+
def clamp?; false; end
|
27
|
+
|
28
|
+
def self.build(cond, *args, **opts, &block)
|
29
|
+
if block.nil?
|
30
|
+
new cond, *args, **opts
|
31
|
+
else
|
32
|
+
new cond, block, *args, **opts
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.instance(cond, *args, **opts)
|
37
|
+
case cond
|
38
|
+
when Range
|
39
|
+
RangeConstraint.new(cond, *args, **opts)
|
40
|
+
when Array, Set
|
41
|
+
EnumConstraint.new(cond, *args, **opts)
|
42
|
+
else
|
43
|
+
raise ParamsReadyError, "Unknown constraint type: " + cond.class.name
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def valid?(input)
|
48
|
+
raise ParamsReadyError, 'This is an abstract class'
|
49
|
+
end
|
50
|
+
|
51
|
+
def error_message
|
52
|
+
"didn't pass validation"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class RangeConstraint < Constraint
|
57
|
+
register :range
|
58
|
+
|
59
|
+
def initialize(cond, *args, **opts)
|
60
|
+
raise ParamsReadyError, "Expected Range, got: " + cond.class.name unless cond.is_a?(Range)
|
61
|
+
super cond, *args, **opts
|
62
|
+
end
|
63
|
+
|
64
|
+
def valid?(input)
|
65
|
+
@condition.include?(input)
|
66
|
+
end
|
67
|
+
|
68
|
+
def error_message
|
69
|
+
'not in range'
|
70
|
+
end
|
71
|
+
|
72
|
+
def clamp(value)
|
73
|
+
if value < @condition.min
|
74
|
+
@condition.min
|
75
|
+
elsif value > @condition.max
|
76
|
+
@condition.max
|
77
|
+
else
|
78
|
+
value
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def clamp?
|
83
|
+
return false if @condition.min.nil? || @condition.max.nil?
|
84
|
+
|
85
|
+
true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class EnumConstraint < Constraint
|
90
|
+
register :enum
|
91
|
+
|
92
|
+
def initialize(cond, *args, **opts)
|
93
|
+
raise ParamsReadyError, "Expected Set or Array, got: " + cond.class.name unless
|
94
|
+
cond.is_a?(Set) ||
|
95
|
+
cond.is_a?(Array)
|
96
|
+
super cond, *args, **opts
|
97
|
+
end
|
98
|
+
|
99
|
+
def valid?(input)
|
100
|
+
if input.is_a?(String)
|
101
|
+
@condition.include?(input) || @condition.include?(input.to_sym)
|
102
|
+
else
|
103
|
+
@condition.include?(input)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def error_message
|
108
|
+
'not in enum'
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class OperatorConstraint < Constraint
|
113
|
+
register :operator
|
114
|
+
|
115
|
+
OPERATORS = [:=~, :<, :<=, :==, :>=, :>].to_set.freeze
|
116
|
+
|
117
|
+
def initialize(operator, value, *args, **opts)
|
118
|
+
unless OPERATORS.member? operator
|
119
|
+
raise ParamsReadyError, "Unsupported operator: #{operator}"
|
120
|
+
end
|
121
|
+
cond = Condition.instance(operator, value)
|
122
|
+
super(cond, *args, **opts)
|
123
|
+
end
|
124
|
+
|
125
|
+
def clamp(value)
|
126
|
+
return value if valid?(value)
|
127
|
+
|
128
|
+
@condition.clamp(value)
|
129
|
+
end
|
130
|
+
|
131
|
+
def clamp?
|
132
|
+
@condition.clamp?
|
133
|
+
end
|
134
|
+
|
135
|
+
def valid?(input)
|
136
|
+
@condition.true?(input)
|
137
|
+
end
|
138
|
+
|
139
|
+
def error_message
|
140
|
+
@condition.error_message
|
141
|
+
end
|
142
|
+
|
143
|
+
module ClampingCondition
|
144
|
+
CLAMPING_OPERATORS = %i(<= == >=).to_set.freeze
|
145
|
+
def clamp(_)
|
146
|
+
case @operator
|
147
|
+
when :<=, :>=, :==
|
148
|
+
get_value
|
149
|
+
else
|
150
|
+
raise "Unexpected operator: #{@operator}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def clamp?
|
155
|
+
CLAMPING_OPERATORS.member? @operator
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class Condition
|
160
|
+
include ClampingCondition
|
161
|
+
|
162
|
+
def initialize(operator, value)
|
163
|
+
@operator = operator
|
164
|
+
@value = value
|
165
|
+
end
|
166
|
+
|
167
|
+
def true?(input)
|
168
|
+
input.send(@operator, get_value)
|
169
|
+
end
|
170
|
+
|
171
|
+
def error_message
|
172
|
+
"not #{@operator} #{get_value}"
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.instance(operator, value)
|
176
|
+
case value
|
177
|
+
when Method, Proc
|
178
|
+
DynamicCondition.new operator, value
|
179
|
+
else
|
180
|
+
StaticCondition.new operator, value
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
class StaticCondition < Condition
|
186
|
+
def get_value
|
187
|
+
@value
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class DynamicCondition < Condition
|
192
|
+
def get_value
|
193
|
+
@value.call
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|