haveapi 0.28.3 → 0.29.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/README.md +139 -0
- data/Rakefile +1 -0
- data/haveapi.gemspec +2 -1
- data/lib/haveapi/action.rb +26 -9
- data/lib/haveapi/actions/default.rb +5 -2
- data/lib/haveapi/actions/paginable.rb +8 -4
- data/lib/haveapi/authentication/oauth2/provider.rb +1 -1
- data/lib/haveapi/authentication/token/config.rb +10 -3
- data/lib/haveapi/authentication/token/provider.rb +22 -21
- data/lib/haveapi/context.rb +4 -1
- data/lib/haveapi/extensions/exception_mailer.rb +136 -17
- data/lib/haveapi/i18n.rb +125 -0
- data/lib/haveapi/locales/cs.yml +167 -0
- data/lib/haveapi/locales/en.yml +168 -0
- data/lib/haveapi/metadata.rb +25 -3
- data/lib/haveapi/model_adapters/active_record.rb +40 -26
- data/lib/haveapi/output_formatter.rb +2 -2
- data/lib/haveapi/parameters/metadata_i18n.rb +179 -0
- data/lib/haveapi/parameters/resource.rb +18 -7
- data/lib/haveapi/parameters/typed.rb +27 -20
- data/lib/haveapi/params.rb +76 -7
- data/lib/haveapi/resource.rb +1 -1
- data/lib/haveapi/resources/action_state.rb +47 -27
- data/lib/haveapi/server.rb +287 -94
- data/lib/haveapi/spec/api_builder.rb +25 -0
- data/lib/haveapi/spec/spec_methods.rb +10 -0
- data/lib/haveapi/tasks/i18n.rb +198 -0
- data/lib/haveapi/validator_chain.rb +1 -1
- data/lib/haveapi/validators/acceptance.rb +5 -2
- data/lib/haveapi/validators/confirmation.rb +5 -2
- data/lib/haveapi/validators/exclusion.rb +2 -2
- data/lib/haveapi/validators/format.rb +5 -2
- data/lib/haveapi/validators/inclusion.rb +2 -2
- data/lib/haveapi/validators/length.rb +5 -5
- data/lib/haveapi/validators/numericality.rb +20 -22
- data/lib/haveapi/validators/presence.rb +4 -2
- data/lib/haveapi/version.rb +1 -1
- data/lib/haveapi.rb +1 -0
- data/spec/authentication/oauth2_spec.rb +10 -0
- data/spec/extensions/exception_mailer_spec.rb +195 -0
- data/spec/i18n_spec.rb +520 -0
- data/spec/params_spec.rb +183 -0
- data/spec/server/integration_spec.rb +34 -0
- metadata +30 -7
- data/doc/create-client.md +0 -107
- data/doc/json-schema.html +0 -1182
- data/doc/protocol.md +0 -535
- data/doc/protocol.png +0 -0
data/lib/haveapi/metadata.rb
CHANGED
|
@@ -45,12 +45,34 @@ module HaveAPI
|
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
def describe(context)
|
|
48
|
+
def describe(context, type:)
|
|
49
49
|
{
|
|
50
|
-
input: @input && @input.describe(
|
|
51
|
-
|
|
50
|
+
input: @input && @input.describe(
|
|
51
|
+
context,
|
|
52
|
+
i18n_path: Params.metadata_i18n_path(context, type, :input)
|
|
53
|
+
),
|
|
54
|
+
output: @output && @output.describe(
|
|
55
|
+
context,
|
|
56
|
+
metadata: true,
|
|
57
|
+
i18n_path: Params.metadata_i18n_path(context, type, :output)
|
|
58
|
+
)
|
|
52
59
|
}
|
|
53
60
|
end
|
|
61
|
+
|
|
62
|
+
def parameter_metadata_i18n_items(context, type:)
|
|
63
|
+
[
|
|
64
|
+
*@input&.parameter_metadata_i18n_items(
|
|
65
|
+
context,
|
|
66
|
+
i18n_path: Params.metadata_i18n_path(context, type, :input),
|
|
67
|
+
meta_type: type
|
|
68
|
+
),
|
|
69
|
+
*@output&.parameter_metadata_i18n_items(
|
|
70
|
+
context,
|
|
71
|
+
i18n_path: Params.metadata_i18n_path(context, type, :output),
|
|
72
|
+
meta_type: type
|
|
73
|
+
)
|
|
74
|
+
].compact
|
|
75
|
+
end
|
|
54
76
|
end
|
|
55
77
|
end
|
|
56
78
|
end
|
|
@@ -81,7 +81,10 @@ module HaveAPI::ModelAdapters
|
|
|
81
81
|
|
|
82
82
|
if limit && limit > HaveAPI::Actions::Paginable::MAX_LIMIT
|
|
83
83
|
error!(
|
|
84
|
-
|
|
84
|
+
HaveAPI.message(
|
|
85
|
+
'haveapi.pagination.limit_max',
|
|
86
|
+
max: HaveAPI::Actions::Paginable::MAX_LIMIT
|
|
87
|
+
),
|
|
85
88
|
{},
|
|
86
89
|
http_status: 400
|
|
87
90
|
)
|
|
@@ -174,18 +177,18 @@ module HaveAPI::ModelAdapters
|
|
|
174
177
|
if raw.nil?
|
|
175
178
|
return nil if allow_null
|
|
176
179
|
|
|
177
|
-
raise
|
|
180
|
+
raise invalid_id(original)
|
|
178
181
|
end
|
|
179
182
|
|
|
180
183
|
if raw.is_a?(Array) || raw.is_a?(Hash) || [true, false].include?(raw)
|
|
181
|
-
raise
|
|
184
|
+
raise invalid_id(original)
|
|
182
185
|
elsif raw.is_a?(String)
|
|
183
186
|
stripped = raw.strip
|
|
184
187
|
|
|
185
188
|
if stripped.empty?
|
|
186
189
|
return nil if allow_null
|
|
187
190
|
|
|
188
|
-
raise
|
|
191
|
+
raise invalid_id(original)
|
|
189
192
|
end
|
|
190
193
|
|
|
191
194
|
raw = stripped
|
|
@@ -193,7 +196,7 @@ module HaveAPI::ModelAdapters
|
|
|
193
196
|
|
|
194
197
|
value = if integer_pk?(model)
|
|
195
198
|
id = coerce_integer_id(raw, original)
|
|
196
|
-
raise
|
|
199
|
+
raise invalid_id(original) if id < 0
|
|
197
200
|
|
|
198
201
|
id
|
|
199
202
|
else
|
|
@@ -207,14 +210,14 @@ module HaveAPI::ModelAdapters
|
|
|
207
210
|
end
|
|
208
211
|
|
|
209
212
|
if ret.nil? && !allow_null
|
|
210
|
-
raise
|
|
213
|
+
raise resource_not_found
|
|
211
214
|
end
|
|
212
215
|
|
|
213
216
|
ret
|
|
214
217
|
rescue ::ActiveRecord::RecordNotFound
|
|
215
|
-
raise
|
|
218
|
+
raise resource_not_found
|
|
216
219
|
rescue ArgumentError, TypeError
|
|
217
|
-
raise
|
|
220
|
+
raise invalid_id(original)
|
|
218
221
|
end
|
|
219
222
|
|
|
220
223
|
def self.integer_pk?(model)
|
|
@@ -232,7 +235,7 @@ module HaveAPI::ModelAdapters
|
|
|
232
235
|
raw
|
|
233
236
|
when Float
|
|
234
237
|
unless raw.finite? && (raw % 1) == 0
|
|
235
|
-
raise
|
|
238
|
+
raise invalid_id(original)
|
|
236
239
|
end
|
|
237
240
|
|
|
238
241
|
raw.to_i
|
|
@@ -240,23 +243,38 @@ module HaveAPI::ModelAdapters
|
|
|
240
243
|
s = raw.strip
|
|
241
244
|
|
|
242
245
|
if s.empty? || !s.match?(/\A[+-]?\d+\z/)
|
|
243
|
-
raise
|
|
246
|
+
raise invalid_id(original)
|
|
244
247
|
end
|
|
245
248
|
|
|
246
249
|
Integer(s, 10)
|
|
247
250
|
else
|
|
248
|
-
raise
|
|
251
|
+
raise invalid_id(original)
|
|
249
252
|
end
|
|
250
253
|
end
|
|
254
|
+
|
|
255
|
+
def self.invalid_id(original)
|
|
256
|
+
HaveAPI::ValidationError.new(
|
|
257
|
+
HaveAPI.message('haveapi.validation.invalid_id', value: original.inspect)
|
|
258
|
+
)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def self.resource_not_found
|
|
262
|
+
HaveAPI::ValidationError.new(
|
|
263
|
+
HaveAPI.message('haveapi.validation.resource_not_found')
|
|
264
|
+
)
|
|
265
|
+
end
|
|
251
266
|
end
|
|
252
267
|
|
|
253
268
|
class Output < ::HaveAPI::ModelAdapter::Output
|
|
254
269
|
def self.used_by(action)
|
|
255
270
|
action.meta(:object) do
|
|
256
271
|
output do
|
|
257
|
-
custom :path_params,
|
|
258
|
-
|
|
259
|
-
|
|
272
|
+
custom :path_params,
|
|
273
|
+
label: HaveAPI.message('haveapi.parameters.active_record.path_params.label'),
|
|
274
|
+
desc: HaveAPI.message('haveapi.parameters.active_record.path_params.description')
|
|
275
|
+
bool :resolved,
|
|
276
|
+
label: HaveAPI.message('haveapi.parameters.active_record.resolved.label'),
|
|
277
|
+
desc: HaveAPI.message('haveapi.parameters.active_record.resolved.description')
|
|
260
278
|
end
|
|
261
279
|
end
|
|
262
280
|
|
|
@@ -268,30 +286,26 @@ module HaveAPI::ModelAdapters
|
|
|
268
286
|
elsif raw.is_a?(Array)
|
|
269
287
|
raw
|
|
270
288
|
else
|
|
271
|
-
raise HaveAPI::ValidationError,
|
|
289
|
+
raise HaveAPI::ValidationError,
|
|
290
|
+
HaveAPI.message('haveapi.validation.includes_string_or_array')
|
|
272
291
|
end
|
|
273
292
|
|
|
274
293
|
values.map do |value|
|
|
275
294
|
unless value.is_a?(String)
|
|
276
|
-
raise HaveAPI::ValidationError,
|
|
295
|
+
raise HaveAPI::ValidationError,
|
|
296
|
+
HaveAPI.message('haveapi.validation.includes_only_strings')
|
|
277
297
|
end
|
|
278
298
|
|
|
279
299
|
value.strip
|
|
280
300
|
end
|
|
281
301
|
end
|
|
282
302
|
|
|
283
|
-
desc = <<~END
|
|
284
|
-
A list of names of associated resources separated by a comma.
|
|
285
|
-
Nested associations are declared with '__' between resource names.
|
|
286
|
-
For example, 'user,node' will resolve the two associations.
|
|
287
|
-
To resolve further associations of node, use e.g. 'user,node__location',
|
|
288
|
-
to go even deeper, use e.g. 'user,node__location__environment'.
|
|
289
|
-
END
|
|
290
|
-
|
|
291
303
|
action.meta(:global) do
|
|
292
304
|
input do
|
|
293
|
-
custom :includes,
|
|
294
|
-
|
|
305
|
+
custom :includes,
|
|
306
|
+
label: HaveAPI.message('haveapi.parameters.active_record.includes.label'),
|
|
307
|
+
desc: HaveAPI.message('haveapi.parameters.active_record.includes.description'),
|
|
308
|
+
&clean
|
|
295
309
|
end
|
|
296
310
|
end
|
|
297
311
|
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
module HaveAPI::Parameters
|
|
2
|
+
module MetadataI18n
|
|
3
|
+
def metadata_i18n_catalog_items(context, i18n_path)
|
|
4
|
+
%i[label description].filter_map do |kind|
|
|
5
|
+
fallback = metadata_i18n_fallback(kind)
|
|
6
|
+
keys = metadata_i18n_keys(context, i18n_path, kind, fallback)
|
|
7
|
+
value = HaveAPI.localize(fallback).to_s.strip
|
|
8
|
+
|
|
9
|
+
next if keys.empty? || value.empty?
|
|
10
|
+
|
|
11
|
+
{
|
|
12
|
+
param: HaveAPI::Params.i18n_segment(@name),
|
|
13
|
+
kind: metadata_i18n_key_suffix(kind),
|
|
14
|
+
keys:,
|
|
15
|
+
value:
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def localized_label(context, i18n_path)
|
|
23
|
+
localized_metadata(context, i18n_path, :label, @label)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def localized_description(context, i18n_path)
|
|
27
|
+
localized_metadata(context, i18n_path, :description, @desc)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def localized_metadata(context, i18n_path, kind, fallback)
|
|
31
|
+
keys = metadata_i18n_keys(context, i18n_path, kind, fallback)
|
|
32
|
+
default = HaveAPI.localize(fallback)
|
|
33
|
+
|
|
34
|
+
return default if keys.empty?
|
|
35
|
+
|
|
36
|
+
keys.each do |key|
|
|
37
|
+
value = ::I18n.t(key, default: nil)
|
|
38
|
+
return value unless value.nil?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
default
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def metadata_i18n_keys(context, i18n_path, kind, fallback)
|
|
45
|
+
explicit_key = explicit_metadata_i18n_key(kind)
|
|
46
|
+
|
|
47
|
+
return [normalize_metadata_i18n_key(context, explicit_key)] if explicit_key
|
|
48
|
+
return [] if fallback.is_a?(HaveAPI::LocalizedMessage)
|
|
49
|
+
return [] unless i18n_path
|
|
50
|
+
|
|
51
|
+
scope = parameter_i18n_scope(context)
|
|
52
|
+
return [] unless scope
|
|
53
|
+
|
|
54
|
+
exact_key = [
|
|
55
|
+
scope,
|
|
56
|
+
i18n_path,
|
|
57
|
+
HaveAPI::Params.i18n_segment(@name),
|
|
58
|
+
metadata_i18n_key_suffix(kind)
|
|
59
|
+
].join('.')
|
|
60
|
+
|
|
61
|
+
[exact_key, *metadata_i18n_default_keys(context, i18n_path, kind, scope)].uniq
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def metadata_i18n_fallback(kind)
|
|
65
|
+
case kind
|
|
66
|
+
when :label
|
|
67
|
+
@label
|
|
68
|
+
when :description
|
|
69
|
+
@desc
|
|
70
|
+
else
|
|
71
|
+
raise ArgumentError, "unsupported parameter metadata kind #{kind.inspect}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def explicit_metadata_i18n_key(kind)
|
|
76
|
+
case kind
|
|
77
|
+
when :label
|
|
78
|
+
@label_key
|
|
79
|
+
when :description
|
|
80
|
+
@desc_key
|
|
81
|
+
else
|
|
82
|
+
raise ArgumentError, "unsupported parameter metadata kind #{kind.inspect}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def normalize_metadata_i18n_key(context, key)
|
|
87
|
+
str = key.to_s
|
|
88
|
+
return str if str.include?('.')
|
|
89
|
+
|
|
90
|
+
scope = parameter_i18n_scope(context)
|
|
91
|
+
scope ? "#{scope}.#{str}" : str
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def parameter_i18n_scope(context)
|
|
95
|
+
return unless context.respond_to?(:server)
|
|
96
|
+
return unless context.server.respond_to?(:parameter_i18n_scope)
|
|
97
|
+
|
|
98
|
+
scope = context.server.parameter_i18n_scope
|
|
99
|
+
return if scope.nil? || scope == false
|
|
100
|
+
|
|
101
|
+
Array(scope).flat_map { |segment| segment.to_s.split('.') }
|
|
102
|
+
.map { |segment| HaveAPI::Params.i18n_segment(segment) }
|
|
103
|
+
.join('.')
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def metadata_i18n_key_suffix(kind)
|
|
107
|
+
kind == :description ? 'description' : kind.to_s
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def metadata_i18n_default_keys(context, i18n_path, kind, scope)
|
|
111
|
+
param = HaveAPI::Params.i18n_segment(@name)
|
|
112
|
+
suffix = metadata_i18n_key_suffix(kind)
|
|
113
|
+
resource_prefix = metadata_i18n_resource_prefix(context)
|
|
114
|
+
path = metadata_i18n_path_parts(i18n_path)
|
|
115
|
+
keys = []
|
|
116
|
+
|
|
117
|
+
if path[:meta_type]
|
|
118
|
+
if resource_prefix
|
|
119
|
+
keys << [
|
|
120
|
+
scope,
|
|
121
|
+
resource_prefix,
|
|
122
|
+
'meta',
|
|
123
|
+
path.fetch(:meta_type),
|
|
124
|
+
path.fetch(:direction),
|
|
125
|
+
param,
|
|
126
|
+
suffix
|
|
127
|
+
].join('.')
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
keys << [
|
|
131
|
+
scope,
|
|
132
|
+
'meta',
|
|
133
|
+
path.fetch(:meta_type),
|
|
134
|
+
path.fetch(:direction),
|
|
135
|
+
param,
|
|
136
|
+
suffix
|
|
137
|
+
].join('.')
|
|
138
|
+
elsif resource_prefix
|
|
139
|
+
keys << [
|
|
140
|
+
scope,
|
|
141
|
+
resource_prefix,
|
|
142
|
+
path.fetch(:direction),
|
|
143
|
+
param,
|
|
144
|
+
suffix
|
|
145
|
+
].join('.')
|
|
146
|
+
keys << [
|
|
147
|
+
scope,
|
|
148
|
+
resource_prefix,
|
|
149
|
+
'attributes',
|
|
150
|
+
param,
|
|
151
|
+
suffix
|
|
152
|
+
].join('.')
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
keys << [scope, 'attributes', param, suffix].join('.') unless path[:meta_type]
|
|
156
|
+
keys
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def metadata_i18n_resource_prefix(context)
|
|
160
|
+
return unless context.respond_to?(:resource_path) && context.resource_path
|
|
161
|
+
|
|
162
|
+
[
|
|
163
|
+
'resources',
|
|
164
|
+
*context.resource_path.map { |segment| HaveAPI::Params.i18n_segment(segment) }
|
|
165
|
+
].join('.')
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def metadata_i18n_path_parts(i18n_path)
|
|
169
|
+
parts = i18n_path.to_s.split('.')
|
|
170
|
+
|
|
171
|
+
return { direction: parts.last } unless parts[-3] == 'meta'
|
|
172
|
+
|
|
173
|
+
{
|
|
174
|
+
meta_type: parts[-2],
|
|
175
|
+
direction: parts[-1]
|
|
176
|
+
}
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
+
require_relative 'metadata_i18n'
|
|
2
|
+
|
|
1
3
|
module HaveAPI::Parameters
|
|
2
4
|
class Resource
|
|
5
|
+
include MetadataI18n
|
|
6
|
+
|
|
3
7
|
attr_reader :name, :resource, :label, :desc, :type, :value_id, :value_label,
|
|
4
8
|
:choices, :value_params
|
|
5
9
|
|
|
6
10
|
def initialize(resource, name: nil, label: nil, desc: nil,
|
|
7
11
|
choices: nil, value_id: :id, value_label: :label, required: nil,
|
|
8
|
-
db_name: nil, fetch: nil, nullable: nil
|
|
12
|
+
db_name: nil, fetch: nil, nullable: nil, label_key: nil,
|
|
13
|
+
desc_key: nil)
|
|
9
14
|
@resource = resource
|
|
10
15
|
@resource_path = build_resource_path(resource)
|
|
11
16
|
@name = name || resource.resource_name.underscore.to_sym
|
|
12
17
|
@label = label || (name && name.to_s.capitalize) || resource.resource_name
|
|
13
18
|
@desc = desc
|
|
19
|
+
@label_key = label_key
|
|
20
|
+
@desc_key = desc_key
|
|
14
21
|
@choices = choices || @resource::Index
|
|
15
22
|
@value_id = value_id
|
|
16
23
|
@value_label = value_label
|
|
@@ -46,7 +53,7 @@ module HaveAPI::Parameters
|
|
|
46
53
|
@resource::Index
|
|
47
54
|
end
|
|
48
55
|
|
|
49
|
-
def describe(context)
|
|
56
|
+
def describe(context, i18n_path: nil)
|
|
50
57
|
val_path = context.path_for(
|
|
51
58
|
@resource::Show,
|
|
52
59
|
context.endpoint && context.action_prepare && context.layout == :object && context.call_path_params(context.action, context.action_prepare)
|
|
@@ -62,8 +69,8 @@ module HaveAPI::Parameters
|
|
|
62
69
|
{
|
|
63
70
|
required: required?,
|
|
64
71
|
nullable: nullable?,
|
|
65
|
-
label:
|
|
66
|
-
description:
|
|
72
|
+
label: localized_label(context, i18n_path),
|
|
73
|
+
description: localized_description(context, i18n_path),
|
|
67
74
|
type: 'Resource',
|
|
68
75
|
resource: @resource_path,
|
|
69
76
|
value_id: @value_id,
|
|
@@ -101,7 +108,7 @@ module HaveAPI::Parameters
|
|
|
101
108
|
if raw.nil?
|
|
102
109
|
return nil if nullable?
|
|
103
110
|
|
|
104
|
-
raise
|
|
111
|
+
raise validation_error('haveapi.validation.cannot_be_null')
|
|
105
112
|
end
|
|
106
113
|
|
|
107
114
|
if raw.is_a?(String)
|
|
@@ -133,7 +140,7 @@ module HaveAPI::Parameters
|
|
|
133
140
|
def strip_string(value)
|
|
134
141
|
value.strip
|
|
135
142
|
rescue ArgumentError, Encoding::CompatibilityError
|
|
136
|
-
raise
|
|
143
|
+
raise validation_error('haveapi.validation.invalid_string_encoding')
|
|
137
144
|
end
|
|
138
145
|
|
|
139
146
|
def build_resource_path(r)
|
|
@@ -175,7 +182,11 @@ module HaveAPI::Parameters
|
|
|
175
182
|
action = show_action.new(context.request, context.version, path_params, {}, child_context)
|
|
176
183
|
return if action.authorized?(context.current_user)
|
|
177
184
|
|
|
178
|
-
raise
|
|
185
|
+
raise validation_error('haveapi.validation.resource_not_found')
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def validation_error(key, **values)
|
|
189
|
+
HaveAPI::ValidationError.new(HaveAPI.message(key, **values))
|
|
179
190
|
end
|
|
180
191
|
end
|
|
181
192
|
end
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
require 'date'
|
|
2
2
|
require 'time'
|
|
3
|
+
require_relative 'metadata_i18n'
|
|
3
4
|
|
|
4
5
|
module HaveAPI::Parameters
|
|
5
6
|
class Typed
|
|
7
|
+
include MetadataI18n
|
|
8
|
+
|
|
6
9
|
ATTRIBUTES = %i[
|
|
7
10
|
label desc type db_name default fill clean protected load_validators
|
|
8
|
-
nullable symbolize_keys
|
|
11
|
+
nullable symbolize_keys label_key desc_key
|
|
9
12
|
].freeze
|
|
10
13
|
|
|
11
14
|
attr_reader :name, :label, :desc, :type, :default
|
|
@@ -52,12 +55,12 @@ module HaveAPI::Parameters
|
|
|
52
55
|
@load_validators.nil? || @load_validators
|
|
53
56
|
end
|
|
54
57
|
|
|
55
|
-
def describe(context)
|
|
58
|
+
def describe(context, i18n_path: nil)
|
|
56
59
|
{
|
|
57
60
|
required: required?,
|
|
58
61
|
nullable: nullable?,
|
|
59
|
-
label:
|
|
60
|
-
description:
|
|
62
|
+
label: localized_label(context, i18n_path),
|
|
63
|
+
description: localized_description(context, i18n_path),
|
|
61
64
|
type: @type ? @type.to_s : String.to_s,
|
|
62
65
|
validators: @validators ? @validators.describe : {},
|
|
63
66
|
default: @default,
|
|
@@ -88,7 +91,7 @@ module HaveAPI::Parameters
|
|
|
88
91
|
if raw.nil?
|
|
89
92
|
return nil if nullable?
|
|
90
93
|
|
|
91
|
-
raise
|
|
94
|
+
raise validation_error('haveapi.validation.cannot_be_null')
|
|
92
95
|
end
|
|
93
96
|
|
|
94
97
|
if raw.is_a?(String)
|
|
@@ -154,7 +157,7 @@ module HaveAPI::Parameters
|
|
|
154
157
|
|
|
155
158
|
def validate_cleaned_value(value)
|
|
156
159
|
if value.nil? && !nullable?
|
|
157
|
-
raise
|
|
160
|
+
raise validation_error('haveapi.validation.cannot_be_null')
|
|
158
161
|
end
|
|
159
162
|
|
|
160
163
|
value
|
|
@@ -188,7 +191,7 @@ module HaveAPI::Parameters
|
|
|
188
191
|
def strip_string(value)
|
|
189
192
|
value.strip
|
|
190
193
|
rescue ArgumentError, Encoding::CompatibilityError
|
|
191
|
-
raise
|
|
194
|
+
raise validation_error('haveapi.validation.invalid_string_encoding')
|
|
192
195
|
end
|
|
193
196
|
|
|
194
197
|
def coerce_integer(raw)
|
|
@@ -197,7 +200,7 @@ module HaveAPI::Parameters
|
|
|
197
200
|
raw
|
|
198
201
|
when Float
|
|
199
202
|
unless raw.finite? && (raw % 1) == 0
|
|
200
|
-
raise
|
|
203
|
+
raise validation_error('haveapi.types.invalid_integer', value: raw.inspect)
|
|
201
204
|
end
|
|
202
205
|
|
|
203
206
|
raw.to_i
|
|
@@ -205,12 +208,12 @@ module HaveAPI::Parameters
|
|
|
205
208
|
s = strip_string(raw)
|
|
206
209
|
|
|
207
210
|
if s.empty? || !s.match?(/\A[+-]?\d+\z/)
|
|
208
|
-
raise
|
|
211
|
+
raise validation_error('haveapi.types.invalid_integer', value: raw.inspect)
|
|
209
212
|
end
|
|
210
213
|
|
|
211
214
|
Integer(s, 10)
|
|
212
215
|
else
|
|
213
|
-
raise
|
|
216
|
+
raise validation_error('haveapi.types.invalid_integer', value: raw.inspect)
|
|
214
217
|
end
|
|
215
218
|
end
|
|
216
219
|
|
|
@@ -220,19 +223,19 @@ module HaveAPI::Parameters
|
|
|
220
223
|
|
|
221
224
|
elsif raw.is_a?(String)
|
|
222
225
|
s = strip_string(raw)
|
|
223
|
-
raise
|
|
226
|
+
raise validation_error('haveapi.types.invalid_float', value: raw.inspect) if s.empty?
|
|
224
227
|
|
|
225
228
|
begin
|
|
226
229
|
f = Float(s)
|
|
227
230
|
rescue ArgumentError
|
|
228
|
-
raise
|
|
231
|
+
raise validation_error('haveapi.types.invalid_float', value: raw.inspect)
|
|
229
232
|
end
|
|
230
233
|
|
|
231
234
|
else
|
|
232
|
-
raise
|
|
235
|
+
raise validation_error('haveapi.types.invalid_float', value: raw.inspect)
|
|
233
236
|
end
|
|
234
237
|
|
|
235
|
-
raise
|
|
238
|
+
raise validation_error('haveapi.types.invalid_float', value: raw.inspect) unless f.finite?
|
|
236
239
|
|
|
237
240
|
f
|
|
238
241
|
end
|
|
@@ -247,35 +250,39 @@ module HaveAPI::Parameters
|
|
|
247
250
|
|
|
248
251
|
elsif raw.is_a?(String)
|
|
249
252
|
s = strip_string(raw)
|
|
250
|
-
raise
|
|
253
|
+
raise validation_error('haveapi.types.invalid_boolean', value: raw.inspect) if s.empty?
|
|
251
254
|
|
|
252
255
|
return true if %w[true t yes y 1].include?(s.downcase)
|
|
253
256
|
return false if %w[false f no n 0].include?(s.downcase)
|
|
254
257
|
end
|
|
255
258
|
|
|
256
|
-
raise
|
|
259
|
+
raise validation_error('haveapi.types.invalid_boolean', value: raw.inspect)
|
|
257
260
|
end
|
|
258
261
|
|
|
259
262
|
def coerce_datetime(raw)
|
|
260
263
|
unless raw.is_a?(String)
|
|
261
|
-
raise
|
|
264
|
+
raise validation_error('haveapi.types.invalid_datetime', value: raw)
|
|
262
265
|
end
|
|
263
266
|
|
|
264
267
|
if strip_string(raw).empty?
|
|
265
|
-
raise
|
|
268
|
+
raise validation_error('haveapi.types.invalid_datetime', value: raw)
|
|
266
269
|
end
|
|
267
270
|
|
|
268
271
|
DateTime.iso8601(raw).to_time
|
|
269
272
|
rescue ArgumentError, TypeError
|
|
270
|
-
raise
|
|
273
|
+
raise validation_error('haveapi.types.invalid_datetime', value: raw)
|
|
271
274
|
end
|
|
272
275
|
|
|
273
276
|
def coerce_string(raw)
|
|
274
277
|
if raw.is_a?(Array) || raw.is_a?(Hash)
|
|
275
|
-
raise
|
|
278
|
+
raise validation_error('haveapi.types.invalid_string', value: raw.inspect)
|
|
276
279
|
end
|
|
277
280
|
|
|
278
281
|
raw.to_s
|
|
279
282
|
end
|
|
283
|
+
|
|
284
|
+
def validation_error(key, **values)
|
|
285
|
+
HaveAPI::ValidationError.new(HaveAPI.message(key, **values))
|
|
286
|
+
end
|
|
280
287
|
end
|
|
281
288
|
end
|