haveapi 0.27.3 → 0.28.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/haveapi.gemspec +1 -1
  4. data/lib/haveapi/action.rb +125 -36
  5. data/lib/haveapi/actions/paginable.rb +3 -1
  6. data/lib/haveapi/authentication/basic/provider.rb +2 -0
  7. data/lib/haveapi/authentication/chain.rb +11 -7
  8. data/lib/haveapi/authentication/oauth2/config.rb +25 -3
  9. data/lib/haveapi/authentication/oauth2/provider.rb +92 -11
  10. data/lib/haveapi/authentication/oauth2/revoke_endpoint.rb +44 -3
  11. data/lib/haveapi/authentication/token/provider.rb +53 -15
  12. data/lib/haveapi/authorization.rb +42 -18
  13. data/lib/haveapi/client_examples/php_client.rb +1 -1
  14. data/lib/haveapi/client_examples/ruby_client.rb +1 -1
  15. data/lib/haveapi/context.rb +10 -4
  16. data/lib/haveapi/example.rb +15 -16
  17. data/lib/haveapi/extensions/action_exceptions.rb +6 -6
  18. data/lib/haveapi/model_adapters/active_record.rb +140 -68
  19. data/lib/haveapi/model_adapters/hash.rb +1 -1
  20. data/lib/haveapi/parameters/resource.rb +35 -3
  21. data/lib/haveapi/parameters/typed.rb +26 -7
  22. data/lib/haveapi/params.rb +27 -8
  23. data/lib/haveapi/resource.rb +4 -1
  24. data/lib/haveapi/resources/action_state.rb +8 -1
  25. data/lib/haveapi/route.rb +2 -2
  26. data/lib/haveapi/server.rb +137 -45
  27. data/lib/haveapi/validator.rb +2 -2
  28. data/lib/haveapi/validator_chain.rb +1 -0
  29. data/lib/haveapi/validators/confirmation.rb +1 -0
  30. data/lib/haveapi/validators/format.rb +6 -2
  31. data/lib/haveapi/validators/length.rb +2 -0
  32. data/lib/haveapi/validators/numericality.rb +2 -0
  33. data/lib/haveapi/validators/presence.rb +1 -1
  34. data/lib/haveapi/version.rb +1 -1
  35. data/lib/haveapi/views/version_page/client_auth.erb +1 -1
  36. data/lib/haveapi/views/version_page/client_example.erb +3 -3
  37. data/lib/haveapi/views/version_page/client_init.erb +1 -1
  38. data/lib/haveapi/views/version_page.erb +2 -2
  39. data/lib/haveapi/views/version_sidebar.erb +4 -2
  40. data/spec/action/authorize_spec.rb +99 -0
  41. data/spec/action/runtime_spec.rb +426 -0
  42. data/spec/action_state_spec.rb +52 -0
  43. data/spec/authentication/basic_spec.rb +29 -0
  44. data/spec/authentication/oauth2_spec.rb +329 -0
  45. data/spec/authentication/token_spec.rb +195 -0
  46. data/spec/authentication/token_version_routes_spec.rb +164 -0
  47. data/spec/authorization_spec.rb +66 -0
  48. data/spec/documentation/auth_filtering_spec.rb +195 -1
  49. data/spec/documentation/current_user_html_escaping_spec.rb +47 -0
  50. data/spec/documentation/examples_spec.rb +97 -0
  51. data/spec/documentation/host_html_escaping_spec.rb +41 -0
  52. data/spec/documentation_spec.rb +13 -0
  53. data/spec/extensions/action_exceptions_spec.rb +30 -0
  54. data/spec/model_adapters/active_record_spec.rb +406 -1
  55. data/spec/parameters/typed_spec.rb +42 -0
  56. data/spec/params_spec.rb +41 -0
  57. data/spec/server/integration_spec.rb +90 -0
  58. data/spec/validator_chain_spec.rb +39 -0
  59. data/spec/validators/confirmation_spec.rb +14 -0
  60. data/spec/validators/format_spec.rb +7 -0
  61. data/spec/validators/length_spec.rb +6 -0
  62. data/spec/validators/numericality_spec.rb +7 -0
  63. data/spec/validators/presence_spec.rb +2 -0
  64. data/test_support/client_test_api.rb +28 -0
  65. metadata +8 -4
  66. data/shell.nix +0 -20
@@ -153,6 +153,8 @@ module HaveAPI::Authentication
153
153
  t = token(request)
154
154
 
155
155
  t && config.find_user_by_token(request, t)
156
+ rescue HaveAPI::AuthenticationError
157
+ nil
156
158
  end
157
159
 
158
160
  # Extract token from HTTP request
@@ -194,10 +196,7 @@ module HaveAPI::Authentication
194
196
  end
195
197
 
196
198
  def token_present?(value)
197
- return false if value.nil?
198
- return false if value.respond_to?(:empty?) && value.empty?
199
-
200
- true
199
+ value.is_a?(String) && !value.empty?
201
200
  end
202
201
 
203
202
  def token_resource
@@ -228,7 +227,8 @@ module HaveAPI::Authentication
228
227
  END
229
228
  integer :interval, label: 'Interval',
230
229
  desc: 'How long will requested token be valid, in seconds.',
231
- default: 60 * 5, fill: true
230
+ default: 60 * 5, fill: true,
231
+ number: { min: 1, max: 86_400 }
232
232
  end
233
233
 
234
234
  output(:hash) do
@@ -277,11 +277,30 @@ module HaveAPI::Authentication
277
277
 
278
278
  def exec
279
279
  provider = self.class.resource.token_instance
280
- result = provider.config.class.revoke.handle.call(ActionRequest.new(
281
- request:,
282
- user: current_user,
283
- token: provider.token(request)
284
- ), ActionResult.new)
280
+ begin
281
+ token = provider.token(request)
282
+ user = provider.authenticate(request)
283
+ rescue HaveAPI::Authentication::TokenConflict => e
284
+ error!(e.message, {}, http_status: 400)
285
+ end
286
+
287
+ unless user
288
+ error!(
289
+ 'Action requires user to authenticate with a token',
290
+ {},
291
+ http_status: 401
292
+ )
293
+ end
294
+
295
+ begin
296
+ result = provider.config.class.revoke.handle.call(ActionRequest.new(
297
+ request:,
298
+ user:,
299
+ token:
300
+ ), ActionResult.new)
301
+ rescue HaveAPI::AuthenticationError => e
302
+ error!(e.message)
303
+ end
285
304
 
286
305
  if result.ok?
287
306
  ok!
@@ -305,11 +324,30 @@ module HaveAPI::Authentication
305
324
 
306
325
  def exec
307
326
  provider = self.class.resource.token_instance
308
- result = provider.config.class.renew.handle.call(ActionRequest.new(
309
- request:,
310
- user: current_user,
311
- token: provider.token(request)
312
- ), ActionResult.new)
327
+ begin
328
+ token = provider.token(request)
329
+ user = provider.authenticate(request)
330
+ rescue HaveAPI::Authentication::TokenConflict => e
331
+ error!(e.message, {}, http_status: 400)
332
+ end
333
+
334
+ unless user
335
+ error!(
336
+ 'Action requires user to authenticate with a token',
337
+ {},
338
+ http_status: 401
339
+ )
340
+ end
341
+
342
+ begin
343
+ result = provider.config.class.renew.handle.call(ActionRequest.new(
344
+ request:,
345
+ user:,
346
+ token:
347
+ ), ActionResult.new)
348
+ rescue HaveAPI::AuthenticationError => e
349
+ error!(e.message)
350
+ end
313
351
 
314
352
  if result.ok?
315
353
  { valid_to: result.valid_to }
@@ -30,7 +30,15 @@ module HaveAPI
30
30
  # Apply restrictions on query which selects objects from database.
31
31
  # Most common usage is restrict user to access only objects he owns.
32
32
  def restrict(**kwargs)
33
- @restrict << kwargs
33
+ normalized = normalize_hash_keys(kwargs)
34
+
35
+ normalized.each do |key, value|
36
+ @restrict.each do |restriction|
37
+ deny if restriction.has_key?(key) && restriction[key] != value
38
+ end
39
+ end
40
+
41
+ @restrict << normalized
34
42
  end
35
43
 
36
44
  # Restrict parameters client can set/change.
@@ -65,7 +73,11 @@ module HaveAPI
65
73
  ret = {}
66
74
 
67
75
  @restrict.each do |r|
68
- ret.update(r)
76
+ r.each do |key, value|
77
+ deny if ret.has_key?(key) && ret[key] != value
78
+
79
+ ret[key] = value
80
+ end
69
81
  end
70
82
 
71
83
  ret
@@ -79,12 +91,16 @@ module HaveAPI
79
91
  filter_inner(output, @output, params, format)
80
92
  end
81
93
 
94
+ def permitted_input_names(params)
95
+ permitted_params(params, @input).map(&:name)
96
+ end
97
+
82
98
  private
83
99
 
84
100
  def filter_inner(allowed_params, direction, params, format)
85
101
  allowed = {}
86
102
 
87
- allowed_params.each do |p|
103
+ permitted_params(allowed_params, direction).each do |p|
88
104
  if params.has_param?(p.name)
89
105
  allowed[p.name] = format ? p.format_output(params[p.name]) : params[p.name]
90
106
 
@@ -93,29 +109,37 @@ module HaveAPI
93
109
  end
94
110
  end
95
111
 
96
- return allowed unless direction
112
+ allowed
113
+ end
97
114
 
98
- if direction[:whitelist]
99
- ret = {}
115
+ def permitted_params(params, direction)
116
+ return params unless direction
100
117
 
101
- direction[:whitelist].each do |p|
102
- ret[p] = allowed[p] if allowed.has_key?(p)
103
- end
104
-
105
- ret
118
+ if direction[:whitelist]
119
+ whitelist = normalize_names(direction[:whitelist])
106
120
 
121
+ params.select { |p| whitelist.include?(p.name) }
107
122
  elsif direction[:blacklist]
108
- ret = allowed.dup
123
+ blacklist = normalize_names(direction[:blacklist])
109
124
 
110
- direction[:blacklist].each do |p|
111
- ret.delete(p)
112
- end
125
+ params.reject { |p| blacklist.include?(p.name) }
126
+ else
127
+ params
128
+ end
129
+ end
113
130
 
114
- ret
131
+ def normalize_names(names)
132
+ names.map { |name| normalize_key(name) }
133
+ end
115
134
 
116
- else
117
- allowed
135
+ def normalize_hash_keys(hash)
136
+ hash.each_with_object({}) do |(key, value), ret|
137
+ ret[normalize_key(key)] = value
118
138
  end
119
139
  end
140
+
141
+ def normalize_key(key)
142
+ key.is_a?(String) ? key.to_sym : key
143
+ end
120
144
  end
121
145
  end
@@ -94,7 +94,7 @@ module HaveAPI::ClientExamples
94
94
  out << "$reply = $api->#{resource_path.join('->')}->#{action_name}"
95
95
  out << "(#{args.join(', ')});\n"
96
96
 
97
- return (out << response(sample)) if sample[:status]
97
+ return out << response(sample) if sample[:status]
98
98
 
99
99
  out << '// Throws exception \\HaveAPI\\Client\\Exception\\ActionFailed'
100
100
  out
@@ -55,7 +55,7 @@ module HaveAPI::ClientExamples
55
55
  out << "reply = client.#{resource_path.join('.')}.#{action_name}"
56
56
  out << "(#{args.join(', ')})" unless args.empty?
57
57
 
58
- return (out << response(sample)) if sample[:status]
58
+ return out << response(sample) if sample[:status]
59
59
 
60
60
  out << "\n"
61
61
  out << '# Raises exception HaveAPI::Client::ActionFailed'
@@ -2,11 +2,13 @@ module HaveAPI
2
2
  class Context
3
3
  attr_accessor :server, :version, :request, :resource, :action, :path, :args,
4
4
  :params, :current_user, :authorization, :endpoint, :resource_path,
5
- :action_instance, :action_prepare, :layout, :doc
5
+ :action_instance, :action_prepare, :layout, :doc,
6
+ :auth_users_by_version
6
7
 
7
8
  def initialize(server, version: nil, request: nil, resource: [], action: nil,
8
9
  path: nil, args: nil, params: nil, user: nil,
9
- authorization: nil, endpoint: nil, resource_path: [], doc: false)
10
+ authorization: nil, endpoint: nil, resource_path: [], doc: false,
11
+ auth_users_by_version: nil)
10
12
  @server = server
11
13
  @version = version
12
14
  @request = request
@@ -20,6 +22,7 @@ module HaveAPI
20
22
  @endpoint = endpoint
21
23
  @resource_path = resource_path
22
24
  @doc = doc
25
+ @auth_users_by_version = auth_users_by_version
23
26
  end
24
27
 
25
28
  def resolved_path
@@ -79,7 +82,7 @@ module HaveAPI
79
82
 
80
83
  my_args = args.clone
81
84
 
82
- path.scan(/\{([a-zA-Z\-_]+)\}/) do |match|
85
+ path.scan(/\{([a-zA-Z0-9\-_]+)\}/) do |match|
83
86
  path_param = match.first
84
87
  ret[path_param] = my_args.shift
85
88
  end
@@ -94,7 +97,10 @@ module HaveAPI
94
97
  private
95
98
 
96
99
  def resolve_arg!(path, arg)
97
- path.sub!(/\{[a-zA-Z\-_]+\}/, arg.to_s)
100
+ value = arg.to_s
101
+ raise HaveAPI::ValidationError, 'invalid path parameter encoding' unless value.valid_encoding?
102
+
103
+ path.sub!(/\{[a-zA-Z0-9\-_]+\}/, value)
98
104
  end
99
105
  end
100
106
  end
@@ -41,21 +41,17 @@ module HaveAPI
41
41
  end
42
42
 
43
43
  def authorized?(context)
44
- if (context.endpoint || context.current_user) \
45
- && @authorization && !@authorization.call(context.current_user)
46
- false
47
- else
48
- true
49
- end
44
+ return true unless @authorization
45
+
46
+ @authorization.call(context.current_user) ? true : false
50
47
  end
51
48
 
52
49
  def provided?
53
- if instance_variables.detect do |v|
54
- instance_variable_get(v)
55
- end
56
- true
57
- else
58
- false
50
+ instance_variables.any? do |v|
51
+ value = instance_variable_get(v)
52
+ next false if v == :@title && value.to_s.empty?
53
+
54
+ !value.nil? && value != false
59
55
  end
60
56
  end
61
57
 
@@ -65,8 +61,8 @@ module HaveAPI
65
61
  title: @title,
66
62
  comment: @comment,
67
63
  path_params: @path_params,
68
- request: filter_input_params(context, @request),
69
- response: filter_output_params(context, @response),
64
+ request: @request.nil? ? nil : filter_input_params(context, @request),
65
+ response: @response.nil? ? nil : filter_output_params(context, @response),
70
66
  status: @status.nil? ? true : @status,
71
67
  message: @message,
72
68
  errors: @errors,
@@ -80,6 +76,8 @@ module HaveAPI
80
76
  protected
81
77
 
82
78
  def filter_input_params(context, input)
79
+ return nil if input.nil?
80
+
83
81
  case context.action.input.layout
84
82
  when :object, :hash
85
83
  context.authorization.filter_input(
@@ -91,14 +89,15 @@ module HaveAPI
91
89
  input.map do |obj|
92
90
  context.authorization.filter_input(
93
91
  context.action.input.params,
94
- ModelAdapters::Hash.output(context, obj),
95
- true
92
+ ModelAdapters::Hash.output(context, obj)
96
93
  )
97
94
  end
98
95
  end
99
96
  end
100
97
 
101
98
  def filter_output_params(context, output)
99
+ return nil if output.nil?
100
+
102
101
  case context.action.output.layout
103
102
  when :object, :hash
104
103
  context.authorization.filter_output(
@@ -5,12 +5,12 @@ module HaveAPI::Extensions
5
5
  class << self
6
6
  def enabled(server)
7
7
  HaveAPI::Action.connect_hook(:exec_exception) do |ret, _context, e|
8
- break(ret) unless @exceptions
9
-
10
- @exceptions.each do |handler|
11
- if e.is_a?(handler[:klass])
12
- ret = handler[:block].call(ret, e)
13
- break
8
+ if @exceptions
9
+ @exceptions.each do |handler|
10
+ if e.is_a?(handler[:klass])
11
+ ret = handler[:block].call(ret, e)
12
+ break
13
+ end
14
14
  end
15
15
  end
16
16
 
@@ -19,6 +19,8 @@ module HaveAPI::ModelAdapters
19
19
 
20
20
  module Action
21
21
  module InstanceMethods
22
+ MAX_INCLUDE_DEPTH = 16
23
+
22
24
  # Helper method that sets correct ActiveRecord includes
23
25
  # according to the meta includes sent by the user.
24
26
  # `q` is the model or partial AR query. If not set,
@@ -77,6 +79,14 @@ module HaveAPI::ModelAdapters
77
79
  paginable = input[parameter]
78
80
  limit = input[:limit]
79
81
 
82
+ if limit && limit > HaveAPI::Actions::Paginable::MAX_LIMIT
83
+ error!(
84
+ "limit has to be maximally #{HaveAPI::Actions::Paginable::MAX_LIMIT}",
85
+ {},
86
+ http_status: 400
87
+ )
88
+ end
89
+
80
90
  q = yield(q, paginable) if paginable
81
91
  q = q.limit(limit) if limit
82
92
  q
@@ -87,40 +97,54 @@ module HaveAPI::ModelAdapters
87
97
  def ar_parse_includes(raw)
88
98
  return @ar_parsed_includes if @ar_parsed_includes
89
99
 
90
- @ar_parsed_includes = ar_inner_includes(raw).select do |inc|
91
- # Drop associations that are not registered in the AR:
92
- # The API resource may have associations that are not based on
93
- # associations in AR.
94
- if inc.is_a?(::Hash)
95
- inc.each_key do |k|
96
- next(false) unless self.class.model.reflections.has_key?(k.to_s)
97
- end
100
+ @ar_parsed_includes = raw.filter_map do |assoc|
101
+ ar_parse_include_path(assoc, self.class.model)
102
+ end
103
+ end
98
104
 
99
- else
100
- next(false) unless self.class.model.reflections.has_key?(inc.to_s)
101
- end
105
+ # Kept for callers that used the old parser directly.
106
+ def ar_inner_includes(includes)
107
+ includes.filter_map do |assoc|
108
+ parts = assoc.to_s.split('__').reject(&:empty?).map(&:to_sym)
109
+ next if parts.empty? || parts.size > MAX_INCLUDE_DEPTH
102
110
 
103
- true
111
+ ar_include_tree(parts)
104
112
  end
105
113
  end
106
114
 
107
- # Called by ar_parse_includes for recursion purposes.
108
- def ar_inner_includes(includes)
109
- args = []
115
+ def ar_parse_include_path(path, model)
116
+ parts = path.to_s.split('__')
117
+ return if parts.empty? || parts.size > MAX_INCLUDE_DEPTH
118
+
119
+ current_model = model
120
+ symbols = []
121
+ i = 0
122
+
123
+ while i < parts.size
124
+ part = parts[i]
125
+ return if part.empty?
110
126
 
111
- includes.each do |assoc|
112
- if assoc.index('__')
113
- tmp = {}
114
- parts = assoc.split('__')
115
- tmp[parts.first.to_sym] = ar_inner_includes([parts[1..].join('__')])
127
+ begin
128
+ reflection = current_model.reflections[part.to_s]
129
+ return unless reflection
116
130
 
117
- args << tmp
118
- else
119
- args << assoc.to_sym
131
+ symbols << part.to_sym
132
+ current_model = reflection.klass
133
+ rescue NameError
134
+ return
120
135
  end
136
+ i += 1
121
137
  end
122
138
 
123
- args
139
+ ar_include_tree(symbols)
140
+ end
141
+
142
+ def ar_include_tree(parts)
143
+ ret = parts.last
144
+ (parts.size - 2).downto(0) do |i|
145
+ ret = { parts[i] => [ret] }
146
+ end
147
+ ret
124
148
  end
125
149
 
126
150
  # Default includes contain all associated resources specified
@@ -153,7 +177,9 @@ module HaveAPI::ModelAdapters
153
177
  raise HaveAPI::ValidationError, "not a valid id #{original.inspect}"
154
178
  end
155
179
 
156
- if raw.is_a?(String)
180
+ if raw.is_a?(Array) || raw.is_a?(Hash) || [true, false].include?(raw)
181
+ raise HaveAPI::ValidationError, "not a valid id #{original.inspect}"
182
+ elsif raw.is_a?(String)
157
183
  stripped = raw.strip
158
184
 
159
185
  if stripped.empty?
@@ -163,9 +189,6 @@ module HaveAPI::ModelAdapters
163
189
  end
164
190
 
165
191
  raw = stripped
166
-
167
- elsif [true, false].include?(raw)
168
- raise HaveAPI::ValidationError, "not a valid id #{original.inspect}"
169
192
  end
170
193
 
171
194
  value = if integer_pk?(model)
@@ -177,11 +200,17 @@ module HaveAPI::ModelAdapters
177
200
  raw
178
201
  end
179
202
 
180
- if extra[:fetch]
181
- model.instance_exec(value, &extra[:fetch])
182
- else
183
- model.find(value)
203
+ ret = if extra[:fetch]
204
+ model.instance_exec(value, &extra[:fetch])
205
+ else
206
+ model.find(value)
207
+ end
208
+
209
+ if ret.nil? && !allow_null
210
+ raise HaveAPI::ValidationError, 'resource not found'
184
211
  end
212
+
213
+ ret
185
214
  rescue ::ActiveRecord::RecordNotFound
186
215
  raise HaveAPI::ValidationError, 'resource not found'
187
216
  rescue ArgumentError, TypeError
@@ -234,10 +263,20 @@ module HaveAPI::ModelAdapters
234
263
  return unless %i[object object_list].include?(action.input.layout)
235
264
 
236
265
  clean = proc do |raw|
237
- if raw.is_a?(String)
238
- raw.strip.split(',')
239
- elsif raw.is_a?(Array)
240
- raw
266
+ values = if raw.is_a?(String)
267
+ raw.strip.split(',')
268
+ elsif raw.is_a?(Array)
269
+ raw
270
+ else
271
+ raise HaveAPI::ValidationError, 'includes must be a string or array'
272
+ end
273
+
274
+ values.map do |value|
275
+ unless value.is_a?(String)
276
+ raise HaveAPI::ValidationError, 'includes must contain only strings'
277
+ end
278
+
279
+ value.strip
241
280
  end
242
281
  end
243
282
 
@@ -306,49 +345,82 @@ module HaveAPI::ModelAdapters
306
345
  res_output = res_show.output
307
346
 
308
347
  args = res_show.resolve_path_params(val)
348
+ resolve_assoc = includes_include?(param.name)
349
+ pass_includes = includes_pass_on_to(param.name) if resolve_assoc
309
350
 
310
- if includes_include?(param.name)
311
- push_cls = @context.action
312
- push_ins = @context.action_instance
351
+ with_association_context(res_show, args) do |show|
352
+ # Tell the child action it is being checked as a nested association.
353
+ show.flags[:inner_assoc] = true
313
354
 
314
- pass_includes = includes_pass_on_to(param.name)
355
+ return unauthorized_resource unless show.authorized?(@context.current_user)
356
+ return unauthorized_resource unless show_prepared?(show)
315
357
 
316
- show = res_show.new(
317
- push_ins.request,
318
- push_ins.version,
319
- {},
320
- nil,
321
- @context
322
- )
323
- show.meta[:includes] = pass_includes
358
+ if resolve_assoc
359
+ show.meta[:includes] = pass_includes
324
360
 
325
- # This flag is used to tell the action that it is being used
326
- # as a nested association, that it wasn't called directly by the user.
327
- show.flags[:inner_assoc] = true
361
+ ret = show.safe_output(val)
328
362
 
329
- show.authorized?(push_ins.current_user) # FIXME: handle false
363
+ raise "#{res_show} resolve failed" unless ret[0]
330
364
 
331
- ret = show.safe_output(val)
365
+ ret[1][res_show.output.namespace].update({
366
+ _meta: ret[1][:_meta].update(resolved: true)
367
+ })
332
368
 
333
- @context.action_instance = push_ins
334
- @context.action = push_cls
369
+ else
370
+ {
371
+ param.value_id => val.send(res_output[param.value_id].db_name),
372
+ param.value_label => val.send(res_output[param.value_label].db_name),
373
+ _meta: {
374
+ path_params: args.is_a?(Array) ? args : [args],
375
+ resolved: false
376
+ }
377
+ }
378
+ end
379
+ end
380
+ end
335
381
 
336
- raise "#{res_show} resolve failed" unless ret[0]
382
+ def with_association_context(res_show, args)
383
+ push_cls = @context.action
384
+ push_ins = @context.action_instance
385
+ push_path = @context.path
386
+ @context.path = res_show.build_route('')
387
+
388
+ res_show.new(
389
+ push_ins.request,
390
+ push_ins.version,
391
+ res_show.path_params(@context.path, args),
392
+ nil,
393
+ @context
394
+ )
395
+ yield @context.action_instance
396
+ ensure
397
+ restore_context(push_cls, push_ins, push_path)
398
+ end
337
399
 
338
- ret[1][res_show.output.namespace].update({
339
- _meta: ret[1][:_meta].update(resolved: true)
340
- })
400
+ def restore_context(action, action_instance, path)
401
+ @context.action = action
402
+ @context.action_instance = action_instance
403
+ @context.path = path
404
+ end
341
405
 
342
- else
343
- {
344
- param.value_id => val.send(res_output[param.value_id].db_name),
345
- param.value_label => val.send(res_output[param.value_label].db_name),
346
- _meta: {
347
- path_params: args.is_a?(Array) ? args : [args],
348
- resolved: false
349
- }
350
- }
406
+ def show_prepared?(show)
407
+ completed = Object.new
408
+ ret = catch(:return) do
409
+ show.validate!
410
+ show.prepare
411
+ completed
351
412
  end
413
+
414
+ ret.equal?(completed)
415
+ end
416
+
417
+ def unauthorized_resource
418
+ {
419
+ _meta: {
420
+ resolved: false,
421
+ authorized: false
422
+ }
423
+ }
352
424
  end
353
425
 
354
426
  # Should an association with `name` be resolved?
@@ -11,7 +11,7 @@ module HaveAPI::ModelAdapters
11
11
  end
12
12
 
13
13
  class Input < ::HaveAPI::ModelAdapter::Input
14
- def self.clean(model, raw)
14
+ def self.clean(model, raw, _extra = nil)
15
15
  raw
16
16
  end
17
17
  end