runapi-core 0.5.2 → 0.6.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/lib/runapi/core/resource_helpers.rb +0 -218
- data/lib/runapi/core/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ad3b0781bacf4ae45f6fe15948391c47a7ba0e8a37ea9481af61c1faef44a881
|
|
4
|
+
data.tar.gz: 619dd3f7627899141c3072acf45aaeeaeafd4b312d974a4b5fd0982953967911
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 63b482c33a6a4fc50ea8a85f147735a155b6d7c6365be3e3cd71e032bde911da9864d072ba22ed324278a2dcc9033445b3ddf66d11c0245563091305870b832e
|
|
7
|
+
data.tar.gz: eaff58ff1a0ab6c1d5c613666acf32b78d74a73a1a998d5bd8842aacad5ee3bb34eadf142f64a52dbe2f24ab75ad7c5c4b529e422eece2bae32fe3f2628ae622
|
|
@@ -40,224 +40,6 @@ module RunApi
|
|
|
40
40
|
params[key.to_s] if params.key?(key.to_s)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
def validate_optional!(params, key, allowed)
|
|
44
|
-
value = param(params, key)
|
|
45
|
-
return unless value
|
|
46
|
-
|
|
47
|
-
unless allowed.include?(value)
|
|
48
|
-
raise Core::ValidationError, "Invalid #{key}: #{value}. Must be one of: #{allowed.join(", ")}"
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# ---- Contract validation ------------------------------------------
|
|
53
|
-
# Validates request params against the generated contract: model
|
|
54
|
-
# membership, then declared cross-field rules, then per-field
|
|
55
|
-
# required/enum/integer/min/max/length. `schema` is one action entry from the generated
|
|
56
|
-
# per-package CONTRACT (CONTRACT["<action>"]).
|
|
57
|
-
|
|
58
|
-
def validate_contract!(schema, params)
|
|
59
|
-
model = param_value(params, "model")
|
|
60
|
-
models = schema["models"] || []
|
|
61
|
-
if models.empty?
|
|
62
|
-
fields = schema.dig("fields_by_model", "_") || {}
|
|
63
|
-
else
|
|
64
|
-
unless models.include?(model)
|
|
65
|
-
raise Core::ValidationError, "model must be one of: #{models.sort.join(", ")}"
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
fields = schema.dig("fields_by_model", model) || {}
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
Array(schema["rules"]).each { |rule| enforce_contract_rule!(params, rule) }
|
|
72
|
-
fields.each do |field, rules|
|
|
73
|
-
validate_schema_field!(params, field, rules)
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def validate_schema_field!(params, field, rules)
|
|
78
|
-
value = param_value(params, field)
|
|
79
|
-
if !value.nil? && (rules.key?("min_items") || rules.key?("max_items"))
|
|
80
|
-
validate_schema_item_count!(field, value, rules)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
present = field_present?(params, field)
|
|
84
|
-
raise Core::ValidationError, "#{field} is required" if rules["required"] && !present
|
|
85
|
-
return unless present
|
|
86
|
-
|
|
87
|
-
if (enum = rules["enum"]) && !enum_value_allowed?(enum, value)
|
|
88
|
-
raise Core::ValidationError, "#{field} must be one of: #{enum.join(", ")}"
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
validate_schema_integer!(field, value, rules) if rules["type"] == "integer"
|
|
92
|
-
validate_schema_range!(field, value, rules) if rules.key?("min") || rules.key?("max")
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def validate_schema_item_count!(field, value, rules)
|
|
96
|
-
raise Core::ValidationError, "#{field} must be an array" unless value.is_a?(Array)
|
|
97
|
-
|
|
98
|
-
min = rules["min_items"]
|
|
99
|
-
max = rules["max_items"]
|
|
100
|
-
return if (min.nil? || value.size >= min) && (max.nil? || value.size <= max)
|
|
101
|
-
|
|
102
|
-
raise Core::ValidationError, item_count_message(field, min, max)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def item_count_message(field, min, max)
|
|
106
|
-
if min && max
|
|
107
|
-
"#{field} must contain between #{min} and #{max} items"
|
|
108
|
-
elsif min
|
|
109
|
-
"#{field} must contain at least #{min} items"
|
|
110
|
-
else
|
|
111
|
-
"#{field} must contain at most #{max} items"
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Mirrors GatewayEntry#validate_schema_integer!: a type: integer field
|
|
116
|
-
# rejects non-integer numbers (e.g. 11.5), which min/max alone admit.
|
|
117
|
-
def validate_schema_integer!(field, value, rules)
|
|
118
|
-
return if value.is_a?(Integer)
|
|
119
|
-
|
|
120
|
-
min = rules["min"]
|
|
121
|
-
max = rules["max"]
|
|
122
|
-
detail = (min && max) ? " between #{min} and #{max}" : ""
|
|
123
|
-
raise Core::ValidationError, "#{field} must be an integer#{detail}"
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def validate_schema_range!(field, value, rules)
|
|
127
|
-
if rules["length"]
|
|
128
|
-
measured = value.to_s.length
|
|
129
|
-
unit = "characters"
|
|
130
|
-
else
|
|
131
|
-
raise Core::ValidationError, "#{field} must be a number" unless value.is_a?(Numeric)
|
|
132
|
-
|
|
133
|
-
measured = value
|
|
134
|
-
unit = nil
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
min = rules["min"]
|
|
138
|
-
max = rules["max"]
|
|
139
|
-
return if (min.nil? || measured >= min) && (max.nil? || measured <= max)
|
|
140
|
-
|
|
141
|
-
raise Core::ValidationError, schema_range_message(field, min, max, unit)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def schema_range_message(field, min, max, unit)
|
|
145
|
-
suffix = unit ? " #{unit}" : ""
|
|
146
|
-
if min && max
|
|
147
|
-
"#{field} must be between #{min} and #{max}#{suffix}"
|
|
148
|
-
elsif min
|
|
149
|
-
"#{field} must be at least #{min}#{suffix}"
|
|
150
|
-
else
|
|
151
|
-
"#{field} must be at most #{max}#{suffix}"
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def enum_value_allowed?(enum, value)
|
|
156
|
-
enum.any? do |allowed|
|
|
157
|
-
if allowed == true || allowed == false
|
|
158
|
-
value == allowed
|
|
159
|
-
elsif allowed.is_a?(Numeric)
|
|
160
|
-
value.is_a?(Numeric) && value == allowed
|
|
161
|
-
elsif value.is_a?(Numeric)
|
|
162
|
-
allowed == value
|
|
163
|
-
else
|
|
164
|
-
allowed.to_s == value.to_s
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def enforce_contract_rule!(params, rule)
|
|
170
|
-
conditions = rule["when"] || {}
|
|
171
|
-
return unless conditions.all? { |field, condition| rule_condition_met?(params, field, condition) }
|
|
172
|
-
|
|
173
|
-
# Sorted so the message matches every other SDK; Go maps have no declaration order.
|
|
174
|
-
context = conditions.sort_by { |field, _| field.to_s }
|
|
175
|
-
.map { |field, condition| rule_condition_label(field, condition) }.join(" and ")
|
|
176
|
-
qualifier = context.empty? ? "" : " when #{context}"
|
|
177
|
-
|
|
178
|
-
Array(rule["required"]).each do |field|
|
|
179
|
-
next if field_present?(params, field)
|
|
180
|
-
|
|
181
|
-
raise Core::ValidationError, "#{field} is required#{qualifier}"
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
required_any = Array(rule["required_any"])
|
|
185
|
-
if required_any.any? && required_any.none? { |field| field_present?(params, field) }
|
|
186
|
-
raise Core::ValidationError, "one of #{required_any.join(", ")} is required#{qualifier}"
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
Array(rule["forbidden"]).each do |field|
|
|
190
|
-
next unless field_present?(params, field)
|
|
191
|
-
|
|
192
|
-
raise Core::ValidationError, "#{field} is not allowed#{qualifier}"
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
(rule["enum"] || {}).sort_by { |field, _| field.to_s }.each do |field, allowed|
|
|
196
|
-
next unless field_present?(params, field)
|
|
197
|
-
|
|
198
|
-
value = param_value(params, field)
|
|
199
|
-
next if Array(allowed).any? { |candidate| candidate.to_s == value.to_s }
|
|
200
|
-
|
|
201
|
-
raise Core::ValidationError, "#{field} must be one of: #{Array(allowed).join(", ")}#{qualifier}"
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
# A `when` entry is either `{present: true|false}` or a scalar the
|
|
206
|
-
# supplied value must equal. Rules never resolve declared defaults.
|
|
207
|
-
def rule_condition_met?(params, field, condition)
|
|
208
|
-
if condition.is_a?(Hash) && (condition.key?("present") || condition.key?(:present))
|
|
209
|
-
expected = condition["present"].nil? ? condition[:present] : condition["present"]
|
|
210
|
-
return field_present?(params, field) == (expected == true)
|
|
211
|
-
end
|
|
212
|
-
return false unless param_key?(params, field)
|
|
213
|
-
|
|
214
|
-
param_value(params, field).to_s == condition.to_s
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
def rule_condition_label(field, condition)
|
|
218
|
-
if condition.is_a?(Hash) && (condition.key?("present") || condition.key?(:present))
|
|
219
|
-
expected = condition["present"].nil? ? condition[:present] : condition["present"]
|
|
220
|
-
return (expected == true) ? "#{field} is present" : "#{field} is absent"
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
"#{field} is #{condition}"
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
def field_present?(params, field)
|
|
227
|
-
return false unless param_key?(params, field)
|
|
228
|
-
|
|
229
|
-
value = param_value(params, field)
|
|
230
|
-
case value
|
|
231
|
-
when false then true
|
|
232
|
-
when Array then value.any? { |item| present_value?(item) }
|
|
233
|
-
else present_value?(value)
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def param_key?(params, field)
|
|
238
|
-
params.key?(field.to_sym) || params.key?(field.to_s)
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
# Indifferent value lookup for a string-or-symbol field across params that
|
|
242
|
-
# may be keyed either way.
|
|
243
|
-
def param_value(params, field)
|
|
244
|
-
if params.key?(field.to_sym)
|
|
245
|
-
params[field.to_sym]
|
|
246
|
-
elsif params.key?(field.to_s)
|
|
247
|
-
params[field.to_s]
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
def present_value?(value)
|
|
252
|
-
case value
|
|
253
|
-
when nil, false then false
|
|
254
|
-
when true then true
|
|
255
|
-
when String then !value.strip.empty?
|
|
256
|
-
when Array, Hash then !value.empty?
|
|
257
|
-
else true
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
|
|
261
43
|
def default_response_class
|
|
262
44
|
if self.class.const_defined?(:RESPONSE_CLASS, false)
|
|
263
45
|
self.class::RESPONSE_CLASS
|
data/lib/runapi/core/version.rb
CHANGED