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
@@ -3,11 +3,22 @@ module HaveAPI
3
3
  end
4
4
 
5
5
  class ValidationError < StandardError
6
+ attr_reader :message_value
7
+
6
8
  def initialize(msg, errors = {})
9
+ @message_value = msg
7
10
  super(msg)
8
11
  @errors = errors
9
12
  end
10
13
 
14
+ def message
15
+ HaveAPI.localize(@message_value)
16
+ end
17
+
18
+ def to_s
19
+ message
20
+ end
21
+
11
22
  def to_hash
12
23
  @errors
13
24
  end
@@ -17,6 +28,45 @@ module HaveAPI
17
28
  attr_reader :params
18
29
  attr_accessor :action
19
30
 
31
+ class << self
32
+ def action_i18n_path(context, direction)
33
+ prefix = action_i18n_prefix(context)
34
+ return unless prefix
35
+
36
+ "#{prefix}.#{i18n_segment(direction)}"
37
+ end
38
+
39
+ def metadata_i18n_path(context, type, direction)
40
+ prefix = action_i18n_prefix(context)
41
+ return unless prefix
42
+
43
+ [
44
+ prefix,
45
+ 'meta',
46
+ i18n_segment(type),
47
+ i18n_segment(direction)
48
+ ].join('.')
49
+ end
50
+
51
+ def i18n_segment(value)
52
+ value.to_s.underscore.downcase.gsub(/[^a-z0-9_]+/, '_').gsub(/\A_+|_+\z/, '')
53
+ end
54
+
55
+ private
56
+
57
+ def action_i18n_prefix(context)
58
+ return unless context.respond_to?(:resource_path) && context.resource_path
59
+ return unless context.respond_to?(:action) && context.action
60
+
61
+ [
62
+ 'resources',
63
+ *context.resource_path.map { |segment| i18n_segment(segment) },
64
+ 'actions',
65
+ i18n_segment(context.action.action_name)
66
+ ].join('.')
67
+ end
68
+ end
69
+
20
70
  def initialize(direction, action)
21
71
  @direction = direction
22
72
  @params = []
@@ -171,7 +221,7 @@ module HaveAPI
171
221
  add_param(name, apply(kwargs, type: Custom, clean: block, symbolize_keys:))
172
222
  end
173
223
 
174
- def describe(context, metadata: false)
224
+ def describe(context, metadata: false, i18n_path: nil)
175
225
  context.layout = layout
176
226
 
177
227
  ret = { parameters: {} }
@@ -179,8 +229,10 @@ module HaveAPI
179
229
  ret[:namespace] = namespace
180
230
  ret[:format] = @structure if @structure
181
231
 
232
+ i18n_path ||= self.class.action_i18n_path(context, @direction)
233
+
182
234
  @params.each do |p|
183
- ret[:parameters][p.name] = p.describe(context)
235
+ ret[:parameters][p.name] = p.describe(context, i18n_path:)
184
236
  end
185
237
 
186
238
  ret[:parameters] = filtered_description_parameters(context, ret, metadata)
@@ -188,6 +240,23 @@ module HaveAPI
188
240
  ret
189
241
  end
190
242
 
243
+ def parameter_metadata_i18n_items(context, i18n_path: nil, meta_type: nil)
244
+ i18n_path ||= self.class.action_i18n_path(context, @direction)
245
+
246
+ @params.flat_map do |param|
247
+ next [] unless param.respond_to?(:metadata_i18n_catalog_items)
248
+
249
+ param.metadata_i18n_catalog_items(context, i18n_path).map do |item|
250
+ item.merge(
251
+ resource_path: Array(context.resource_path).map { |v| self.class.i18n_segment(v) },
252
+ action: self.class.i18n_segment(context.action.action_name),
253
+ direction: self.class.i18n_segment(@direction),
254
+ meta_type: meta_type && self.class.i18n_segment(meta_type)
255
+ )
256
+ end
257
+ end
258
+ end
259
+
191
260
  def validate_build
192
261
  m = :"validate_build_#{@direction}"
193
262
 
@@ -202,10 +271,10 @@ module HaveAPI
202
271
  value = namespace ? params[namespace] : params
203
272
 
204
273
  if value.nil?
205
- raise ValidationError.new('invalid input layout', {}) if any_required_params?
274
+ raise ValidationError.new(HaveAPI.message('haveapi.validation.invalid_input_layout'), {}) if any_required_params?
206
275
 
207
276
  elsif !valid_layout?(value)
208
- raise ValidationError.new('invalid input layout', {})
277
+ raise ValidationError.new(HaveAPI.message('haveapi.validation.invalid_input_layout'), {})
209
278
  end
210
279
 
211
280
  return unless namespace
@@ -231,7 +300,7 @@ module HaveAPI
231
300
  next if permitted && !permitted.include?(p.name)
232
301
 
233
302
  if p.required? && input[p.name].nil?
234
- errors[p.name] = ['required parameter missing']
303
+ errors[p.name] = [HaveAPI.message('haveapi.validation.required_parameter_missing')]
235
304
  next
236
305
  end
237
306
 
@@ -248,7 +317,7 @@ module HaveAPI
248
317
  end
249
318
  rescue ValidationError => e
250
319
  errors[p.name] ||= []
251
- errors[p.name] << e.message
320
+ errors[p.name] << e.message_value
252
321
  next
253
322
  end
254
323
 
@@ -271,7 +340,7 @@ module HaveAPI
271
340
  end
272
341
 
273
342
  unless errors.empty?
274
- raise ValidationError.new('input parameters not valid', errors)
343
+ raise ValidationError.new(HaveAPI.message('haveapi.validation.input_parameters_not_valid'), errors)
275
344
  end
276
345
 
277
346
  params
@@ -80,7 +80,7 @@ module HaveAPI
80
80
  end
81
81
 
82
82
  def self.describe(hash, context)
83
- ret = { description: desc, actions: {}, resources: {} }
83
+ ret = { description: HaveAPI.localize(desc), actions: {}, resources: {} }
84
84
 
85
85
  context.resource = self
86
86
 
@@ -8,19 +8,25 @@ module HaveAPI::Resources
8
8
 
9
9
  params(:all) do
10
10
  id :id
11
- string :label, label: 'Label'
12
- bool :finished, label: 'Finished'
13
- bool :status, label: 'Status',
14
- desc: 'Determines whether the action is proceeding or failing'
15
- integer :current, label: 'Current progress'
16
- integer :total, label: 'Total',
17
- desc: 'The action is finished when current equals to total'
18
- string :unit, label: 'Unit', desc: 'Unit of current and total'
19
- bool :can_cancel, label: 'Can cancel',
20
- desc: 'When true, execution of this action can be cancelled'
21
- datetime :created_at, label: 'Created at'
22
- datetime :updated_at, label: 'Updated at',
23
- desc: 'When was the progress last updated'
11
+ string :label, label: HaveAPI.message('haveapi.parameters.action_state.label.label')
12
+ bool :finished, label: HaveAPI.message('haveapi.parameters.action_state.finished.label')
13
+ bool :status,
14
+ label: HaveAPI.message('haveapi.parameters.action_state.status.label'),
15
+ desc: HaveAPI.message('haveapi.parameters.action_state.status.description')
16
+ integer :current, label: HaveAPI.message('haveapi.parameters.action_state.current.label')
17
+ integer :total,
18
+ label: HaveAPI.message('haveapi.parameters.action_state.total.label'),
19
+ desc: HaveAPI.message('haveapi.parameters.action_state.total.description')
20
+ string :unit,
21
+ label: HaveAPI.message('haveapi.parameters.action_state.unit.label'),
22
+ desc: HaveAPI.message('haveapi.parameters.action_state.unit.description')
23
+ bool :can_cancel,
24
+ label: HaveAPI.message('haveapi.parameters.action_state.can_cancel.label'),
25
+ desc: HaveAPI.message('haveapi.parameters.action_state.can_cancel.description')
26
+ datetime :created_at, label: HaveAPI.message('haveapi.parameters.action_state.created_at.label')
27
+ datetime :updated_at,
28
+ label: HaveAPI.message('haveapi.parameters.action_state.updated_at.label'),
29
+ desc: HaveAPI.message('haveapi.parameters.action_state.updated_at.description')
24
30
  end
25
31
 
26
32
  module Mixin
@@ -84,15 +90,25 @@ module HaveAPI::Resources
84
90
  route '{%{resource}_id}/poll'
85
91
 
86
92
  input(:hash) do
87
- float :timeout, label: 'Timeout', desc: 'in seconds', default: 15, fill: true,
88
- number: { min: 0, max: MAX_TIMEOUT }
89
- float :update_in, label: 'Progress',
90
- desc: 'number of seconds after which the state is returned if the progress ' \
91
- 'has changed',
92
- nullable: true
93
- bool :status, desc: 'status to check with if update_in is set', nullable: true
94
- integer :current, desc: 'progress to check with if update_in is set', nullable: true
95
- integer :total, desc: 'progress to check with if update_in is set', nullable: true
93
+ float :timeout,
94
+ label: HaveAPI.message('haveapi.parameters.action_state.poll.timeout.label'),
95
+ desc: HaveAPI.message('haveapi.parameters.action_state.poll.timeout.description'),
96
+ default: 15,
97
+ fill: true,
98
+ number: { min: 0, max: MAX_TIMEOUT }
99
+ float :update_in,
100
+ label: HaveAPI.message('haveapi.parameters.action_state.poll.update_in.label'),
101
+ desc: HaveAPI.message('haveapi.parameters.action_state.poll.update_in.description'),
102
+ nullable: true
103
+ bool :status,
104
+ desc: HaveAPI.message('haveapi.parameters.action_state.poll.status.description'),
105
+ nullable: true
106
+ integer :current,
107
+ desc: HaveAPI.message('haveapi.parameters.action_state.poll.current.description'),
108
+ nullable: true
109
+ integer :total,
110
+ desc: HaveAPI.message('haveapi.parameters.action_state.poll.total.description'),
111
+ nullable: true
96
112
  end
97
113
 
98
114
  output(:hash) do
@@ -103,7 +119,11 @@ module HaveAPI::Resources
103
119
 
104
120
  def exec
105
121
  if input[:timeout] > MAX_TIMEOUT
106
- error!("timeout has to be maximally #{MAX_TIMEOUT}", {}, http_status: 400)
122
+ error!(
123
+ HaveAPI.message('haveapi.action_state.timeout_max', max: MAX_TIMEOUT),
124
+ {},
125
+ http_status: 400
126
+ )
107
127
  end
108
128
 
109
129
  t = Time.now
@@ -114,7 +134,7 @@ module HaveAPI::Resources
114
134
  id: path_params['action_state_id']
115
135
  )
116
136
 
117
- error!('action state not found') unless state.valid?
137
+ error!(HaveAPI.message('haveapi.action_state.not_found')) unless state.valid?
118
138
 
119
139
  if state.finished? || (Time.now - t) >= input[:timeout]
120
140
  return state_to_hash(state)
@@ -153,7 +173,7 @@ module HaveAPI::Resources
153
173
 
154
174
  return state_to_hash(state) if state.valid?
155
175
 
156
- error!('action state not found')
176
+ error!(HaveAPI.message('haveapi.action_state.not_found'))
157
177
  end
158
178
  end
159
179
 
@@ -172,7 +192,7 @@ module HaveAPI::Resources
172
192
  id: path_params['action_state_id']
173
193
  )
174
194
 
175
- error!('action state not found') unless state.valid?
195
+ error!(HaveAPI.message('haveapi.action_state.not_found')) unless state.valid?
176
196
 
177
197
  ret = state.cancel
178
198
 
@@ -183,7 +203,7 @@ module HaveAPI::Resources
183
203
  ok!
184
204
 
185
205
  else
186
- error!('cancellation failed')
206
+ error!(HaveAPI.message('haveapi.action_state.cancellation_failed'))
187
207
  end
188
208
  rescue RuntimeError, NotImplementedError => e
189
209
  error!(e.message)