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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +139 -0
  3. data/Rakefile +1 -0
  4. data/haveapi.gemspec +2 -1
  5. data/lib/haveapi/action.rb +26 -9
  6. data/lib/haveapi/actions/default.rb +5 -2
  7. data/lib/haveapi/actions/paginable.rb +8 -4
  8. data/lib/haveapi/authentication/oauth2/provider.rb +1 -1
  9. data/lib/haveapi/authentication/token/config.rb +10 -3
  10. data/lib/haveapi/authentication/token/provider.rb +22 -21
  11. data/lib/haveapi/context.rb +4 -1
  12. data/lib/haveapi/extensions/exception_mailer.rb +136 -17
  13. data/lib/haveapi/i18n.rb +125 -0
  14. data/lib/haveapi/locales/cs.yml +167 -0
  15. data/lib/haveapi/locales/en.yml +168 -0
  16. data/lib/haveapi/metadata.rb +25 -3
  17. data/lib/haveapi/model_adapters/active_record.rb +40 -26
  18. data/lib/haveapi/output_formatter.rb +2 -2
  19. data/lib/haveapi/parameters/metadata_i18n.rb +179 -0
  20. data/lib/haveapi/parameters/resource.rb +18 -7
  21. data/lib/haveapi/parameters/typed.rb +27 -20
  22. data/lib/haveapi/params.rb +76 -7
  23. data/lib/haveapi/resource.rb +1 -1
  24. data/lib/haveapi/resources/action_state.rb +47 -27
  25. data/lib/haveapi/server.rb +287 -94
  26. data/lib/haveapi/spec/api_builder.rb +25 -0
  27. data/lib/haveapi/spec/spec_methods.rb +10 -0
  28. data/lib/haveapi/tasks/i18n.rb +198 -0
  29. data/lib/haveapi/validator_chain.rb +1 -1
  30. data/lib/haveapi/validators/acceptance.rb +5 -2
  31. data/lib/haveapi/validators/confirmation.rb +5 -2
  32. data/lib/haveapi/validators/exclusion.rb +2 -2
  33. data/lib/haveapi/validators/format.rb +5 -2
  34. data/lib/haveapi/validators/inclusion.rb +2 -2
  35. data/lib/haveapi/validators/length.rb +5 -5
  36. data/lib/haveapi/validators/numericality.rb +20 -22
  37. data/lib/haveapi/validators/presence.rb +4 -2
  38. data/lib/haveapi/version.rb +1 -1
  39. data/lib/haveapi.rb +1 -0
  40. data/spec/authentication/oauth2_spec.rb +10 -0
  41. data/spec/extensions/exception_mailer_spec.rb +195 -0
  42. data/spec/i18n_spec.rb +520 -0
  43. data/spec/params_spec.rb +183 -0
  44. data/spec/server/integration_spec.rb +34 -0
  45. metadata +30 -7
  46. data/doc/create-client.md +0 -107
  47. data/doc/json-schema.html +0 -1182
  48. data/doc/protocol.md +0 -535
  49. data/doc/protocol.png +0 -0
@@ -11,6 +11,21 @@ module HaveAPI::Extensions
11
11
  # is added. Some helper methods are taken either from Sinatra or Rack.
12
12
  class ExceptionMailer < Base
13
13
  Frame = Struct.new(:filename, :lineno, :function, :context_line)
14
+ FILTERED_VALUE = '[FILTERED]'.freeze
15
+ SENSITIVE_KEY_PATTERN = /
16
+ authorization|cookie|password|passwd|passphrase|secret|token|
17
+ api[_-]?key|credential|jwt|session|csrf|query_string|form_vars|
18
+ request_uri|original_fullpath|fullpath
19
+ /ix
20
+ SENSITIVE_STRING_PATTERN = /
21
+ (
22
+ (?:authorization|cookie|password|passwd|passphrase|secret|token|
23
+ api[_-]?key|credential|jwt|session|csrf)
24
+ [^=:\s&;<>]{0,64}
25
+ \s*(?:=|:|=>)\s*["']?
26
+ )
27
+ [^&;\s<"'}]+
28
+ /ix
14
29
 
15
30
  # @param opts [Hash] options
16
31
  # @option opts to [String] recipient address
@@ -24,21 +39,33 @@ module HaveAPI::Extensions
24
39
 
25
40
  def enabled(server)
26
41
  HaveAPI::Action.connect_hook(:exec_exception) do |ret, context, e|
27
- log(context, e)
42
+ safe_log(context, e)
28
43
  ret
29
44
  end
30
45
 
31
46
  server.connect_hook(:description_exception) do |ret, context, e|
32
- log(context, e)
47
+ safe_log(context, e)
33
48
  ret
34
49
  end
50
+
51
+ server.connect_hook(:request_exception) do |ret, context, e|
52
+ safe_log(context, e)
53
+ ret
54
+ end
55
+ end
56
+
57
+ def safe_log(context, exception)
58
+ log(context, exception)
59
+ rescue StandardError => e
60
+ warn "HaveAPI::Extensions::ExceptionMailer failed: #{e.class}: #{e.message}"
35
61
  end
36
62
 
37
63
  def log(context, exception)
38
- req = context.request.request
39
- path = (req.script_name + req.path_info).squeeze('/')
64
+ request_context = context&.request
65
+ req = request_context.respond_to?(:request) ? request_context.request : request_context
66
+ path = request_path(context, req)
40
67
 
41
- frames = exception.backtrace.map do |line|
68
+ frames = Array(exception.backtrace).map do |line|
42
69
  frame = Frame.new
43
70
 
44
71
  next unless line =~ /(.*?):(\d+)(:in `(.*)')?/
@@ -57,14 +84,21 @@ module HaveAPI::Extensions
57
84
 
58
85
  frame
59
86
  end.compact
87
+ frames = [Frame.new('(unknown)', 0, nil, nil)] if frames.empty?
60
88
 
61
- env = context.request.env
89
+ args = redact(context&.args)
90
+ path_params = redact(context&.path_params)
91
+ input = redact(context&.input)
92
+ get = request_params(req, :GET)
93
+ post = request_params(req, :POST)
94
+ cookies = request_cookies(req)
95
+ env = redact(request_env(request_context, req))
62
96
 
63
97
  user =
64
- if context.current_user.respond_to?(:id)
98
+ if context&.current_user.respond_to?(:id)
65
99
  context.current_user.id
66
100
  else
67
- context.current_user
101
+ context&.current_user
68
102
  end
69
103
 
70
104
  mail(context, exception, TEMPLATE.result(binding))
@@ -114,6 +148,91 @@ module HaveAPI::Extensions
114
148
  end
115
149
  end
116
150
 
151
+ def request_path(context, req)
152
+ if req.respond_to?(:script_name) && req.respond_to?(:path_info)
153
+ return filter_query_string((req.script_name + req.path_info).squeeze('/'))
154
+ end
155
+
156
+ filter_query_string(
157
+ if context.respond_to?(:resolved_path)
158
+ context.resolved_path
159
+ elsif context.respond_to?(:path)
160
+ context.path
161
+ else
162
+ '(unknown)'
163
+ end
164
+ )
165
+ end
166
+
167
+ def request_env(request_context, req)
168
+ if request_context.respond_to?(:env)
169
+ request_context.env
170
+ elsif req.respond_to?(:env)
171
+ req.env
172
+ else
173
+ {}
174
+ end
175
+ end
176
+
177
+ def request_params(req, method)
178
+ return {} unless req.respond_to?(method)
179
+
180
+ redact(req.public_send(method) || {})
181
+ rescue StandardError
182
+ {}
183
+ end
184
+
185
+ def request_cookies(req)
186
+ return {} unless req.respond_to?(:cookies)
187
+
188
+ cookies = req.cookies || {}
189
+
190
+ cookies.each_with_object({}) do |(key, _value), ret|
191
+ ret[key] = FILTERED_VALUE
192
+ end
193
+ rescue StandardError
194
+ {}
195
+ end
196
+
197
+ def filter_query_string(path)
198
+ return path unless path.respond_to?(:sub)
199
+
200
+ path.sub(/\?.*/, "?#{FILTERED_VALUE}")
201
+ end
202
+
203
+ def redact(value, key = nil, seen = nil)
204
+ return FILTERED_VALUE if sensitive_key?(key)
205
+
206
+ seen ||= {}.compare_by_identity
207
+
208
+ case value
209
+ when Hash
210
+ return value if seen.has_key?(value)
211
+
212
+ seen[value] = true
213
+ value.each_with_object({}) do |(inner_key, inner_value), ret|
214
+ ret[inner_key] = redact(inner_value, inner_key, seen)
215
+ end
216
+ when Array
217
+ return value if seen.has_key?(value)
218
+
219
+ seen[value] = true
220
+ value.map { |inner_value| redact(inner_value, nil, seen) }
221
+ when String
222
+ redact_string(value)
223
+ else
224
+ value
225
+ end
226
+ end
227
+
228
+ def sensitive_key?(key)
229
+ key && key.to_s.match?(SENSITIVE_KEY_PATTERN)
230
+ end
231
+
232
+ def redact_string(value)
233
+ value.gsub(SENSITIVE_STRING_PATTERN, "\\1#{FILTERED_VALUE}")
234
+ end
235
+
117
236
  TEMPLATE = ERB.new(<<~END
118
237
  <!DOCTYPE html>
119
238
  <html>
@@ -224,15 +343,15 @@ module HaveAPI::Extensions
224
343
  </tr>
225
344
  <tr>
226
345
  <th>Arguments</th>
227
- <td><%=h context.args %></td>
346
+ <td><%=h args %></td>
228
347
  </tr>
229
348
  <tr>
230
349
  <th>Path parameters</th>
231
- <td><%=h context.path_params %></td>
350
+ <td><%=h path_params %></td>
232
351
  </tr>
233
352
  <tr>
234
353
  <th>Input</th>
235
- <td><%=h context.input %></td>
354
+ <td><%=h input %></td>
236
355
  </tr>
237
356
  <tr>
238
357
  <th>User</th>
@@ -269,13 +388,13 @@ module HaveAPI::Extensions
269
388
  </div> <!-- /BACKTRACE -->
270
389
  <div id="get">
271
390
  <h3 id="get-info">GET</h3>
272
- <% if req.GET and not req.GET.empty? %>
391
+ <% if !get.empty? %>
273
392
  <table class="req">
274
393
  <tr>
275
394
  <th>Variable</th>
276
395
  <th>Value</th>
277
396
  </tr>
278
- <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %>
397
+ <% get.sort_by { |k, v| k.to_s }.each { |key, val| %>
279
398
  <tr>
280
399
  <td><%=h key %></td>
281
400
  <td class="code"><div><%=h val.inspect %></div></td>
@@ -289,13 +408,13 @@ module HaveAPI::Extensions
289
408
  </div> <!-- /GET -->
290
409
  <div id="post">
291
410
  <h3 id="post-info">POST</h3>
292
- <% if req.POST and not req.POST.empty? %>
411
+ <% if !post.empty? %>
293
412
  <table class="req">
294
413
  <tr>
295
414
  <th>Variable</th>
296
415
  <th>Value</th>
297
416
  </tr>
298
- <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %>
417
+ <% post.sort_by { |k, v| k.to_s }.each { |key, val| %>
299
418
  <tr>
300
419
  <td><%=h key %></td>
301
420
  <td class="code"><div><%=h val.inspect %></div></td>
@@ -309,13 +428,13 @@ module HaveAPI::Extensions
309
428
  </div> <!-- /POST -->
310
429
  <div id="cookies">
311
430
  <h3 id="cookie-info">COOKIES</h3>
312
- <% unless req.cookies.empty? %>
431
+ <% unless cookies.empty? %>
313
432
  <table class="req">
314
433
  <tr>
315
434
  <th>Variable</th>
316
435
  <th>Value</th>
317
436
  </tr>
318
- <% req.cookies.each { |key, val| %>
437
+ <% cookies.each { |key, val| %>
319
438
  <tr>
320
439
  <td><%=h key %></td>
321
440
  <td class="code"><div><%=h val.inspect %></div></td>
@@ -0,0 +1,125 @@
1
+ require 'i18n'
2
+
3
+ module HaveAPI
4
+ class LocalizedMessage
5
+ attr_reader :key, :values, :default
6
+
7
+ def initialize(key, default: nil, scope: nil, **values)
8
+ @key = normalize_key(key, scope)
9
+ @default = default
10
+ @values = values
11
+ end
12
+
13
+ def translate(extra_values = {})
14
+ values = localized_values(@values.merge(extra_values))
15
+ opts = values
16
+ opts[:default] = @default if @default
17
+
18
+ ::I18n.t(@key, **opts)
19
+ end
20
+
21
+ def to_s
22
+ translate
23
+ end
24
+
25
+ private
26
+
27
+ def normalize_key(key, scope)
28
+ return key.to_s unless scope
29
+
30
+ "#{scope}.#{key}"
31
+ end
32
+
33
+ def localized_values(values)
34
+ values.transform_values do |value|
35
+ case value
36
+ when Array
37
+ value.map { |v| HaveAPI.localize(v) }.join('; ')
38
+ else
39
+ HaveAPI.localize(value)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ module I18n
46
+ class << self
47
+ def setup
48
+ locale_dir = File.expand_path('locales', __dir__)
49
+ ::I18n.load_path |= Dir[File.join(locale_dir, '*.yml')]
50
+ end
51
+
52
+ def available_locales
53
+ locale_dir = File.expand_path('locales', __dir__)
54
+
55
+ Dir[File.join(locale_dir, '*.yml')].map do |path|
56
+ File.basename(path, '.yml').to_sym
57
+ end.sort
58
+ end
59
+
60
+ def accept_language(header, available_locales)
61
+ header.to_s.split(',').each_with_index.filter_map do |raw, index|
62
+ tag, *params = raw.strip.split(';')
63
+ next if tag.nil? || tag.empty?
64
+
65
+ q = params.map(&:strip).grep(/\Aq=/).first
66
+ weight = q ? q.split('=', 2).last.to_f : 1.0
67
+ next if weight <= 0.0
68
+
69
+ [tag, weight, index]
70
+ end.sort_by { |(_, weight, index)| [-weight, index] }.each do |tag, _, _|
71
+ locale = normalize_locale(tag, available_locales)
72
+ return locale if locale
73
+ end
74
+
75
+ nil
76
+ rescue StandardError
77
+ nil
78
+ end
79
+
80
+ def normalize_locale(locale, available_locales)
81
+ return if locale.nil?
82
+
83
+ tag = locale.to_s.strip.tr('_', '-')
84
+ return if tag.empty? || tag == '*'
85
+
86
+ available = Array(available_locales).map(&:to_s)
87
+ candidates = [tag, tag.split('-').first].compact.map(&:downcase).uniq
88
+
89
+ candidates.each do |candidate|
90
+ match = available.detect { |v| v.downcase == candidate }
91
+ return match.to_sym if match
92
+ end
93
+
94
+ nil
95
+ end
96
+ end
97
+ end
98
+
99
+ class << self
100
+ def message(key, default: nil, scope: nil, **values)
101
+ LocalizedMessage.new(key, default:, scope:, **values)
102
+ end
103
+
104
+ def t(key, default: nil, scope: nil, **values)
105
+ message(key, default:, scope:, **values).translate
106
+ end
107
+
108
+ def localize(value, **extra_values)
109
+ case value
110
+ when LocalizedMessage
111
+ value.translate(extra_values)
112
+ when Array
113
+ value.map { |v| localize(v, **extra_values) }
114
+ when Hash
115
+ value.transform_values { |v| localize(v, **extra_values) }
116
+ when String
117
+ extra_values.empty? ? value : format(value, extra_values)
118
+ else
119
+ value
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ HaveAPI::I18n.setup
@@ -0,0 +1,167 @@
1
+ # This file is generated from i18n/haveapi.yml.
2
+ # Do not edit it manually; manual changes will be overwritten.
3
+ # Update i18n/haveapi.yml and run bundle exec rake i18n:update.
4
+ ---
5
+ cs:
6
+ haveapi:
7
+ action_state:
8
+ cancellation_failed: zrušení selhalo
9
+ not_found: stav akce nebyl nalezen
10
+ timeout_max: timeout může být nejvýše %{max}
11
+ authentication:
12
+ failed: přihlášení selhalo
13
+ invalid_credentials: neplatné přihlašovací údaje
14
+ multiple_oauth2_tokens: Bylo poskytnuto více OAuth2 tokenů
15
+ multiple_tokens: Bylo poskytnuto více ověřovacích tokenů
16
+ renew_failed: obnovení selhalo
17
+ required: Akce vyžaduje přihlášení uživatele
18
+ revoke_failed: zrušení selhalo
19
+ token_required: Akce vyžaduje přihlášení uživatele tokenem
20
+ authorization:
21
+ insufficient_permissions: Přístup odepřen. Nedostatečná oprávnění.
22
+ errors:
23
+ action_not_found: Akce nebyla nalezena
24
+ bad_accept_header: Chybná hlavička Accept
25
+ bad_json_syntax: Chybná syntaxe JSON
26
+ json_body_object: Tělo JSON musí být objekt
27
+ server_error: Došlo k chybě serveru
28
+ unsupported_content_type: Nepodporovaný Content-Type
29
+ pagination:
30
+ limit_max: limit může být nejvýše %{max}
31
+ parameters:
32
+ action_state:
33
+ can_cancel:
34
+ description: Je-li true, spuštění této akce lze zrušit
35
+ label: Lze zrušit
36
+ created_at:
37
+ label: Vytvořeno
38
+ current:
39
+ label: Aktuální průběh
40
+ finished:
41
+ label: Dokončeno
42
+ label:
43
+ label: Popisek
44
+ poll:
45
+ current:
46
+ description: průběh, se kterým se porovnává při nastaveném update_in
47
+ status:
48
+ description: stav, se kterým se porovnává při nastaveném update_in
49
+ timeout:
50
+ description: v sekundách
51
+ label: Časový limit
52
+ total:
53
+ description: průběh, se kterým se porovnává při nastaveném update_in
54
+ update_in:
55
+ description: počet sekund, po kterém se stav vrátí, pokud se průběh změnil
56
+ label: Průběh
57
+ status:
58
+ description: Určuje, zda akce probíhá nebo selhává
59
+ label: Stav
60
+ total:
61
+ description: Akce je dokončena, když se aktuální průběh rovná celkovému
62
+ label: Celkem
63
+ unit:
64
+ description: Jednotka aktuálního a celkového průběhu
65
+ label: Jednotka
66
+ updated_at:
67
+ description: Kdy byl průběh naposledy aktualizován
68
+ label: Aktualizováno
69
+ action_state_id:
70
+ description: ID objektu ActionState pro dotazování stavu. Pokud je null, akce
71
+ není pro aktuální volání blokující.
72
+ label: ID stavu akce
73
+ active_record:
74
+ includes:
75
+ description: |-
76
+ Seznam názvů asociovaných prostředků oddělených čárkou.
77
+ Vnořené asociace se zapisují pomocí '__' mezi názvy prostředků.
78
+ Například 'user,node' přeloží tyto dvě asociace.
79
+ Pro přeložení dalších asociací uzlu použijte např. 'user,node__location',
80
+ pro ještě hlubší úroveň např. 'user,node__location__environment'.
81
+ label: Zahrnuté asociace
82
+ path_params:
83
+ description: Pole parametrů potřebných k přeložení URL na tento objekt
84
+ label: Parametry URL
85
+ resolved:
86
+ description: True, pokud je asociace přeložena
87
+ label: Přeloženo
88
+ authentication:
89
+ token:
90
+ interval:
91
+ description: Jak dlouho bude požadovaný token platný, v sekundách.
92
+ label: Interval
93
+ lifetime:
94
+ description: |-
95
+ fixed - token má pevnou dobu platnosti a nelze ho obnovit
96
+ renewable_manual - token lze obnovit, ale musí se obnovit ručně akcí renew
97
+ renewable_auto - token se při každém použití automaticky obnoví na now+interval
98
+ permanent - token bude platný navždy, dokud nebude smazán
99
+ label: Životnost
100
+ password:
101
+ label: Heslo
102
+ scope:
103
+ label: Rozsah
104
+ user:
105
+ label: Uživatel
106
+ default:
107
+ count:
108
+ label: Vrátit počet všech položek
109
+ total_count:
110
+ label: Celkový počet všech položek
111
+ metadata:
112
+ 'no':
113
+ label: Zakázat metadata
114
+ paginable:
115
+ from_id:
116
+ description: Vypsat objekty s větším/menším ID
117
+ label: Od ID
118
+ limit:
119
+ description: Počet objektů k načtení
120
+ label: Limit
121
+ types:
122
+ invalid_boolean: neplatná pravdivostní hodnota %{value}
123
+ invalid_datetime: není ve formátu ISO 8601 '%{value}'
124
+ invalid_float: neplatné desetinné číslo %{value}
125
+ invalid_integer: neplatné celé číslo %{value}
126
+ invalid_string: neplatný řetězec %{value}
127
+ validation:
128
+ cannot_be_null: nesmí být null
129
+ includes_only_strings: includes musí obsahovat jen řetězce
130
+ includes_string_or_array: includes musí být řetězec nebo pole
131
+ input_parameters_not_valid: vstupní parametry nejsou platné
132
+ invalid_id: neplatné ID %{value}
133
+ invalid_input_layout: neplatná struktura vstupu
134
+ invalid_path_parameter_encoding: neplatné kódování parametru cesty
135
+ invalid_string_encoding: neplatné kódování řetězce
136
+ required_parameter_missing: povinný parametr chybí
137
+ resource_not_found: prostředek nebyl nalezen
138
+ validators:
139
+ acceptance:
140
+ accepted: musí být %{value}
141
+ confirmation:
142
+ different: musí být odlišné od %{parameter}
143
+ same: musí být stejné jako %{parameter}
144
+ exclusion:
145
+ excluded: "%{value} nelze použít"
146
+ format:
147
+ invalid: "%{value} nemá platný formát"
148
+ inclusion:
149
+ included: "%{value} nelze použít"
150
+ length:
151
+ equals: délka musí být %{equals}
152
+ max: délka může být nejvýše %{max}
153
+ min: délka musí být alespoň %{min}
154
+ range: délka musí být v rozsahu <%{min}, %{max}>
155
+ numericality:
156
+ composite: "%{requirements}"
157
+ even: sudé číslo
158
+ max: musí být nejvýše %{max}
159
+ min: musí být alespoň %{min}
160
+ mod: zbytek po dělení %{mod} musí být nula
161
+ number: musí být číslo
162
+ odd: liché číslo
163
+ range: musí být v rozsahu <%{min}, %{max}>
164
+ step: v krocích po %{step}
165
+ presence:
166
+ non_empty: musí být vyplněno a neprázdné
167
+ present: musí být vyplněno
@@ -0,0 +1,168 @@
1
+ # This file is generated from i18n/haveapi.yml.
2
+ # Do not edit it manually; manual changes will be overwritten.
3
+ # Update i18n/haveapi.yml and run bundle exec rake i18n:update.
4
+ ---
5
+ en:
6
+ haveapi:
7
+ action_state:
8
+ cancellation_failed: cancellation failed
9
+ not_found: action state not found
10
+ timeout_max: timeout has to be maximally %{max}
11
+ authentication:
12
+ failed: authentication failed
13
+ invalid_credentials: invalid authentication credentials
14
+ multiple_oauth2_tokens: Multiple OAuth2 tokens provided
15
+ multiple_tokens: Multiple authentication tokens provided
16
+ renew_failed: renew failed
17
+ required: Action requires user to authenticate
18
+ revoke_failed: revoke failed
19
+ token_required: Action requires user to authenticate with a token
20
+ authorization:
21
+ insufficient_permissions: Access denied. Insufficient permissions.
22
+ errors:
23
+ action_not_found: Action not found
24
+ bad_accept_header: Bad Accept header
25
+ bad_json_syntax: Bad JSON syntax
26
+ json_body_object: JSON body must be an object
27
+ server_error: Server error occurred
28
+ unsupported_content_type: Unsupported Content-Type
29
+ pagination:
30
+ limit_max: limit has to be maximally %{max}
31
+ parameters:
32
+ action_state:
33
+ can_cancel:
34
+ description: When true, execution of this action can be cancelled
35
+ label: Can cancel
36
+ created_at:
37
+ label: Created at
38
+ current:
39
+ label: Current progress
40
+ finished:
41
+ label: Finished
42
+ label:
43
+ label: Label
44
+ poll:
45
+ current:
46
+ description: progress to check with if update_in is set
47
+ status:
48
+ description: status to check with if update_in is set
49
+ timeout:
50
+ description: in seconds
51
+ label: Timeout
52
+ total:
53
+ description: progress to check with if update_in is set
54
+ update_in:
55
+ description: number of seconds after which the state is returned if the
56
+ progress has changed
57
+ label: Progress
58
+ status:
59
+ description: Determines whether the action is proceeding or failing
60
+ label: Status
61
+ total:
62
+ description: The action is finished when current equals to total
63
+ label: Total
64
+ unit:
65
+ description: Unit of current and total
66
+ label: Unit
67
+ updated_at:
68
+ description: When was the progress last updated
69
+ label: Updated at
70
+ action_state_id:
71
+ description: ID of ActionState object for state querying. When null, the action
72
+ is not blocking for the current invocation.
73
+ label: Action state ID
74
+ active_record:
75
+ includes:
76
+ description: |-
77
+ A list of names of associated resources separated by a comma.
78
+ Nested associations are declared with '__' between resource names.
79
+ For example, 'user,node' will resolve the two associations.
80
+ To resolve further associations of node, use e.g. 'user,node__location',
81
+ to go even deeper, use e.g. 'user,node__location__environment'.
82
+ label: Included associations
83
+ path_params:
84
+ description: An array of parameters needed to resolve URL to this object
85
+ label: URL parameters
86
+ resolved:
87
+ description: True if the association is resolved
88
+ label: Resolved
89
+ authentication:
90
+ token:
91
+ interval:
92
+ description: How long will requested token be valid, in seconds.
93
+ label: Interval
94
+ lifetime:
95
+ description: |-
96
+ fixed - the token has a fixed validity period, it cannot be renewed
97
+ renewable_manual - the token can be renewed, but it must be done manually via renew action
98
+ renewable_auto - the token is renewed automatically to now+interval every time it is used
99
+ permanent - the token will be valid forever, unless deleted
100
+ label: Lifetime
101
+ password:
102
+ label: Password
103
+ scope:
104
+ label: Scope
105
+ user:
106
+ label: User
107
+ default:
108
+ count:
109
+ label: Return the count of all items
110
+ total_count:
111
+ label: Total count of all items
112
+ metadata:
113
+ 'no':
114
+ label: Disable metadata
115
+ paginable:
116
+ from_id:
117
+ description: List objects with greater/lesser ID
118
+ label: From ID
119
+ limit:
120
+ description: Number of objects to retrieve
121
+ label: Limit
122
+ types:
123
+ invalid_boolean: not a valid boolean %{value}
124
+ invalid_datetime: not in ISO 8601 format '%{value}'
125
+ invalid_float: not a valid float %{value}
126
+ invalid_integer: not a valid integer %{value}
127
+ invalid_string: not a valid string %{value}
128
+ validation:
129
+ cannot_be_null: cannot be null
130
+ includes_only_strings: includes must contain only strings
131
+ includes_string_or_array: includes must be a string or array
132
+ input_parameters_not_valid: input parameters not valid
133
+ invalid_id: not a valid id %{value}
134
+ invalid_input_layout: invalid input layout
135
+ invalid_path_parameter_encoding: invalid path parameter encoding
136
+ invalid_string_encoding: invalid string encoding
137
+ required_parameter_missing: required parameter missing
138
+ resource_not_found: resource not found
139
+ validators:
140
+ acceptance:
141
+ accepted: has to be %{value}
142
+ confirmation:
143
+ different: must be different from %{parameter}
144
+ same: must be the same as %{parameter}
145
+ exclusion:
146
+ excluded: "%{value} cannot be used"
147
+ format:
148
+ invalid: "%{value} is not in a valid format"
149
+ inclusion:
150
+ included: "%{value} cannot be used"
151
+ length:
152
+ equals: length has to be %{equals}
153
+ max: length has to be maximally %{max}
154
+ min: length has to be minimally %{min}
155
+ range: length has to be in range <%{min}, %{max}>
156
+ numericality:
157
+ composite: "%{requirements}"
158
+ even: even
159
+ max: has to be maximally %{max}
160
+ min: has to be minimally %{min}
161
+ mod: mod %{mod} must equal zero
162
+ number: has to be a number
163
+ odd: odd
164
+ range: has to be in range <%{min}, %{max}>
165
+ step: in steps of %{step}
166
+ presence:
167
+ non_empty: must be present and non-empty
168
+ present: must be present