dry-schema 1.3.4 → 1.5.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 +253 -101
- data/LICENSE +1 -1
- data/README.md +6 -6
- data/config/errors.yml +4 -0
- data/dry-schema.gemspec +46 -0
- data/lib/dry-schema.rb +1 -1
- data/lib/dry/schema.rb +20 -7
- data/lib/dry/schema/compiler.rb +4 -4
- data/lib/dry/schema/config.rb +15 -6
- data/lib/dry/schema/constants.rb +19 -9
- data/lib/dry/schema/dsl.rb +144 -38
- data/lib/dry/schema/extensions.rb +10 -2
- data/lib/dry/schema/extensions/hints.rb +15 -8
- data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +2 -2
- data/lib/dry/schema/extensions/hints/message_set_methods.rb +0 -47
- data/lib/dry/schema/extensions/info.rb +27 -0
- data/lib/dry/schema/extensions/info/schema_compiler.rb +105 -0
- data/lib/dry/schema/extensions/monads.rb +1 -1
- data/lib/dry/schema/extensions/struct.rb +32 -0
- data/lib/dry/schema/json.rb +1 -1
- data/lib/dry/schema/key.rb +20 -5
- data/lib/dry/schema/key_coercer.rb +4 -4
- data/lib/dry/schema/key_map.rb +9 -4
- data/lib/dry/schema/key_validator.rb +66 -0
- data/lib/dry/schema/macros.rb +8 -8
- data/lib/dry/schema/macros/array.rb +17 -4
- data/lib/dry/schema/macros/core.rb +11 -6
- data/lib/dry/schema/macros/dsl.rb +53 -21
- data/lib/dry/schema/macros/each.rb +4 -4
- data/lib/dry/schema/macros/filled.rb +5 -6
- data/lib/dry/schema/macros/hash.rb +21 -3
- data/lib/dry/schema/macros/key.rb +10 -10
- data/lib/dry/schema/macros/maybe.rb +4 -5
- data/lib/dry/schema/macros/optional.rb +1 -1
- data/lib/dry/schema/macros/required.rb +1 -1
- data/lib/dry/schema/macros/schema.rb +23 -2
- data/lib/dry/schema/macros/value.rb +34 -7
- data/lib/dry/schema/message.rb +35 -9
- data/lib/dry/schema/message/or.rb +18 -39
- data/lib/dry/schema/message/or/abstract.rb +28 -0
- data/lib/dry/schema/message/or/multi_path.rb +37 -0
- data/lib/dry/schema/message/or/single_path.rb +64 -0
- data/lib/dry/schema/message_compiler.rb +40 -19
- data/lib/dry/schema/message_compiler/visitor_opts.rb +2 -2
- data/lib/dry/schema/message_set.rb +26 -37
- data/lib/dry/schema/messages.rb +6 -6
- data/lib/dry/schema/messages/abstract.rb +79 -66
- data/lib/dry/schema/messages/i18n.rb +36 -10
- data/lib/dry/schema/messages/namespaced.rb +13 -3
- data/lib/dry/schema/messages/template.rb +19 -44
- data/lib/dry/schema/messages/yaml.rb +72 -13
- data/lib/dry/schema/params.rb +1 -1
- data/lib/dry/schema/path.rb +44 -5
- data/lib/dry/schema/predicate.rb +2 -2
- data/lib/dry/schema/predicate_inferrer.rb +4 -184
- data/lib/dry/schema/predicate_registry.rb +3 -24
- data/lib/dry/schema/primitive_inferrer.rb +3 -86
- data/lib/dry/schema/processor.rb +54 -50
- data/lib/dry/schema/processor_steps.rb +139 -0
- data/lib/dry/schema/result.rb +52 -5
- data/lib/dry/schema/rule_applier.rb +8 -7
- data/lib/dry/schema/step.rb +79 -0
- data/lib/dry/schema/trace.rb +5 -4
- data/lib/dry/schema/type_container.rb +3 -3
- data/lib/dry/schema/type_registry.rb +2 -2
- data/lib/dry/schema/types.rb +1 -1
- data/lib/dry/schema/value_coercer.rb +2 -2
- data/lib/dry/schema/version.rb +1 -1
- metadata +21 -7
@@ -1,67 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "dry/initializer"
|
4
|
+
require "dry/equalizer"
|
4
5
|
|
5
|
-
require
|
6
|
+
require "dry/schema/constants"
|
6
7
|
|
7
8
|
module Dry
|
8
9
|
module Schema
|
9
10
|
module Messages
|
10
|
-
# Template wraps a string with interpolation tokens and defines evaluator function
|
11
|
-
# dynamically
|
12
|
-
#
|
13
11
|
# @api private
|
14
12
|
class Template
|
15
|
-
|
13
|
+
extend Dry::Initializer
|
14
|
+
include Dry::Equalizer(:messages, :key, :options)
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# @return [String]
|
21
|
-
attr_reader :text
|
22
|
-
|
23
|
-
# !@attribute [r] tokens
|
24
|
-
# @return [Hash]
|
25
|
-
attr_reader :tokens
|
26
|
-
|
27
|
-
# !@attribute [r] evaluator
|
28
|
-
# @return [Proc]
|
29
|
-
attr_reader :evaluator
|
16
|
+
option :messages
|
17
|
+
option :key
|
18
|
+
option :options
|
30
19
|
|
31
20
|
# @api private
|
32
|
-
def
|
33
|
-
|
21
|
+
def data(data = EMPTY_HASH)
|
22
|
+
ensure_message!
|
23
|
+
messages.interpolatable_data(key, options, **options, **data)
|
34
24
|
end
|
35
25
|
|
36
26
|
# @api private
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
evaluator = <<-RUBY.strip
|
42
|
-
-> (#{tokens.map { |token| "#{token}:" }.join(", ")}) { "#{text}" }
|
43
|
-
RUBY
|
44
|
-
|
45
|
-
[text, tokens, eval(evaluator, binding, __FILE__, __LINE__ - 3)]
|
27
|
+
def call(data = EMPTY_HASH)
|
28
|
+
ensure_message!
|
29
|
+
messages.interpolate(key, options, **data)
|
46
30
|
end
|
31
|
+
alias_method :[], :call
|
47
32
|
|
48
|
-
|
49
|
-
def initialize(text, tokens, evaluator)
|
50
|
-
@text = text
|
51
|
-
@tokens = tokens
|
52
|
-
@evaluator = evaluator
|
53
|
-
end
|
33
|
+
private
|
54
34
|
|
55
|
-
|
56
|
-
|
57
|
-
tokens.each_with_object({}) { |k, h| h[k] = input[k] }
|
58
|
-
end
|
35
|
+
def ensure_message!
|
36
|
+
return if messages.key?(key, options)
|
59
37
|
|
60
|
-
|
61
|
-
def call(data = EMPTY_HASH)
|
62
|
-
data.empty? ? evaluator.() : evaluator.(data)
|
38
|
+
raise KeyError, "No message found for template, template=#{inspect}"
|
63
39
|
end
|
64
|
-
alias_method :[], :call
|
65
40
|
end
|
66
41
|
end
|
67
42
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "yaml"
|
4
|
+
require "pathname"
|
5
5
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
6
|
+
require "dry/equalizer"
|
7
|
+
require "dry/schema/constants"
|
8
|
+
require "dry/schema/messages/abstract"
|
9
9
|
|
10
10
|
module Dry
|
11
11
|
module Schema
|
@@ -13,7 +13,13 @@ module Dry
|
|
13
13
|
#
|
14
14
|
# @api public
|
15
15
|
class Messages::YAML < Messages::Abstract
|
16
|
-
LOCALE_TOKEN =
|
16
|
+
LOCALE_TOKEN = "%<locale>s"
|
17
|
+
TOKEN_REGEXP = /%{(\w*)}/.freeze
|
18
|
+
EMPTY_CONTEXT = Object.new.tap { |ctx|
|
19
|
+
def ctx.context
|
20
|
+
binding
|
21
|
+
end
|
22
|
+
}.freeze.context
|
17
23
|
|
18
24
|
include Dry::Equalizer(:data)
|
19
25
|
|
@@ -22,7 +28,7 @@ module Dry
|
|
22
28
|
# @return [Hash]
|
23
29
|
attr_reader :data
|
24
30
|
|
25
|
-
# Translation function
|
31
|
+
# Translation function
|
26
32
|
#
|
27
33
|
# @return [Proc]
|
28
34
|
attr_reader :t
|
@@ -45,21 +51,26 @@ module Dry
|
|
45
51
|
hash.each do |key, value|
|
46
52
|
flat_hash(value, [*path, key], keys) if value.is_a?(Hash)
|
47
53
|
|
48
|
-
if value.is_a?(String) && hash[
|
54
|
+
if value.is_a?(String) && hash["text"] != value
|
49
55
|
keys[[*path, key].join(DOT)] = {
|
50
56
|
text: value,
|
51
57
|
meta: EMPTY_HASH
|
52
58
|
}
|
53
|
-
elsif value.is_a?(Hash) && value[
|
59
|
+
elsif value.is_a?(Hash) && value["text"].is_a?(String)
|
54
60
|
keys[[*path, key].join(DOT)] = {
|
55
|
-
text: value[
|
56
|
-
meta: value.dup.delete_if { |k| k ==
|
61
|
+
text: value["text"],
|
62
|
+
meta: value.dup.delete_if { |k| k == "text" }.map { |k, v| [k.to_sym, v] }.to_h
|
57
63
|
}
|
58
64
|
end
|
59
65
|
end
|
60
66
|
keys
|
61
67
|
end
|
62
68
|
|
69
|
+
# @api private
|
70
|
+
def self.cache
|
71
|
+
@cache ||= Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new }
|
72
|
+
end
|
73
|
+
|
63
74
|
# @api private
|
64
75
|
def initialize(data: EMPTY_HASH, config: nil)
|
65
76
|
super()
|
@@ -68,6 +79,18 @@ module Dry
|
|
68
79
|
@t = proc { |key, locale: default_locale| get("%<locale>s.#{key}", locale: locale) }
|
69
80
|
end
|
70
81
|
|
82
|
+
# Get an array of looked up paths
|
83
|
+
#
|
84
|
+
# @param [Symbol] predicate
|
85
|
+
# @param [Hash] options
|
86
|
+
#
|
87
|
+
# @return [String]
|
88
|
+
#
|
89
|
+
# @api public
|
90
|
+
def looked_up_paths(predicate, options)
|
91
|
+
super.map { |path| path % {locale: options[:locale] || default_locale} }
|
92
|
+
end
|
93
|
+
|
71
94
|
# Get a message for the given key and its options
|
72
95
|
#
|
73
96
|
# @param [Symbol] key
|
@@ -112,12 +135,48 @@ module Dry
|
|
112
135
|
|
113
136
|
# @api private
|
114
137
|
def prepare
|
115
|
-
@data = config.load_paths.map { |path| load_translations(path) }.reduce(:merge)
|
138
|
+
@data = config.load_paths.map { |path| load_translations(path) }.reduce({}, :merge)
|
116
139
|
self
|
117
140
|
end
|
118
141
|
|
142
|
+
# @api private
|
143
|
+
def interpolatable_data(key, options, **data)
|
144
|
+
tokens = evaluation_context(key, options).fetch(:tokens)
|
145
|
+
data.select { |k,| tokens.include?(k) }
|
146
|
+
end
|
147
|
+
|
148
|
+
# @api private
|
149
|
+
def interpolate(key, options, **data)
|
150
|
+
evaluator = evaluation_context(key, options).fetch(:evaluator)
|
151
|
+
data.empty? ? evaluator.() : evaluator.(**data)
|
152
|
+
end
|
153
|
+
|
119
154
|
private
|
120
155
|
|
156
|
+
# @api private
|
157
|
+
def evaluation_context(key, options)
|
158
|
+
cache.fetch_or_store(get(key, options).fetch(:text)) do |input|
|
159
|
+
tokens = input.scan(TOKEN_REGEXP).flatten(1).map(&:to_sym).to_set
|
160
|
+
text = input.gsub("%", "#")
|
161
|
+
|
162
|
+
# rubocop:disable Security/Eval
|
163
|
+
evaluator = eval(<<~RUBY, EMPTY_CONTEXT, __FILE__, __LINE__ + 1)
|
164
|
+
-> (#{tokens.map { |token| "#{token}:" }.join(", ")}) { "#{text}" }
|
165
|
+
RUBY
|
166
|
+
# rubocop:enable Security/Eval
|
167
|
+
|
168
|
+
{
|
169
|
+
tokens: tokens,
|
170
|
+
evaluator: evaluator
|
171
|
+
}
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# @api private
|
176
|
+
def cache
|
177
|
+
@cache ||= self.class.cache[self]
|
178
|
+
end
|
179
|
+
|
121
180
|
# @api private
|
122
181
|
def load_translations(path)
|
123
182
|
data = self.class.flat_hash(YAML.load_file(path))
|
@@ -131,7 +190,7 @@ module Dry
|
|
131
190
|
def evaluated_key(key, options)
|
132
191
|
return key unless key.include?(LOCALE_TOKEN)
|
133
192
|
|
134
|
-
key % {
|
193
|
+
key % {locale: options[:locale] || default_locale}
|
135
194
|
end
|
136
195
|
end
|
137
196
|
end
|
data/lib/dry/schema/params.rb
CHANGED
data/lib/dry/schema/path.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "dry/schema/constants"
|
4
4
|
|
5
5
|
module Dry
|
6
6
|
module Schema
|
@@ -8,6 +8,7 @@ module Dry
|
|
8
8
|
#
|
9
9
|
# @api private
|
10
10
|
class Path
|
11
|
+
include Dry.Equalizer(:keys)
|
11
12
|
include Comparable
|
12
13
|
include Enumerable
|
13
14
|
|
@@ -23,7 +24,7 @@ module Dry
|
|
23
24
|
# @return [Path]
|
24
25
|
#
|
25
26
|
# @api private
|
26
|
-
def self.
|
27
|
+
def self.call(spec)
|
27
28
|
case spec
|
28
29
|
when Symbol, Array
|
29
30
|
new(Array[*spec])
|
@@ -34,10 +35,15 @@ module Dry
|
|
34
35
|
when Path
|
35
36
|
spec
|
36
37
|
else
|
37
|
-
raise ArgumentError,
|
38
|
+
raise ArgumentError, "+spec+ must be either a Symbol, Array, Hash or a Path"
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
42
|
+
# @api private
|
43
|
+
def self.[](spec)
|
44
|
+
call(spec)
|
45
|
+
end
|
46
|
+
|
41
47
|
# Extract a list of keys from a hash
|
42
48
|
#
|
43
49
|
# @api private
|
@@ -52,6 +58,28 @@ module Dry
|
|
52
58
|
@keys = keys
|
53
59
|
end
|
54
60
|
|
61
|
+
# @api private
|
62
|
+
def to_h(value = EMPTY_ARRAY.dup)
|
63
|
+
curr_idx = 0
|
64
|
+
last_idx = keys.size - 1
|
65
|
+
hash = EMPTY_HASH.dup
|
66
|
+
node = hash
|
67
|
+
|
68
|
+
while curr_idx <= last_idx
|
69
|
+
node =
|
70
|
+
node[keys[curr_idx]] =
|
71
|
+
if curr_idx == last_idx
|
72
|
+
value.is_a?(Array) ? value : [value]
|
73
|
+
else
|
74
|
+
EMPTY_HASH.dup
|
75
|
+
end
|
76
|
+
|
77
|
+
curr_idx += 1
|
78
|
+
end
|
79
|
+
|
80
|
+
hash
|
81
|
+
end
|
82
|
+
|
55
83
|
# @api private
|
56
84
|
def each(&block)
|
57
85
|
keys.each(&block)
|
@@ -83,8 +111,19 @@ module Dry
|
|
83
111
|
end
|
84
112
|
|
85
113
|
# @api private
|
86
|
-
def
|
87
|
-
|
114
|
+
def &(other)
|
115
|
+
unless same_root?(other)
|
116
|
+
raise ArgumentError, "#{other.inspect} doesn't have the same root #{inspect}"
|
117
|
+
end
|
118
|
+
|
119
|
+
self.class.new(
|
120
|
+
key_matches(other, :select).compact.reject { |value| value.equal?(false) }
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
124
|
+
# @api private
|
125
|
+
def key_matches(other, meth = :map)
|
126
|
+
public_send(meth) { |key| (idx = other.index(key)) && keys[idx].equal?(key) }
|
88
127
|
end
|
89
128
|
|
90
129
|
# @api private
|
data/lib/dry/schema/predicate.rb
CHANGED
@@ -1,196 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "dry/types/predicate_inferrer"
|
4
4
|
|
5
5
|
module Dry
|
6
6
|
module Schema
|
7
|
-
# PredicateInferrer is used internally by `Macros::Value`
|
8
|
-
# for inferring type-check predicates from type specs.
|
9
|
-
#
|
10
7
|
# @api private
|
11
|
-
class PredicateInferrer
|
12
|
-
|
8
|
+
class PredicateInferrer < ::Dry::Types::PredicateInferrer
|
9
|
+
Compiler = ::Class.new(superclass::Compiler)
|
13
10
|
|
14
|
-
|
15
|
-
DateTime => :date_time?,
|
16
|
-
FalseClass => :false?,
|
17
|
-
Integer => :int?,
|
18
|
-
NilClass => :nil?,
|
19
|
-
String => :str?,
|
20
|
-
TrueClass => :true?,
|
21
|
-
BigDecimal => :decimal?
|
22
|
-
}.freeze
|
23
|
-
|
24
|
-
REDUCED_TYPES = {
|
25
|
-
[[[:true?], [:false?]]] => %i[bool?]
|
26
|
-
}.freeze
|
27
|
-
|
28
|
-
HASH = %i[hash?].freeze
|
29
|
-
|
30
|
-
ARRAY = %i[array?].freeze
|
31
|
-
|
32
|
-
NIL = %i[nil?].freeze
|
33
|
-
|
34
|
-
# Compiler reduces type AST into a list of predicates
|
35
|
-
#
|
36
|
-
# @api private
|
37
|
-
class Compiler
|
38
|
-
# @return [PredicateRegistry]
|
39
|
-
# @api private
|
40
|
-
attr_reader :registry
|
41
|
-
|
42
|
-
# @api private
|
43
|
-
def initialize(registry)
|
44
|
-
@registry = registry
|
45
|
-
end
|
46
|
-
|
47
|
-
# @api private
|
48
|
-
def infer_predicate(type)
|
49
|
-
[TYPE_TO_PREDICATE.fetch(type) { :"#{type.name.split('::').last.downcase}?" }]
|
50
|
-
end
|
51
|
-
|
52
|
-
# @api private
|
53
|
-
def visit(node)
|
54
|
-
meth, rest = node
|
55
|
-
public_send(:"visit_#{meth}", rest)
|
56
|
-
end
|
57
|
-
|
58
|
-
# @api private
|
59
|
-
def visit_nominal(node)
|
60
|
-
type = node[0]
|
61
|
-
predicate = infer_predicate(type)
|
62
|
-
|
63
|
-
if registry.key?(predicate[0])
|
64
|
-
predicate
|
65
|
-
else
|
66
|
-
[type?: type]
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
# @api private
|
71
|
-
def visit_hash(_)
|
72
|
-
HASH
|
73
|
-
end
|
74
|
-
|
75
|
-
# @api private
|
76
|
-
def visit_array(_)
|
77
|
-
ARRAY
|
78
|
-
end
|
79
|
-
|
80
|
-
# @api private
|
81
|
-
def visit_lax(node)
|
82
|
-
visit(node)
|
83
|
-
end
|
84
|
-
|
85
|
-
# @api private
|
86
|
-
def visit_constructor(node)
|
87
|
-
other, * = node
|
88
|
-
visit(other)
|
89
|
-
end
|
90
|
-
|
91
|
-
# @api private
|
92
|
-
def visit_enum(node)
|
93
|
-
other, * = node
|
94
|
-
visit(other)
|
95
|
-
end
|
96
|
-
|
97
|
-
# @api private
|
98
|
-
def visit_sum(node)
|
99
|
-
left_node, right_node, = node
|
100
|
-
left = visit(left_node)
|
101
|
-
right = visit(right_node)
|
102
|
-
|
103
|
-
if left.eql?(NIL)
|
104
|
-
right
|
105
|
-
else
|
106
|
-
[[left, right]]
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
# @api private
|
111
|
-
def visit_constrained(node)
|
112
|
-
other, rules = node
|
113
|
-
predicates = visit(rules)
|
114
|
-
|
115
|
-
if predicates.empty?
|
116
|
-
visit(other)
|
117
|
-
else
|
118
|
-
[*visit(other), *merge_predicates(predicates)]
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# @api private
|
123
|
-
def visit_any(_)
|
124
|
-
EMPTY_ARRAY
|
125
|
-
end
|
126
|
-
|
127
|
-
# @api private
|
128
|
-
def visit_and(node)
|
129
|
-
left, right = node
|
130
|
-
visit(left) + visit(right)
|
131
|
-
end
|
132
|
-
|
133
|
-
# @api private
|
134
|
-
def visit_predicate(node)
|
135
|
-
pred, args = node
|
136
|
-
|
137
|
-
if pred.equal?(:type?)
|
138
|
-
EMPTY_ARRAY
|
139
|
-
elsif registry.key?(pred)
|
140
|
-
*curried, _ = args
|
141
|
-
values = curried.map { |_, v| v }
|
142
|
-
|
143
|
-
if values.empty?
|
144
|
-
[pred]
|
145
|
-
else
|
146
|
-
[pred => values[0]]
|
147
|
-
end
|
148
|
-
else
|
149
|
-
EMPTY_ARRAY
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
private
|
154
|
-
|
155
|
-
# @api private
|
156
|
-
def merge_predicates(nodes)
|
157
|
-
preds, merged = nodes.each_with_object([[], {}]) do |predicate, (ps, h)|
|
158
|
-
if predicate.is_a?(::Hash)
|
159
|
-
h.update(predicate)
|
160
|
-
else
|
161
|
-
ps << predicate
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
merged.empty? ? preds : [*preds, merged]
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
# @return [Compiler]
|
170
|
-
# @api private
|
171
|
-
attr_reader :compiler
|
172
|
-
|
173
|
-
# @api private
|
174
|
-
def initialize(registry)
|
11
|
+
def initialize(registry = PredicateRegistry.new)
|
175
12
|
@compiler = Compiler.new(registry)
|
176
13
|
end
|
177
|
-
|
178
|
-
# Infer predicate identifier from the provided type
|
179
|
-
#
|
180
|
-
# @return [Symbol]
|
181
|
-
#
|
182
|
-
# @api private
|
183
|
-
def [](type)
|
184
|
-
self.class.fetch_or_store(type.hash) do
|
185
|
-
predicates = compiler.visit(type.to_ast)
|
186
|
-
|
187
|
-
if predicates.is_a?(Hash)
|
188
|
-
predicates
|
189
|
-
else
|
190
|
-
REDUCED_TYPES[predicates] || predicates
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
14
|
end
|
195
15
|
end
|
196
16
|
end
|