haveapi 0.28.4 → 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 (42) 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/i18n.rb +125 -0
  13. data/lib/haveapi/locales/cs.yml +167 -0
  14. data/lib/haveapi/locales/en.yml +168 -0
  15. data/lib/haveapi/metadata.rb +25 -3
  16. data/lib/haveapi/model_adapters/active_record.rb +40 -26
  17. data/lib/haveapi/output_formatter.rb +2 -2
  18. data/lib/haveapi/parameters/metadata_i18n.rb +179 -0
  19. data/lib/haveapi/parameters/resource.rb +18 -7
  20. data/lib/haveapi/parameters/typed.rb +27 -20
  21. data/lib/haveapi/params.rb +76 -7
  22. data/lib/haveapi/resource.rb +1 -1
  23. data/lib/haveapi/resources/action_state.rb +47 -27
  24. data/lib/haveapi/server.rb +156 -16
  25. data/lib/haveapi/spec/api_builder.rb +25 -0
  26. data/lib/haveapi/spec/spec_methods.rb +10 -0
  27. data/lib/haveapi/tasks/i18n.rb +198 -0
  28. data/lib/haveapi/validator_chain.rb +1 -1
  29. data/lib/haveapi/validators/acceptance.rb +5 -2
  30. data/lib/haveapi/validators/confirmation.rb +5 -2
  31. data/lib/haveapi/validators/exclusion.rb +2 -2
  32. data/lib/haveapi/validators/format.rb +5 -2
  33. data/lib/haveapi/validators/inclusion.rb +2 -2
  34. data/lib/haveapi/validators/length.rb +5 -5
  35. data/lib/haveapi/validators/numericality.rb +20 -22
  36. data/lib/haveapi/validators/presence.rb +4 -2
  37. data/lib/haveapi/version.rb +1 -1
  38. data/lib/haveapi.rb +1 -0
  39. data/spec/authentication/oauth2_spec.rb +10 -0
  40. data/spec/i18n_spec.rb +520 -0
  41. data/spec/params_spec.rb +183 -0
  42. metadata +29 -3
@@ -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
@@ -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(context),
51
- output: @output && @output.describe(context, metadata: true)
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
- "limit has to be maximally #{HaveAPI::Actions::Paginable::MAX_LIMIT}",
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 HaveAPI::ValidationError, "not a valid id #{original.inspect}"
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 HaveAPI::ValidationError, "not a valid id #{original.inspect}"
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 HaveAPI::ValidationError, "not a valid id #{original.inspect}"
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 HaveAPI::ValidationError, "not a valid id #{original.inspect}" if id < 0
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 HaveAPI::ValidationError, 'resource not found'
213
+ raise resource_not_found
211
214
  end
212
215
 
213
216
  ret
214
217
  rescue ::ActiveRecord::RecordNotFound
215
- raise HaveAPI::ValidationError, 'resource not found'
218
+ raise resource_not_found
216
219
  rescue ArgumentError, TypeError
217
- raise HaveAPI::ValidationError, "not a valid id #{original.inspect}"
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 HaveAPI::ValidationError, "not a valid id #{original.inspect}"
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 HaveAPI::ValidationError, "not a valid id #{original.inspect}"
246
+ raise invalid_id(original)
244
247
  end
245
248
 
246
249
  Integer(s, 10)
247
250
  else
248
- raise HaveAPI::ValidationError, "not a valid id #{original.inspect}"
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, label: 'URL parameters',
258
- desc: 'An array of parameters needed to resolve URL to this object'
259
- bool :resolved, label: 'Resolved', desc: 'True if the association is resolved'
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, 'includes must be a string or array'
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, 'includes must contain only strings'
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, label: 'Included associations',
294
- desc:, &clean
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
 
@@ -51,8 +51,8 @@ module HaveAPI
51
51
  ret.update({
52
52
  status:,
53
53
  response:,
54
- message:,
55
- errors:
54
+ message: HaveAPI.localize(message),
55
+ errors: HaveAPI.localize(errors)
56
56
  })
57
57
  ret
58
58
  end