jsapi 1.4 → 2.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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jsapi/controller/actions/class_methods.rb +61 -0
  3. data/lib/jsapi/controller/actions.rb +13 -0
  4. data/lib/jsapi/controller/authentication/class_methods.rb +65 -0
  5. data/lib/jsapi/controller/authentication/credentials/api_key.rb +24 -0
  6. data/lib/jsapi/controller/authentication/credentials/http/base.rb +25 -0
  7. data/lib/jsapi/controller/authentication/credentials/http/basic.rb +34 -0
  8. data/lib/jsapi/controller/authentication/credentials/http/bearer.rb +30 -0
  9. data/lib/jsapi/controller/authentication/credentials/http.rb +5 -0
  10. data/lib/jsapi/controller/authentication/credentials.rb +38 -0
  11. data/lib/jsapi/controller/authentication.rb +70 -0
  12. data/lib/jsapi/controller/base.rb +5 -4
  13. data/lib/jsapi/controller/methods/callbacks/callback.rb +80 -0
  14. data/lib/jsapi/controller/methods/callbacks/class_methods.rb +62 -0
  15. data/lib/jsapi/controller/methods/callbacks.rb +54 -0
  16. data/lib/jsapi/controller/methods.rb +209 -116
  17. data/lib/jsapi/controller/parameters.rb +24 -20
  18. data/lib/jsapi/controller/response.rb +71 -39
  19. data/lib/jsapi/controller.rb +2 -1
  20. data/lib/jsapi/dsl/base.rb +38 -5
  21. data/lib/jsapi/dsl/class_methods.rb +2 -2
  22. data/lib/jsapi/dsl/definitions.rb +41 -27
  23. data/lib/jsapi/dsl/operation.rb +10 -109
  24. data/lib/jsapi/dsl/parameter.rb +1 -1
  25. data/lib/jsapi/dsl/path.rb +41 -18
  26. data/lib/jsapi/dsl/request_body.rb +1 -1
  27. data/lib/jsapi/dsl/response.rb +9 -6
  28. data/lib/jsapi/dsl/schema.rb +11 -5
  29. data/lib/jsapi/dsl/shared_operation_methods.rb +140 -0
  30. data/lib/jsapi/dsl.rb +1 -2
  31. data/lib/jsapi/json/array.rb +2 -2
  32. data/lib/jsapi/json/object.rb +6 -6
  33. data/lib/jsapi/json.rb +4 -6
  34. data/lib/jsapi/media/range.rb +102 -0
  35. data/lib/jsapi/media/type.rb +70 -0
  36. data/lib/jsapi/media/type_and_subtype.rb +38 -0
  37. data/lib/jsapi/media.rb +9 -0
  38. data/lib/jsapi/messages.rb +19 -0
  39. data/lib/jsapi/meta/callback/base.rb +63 -8
  40. data/lib/jsapi/meta/content.rb +59 -0
  41. data/lib/jsapi/meta/definitions.rb +299 -153
  42. data/lib/jsapi/meta/example/base.rb +41 -8
  43. data/lib/jsapi/meta/existence.rb +4 -2
  44. data/lib/jsapi/meta/header/base.rb +4 -2
  45. data/lib/jsapi/meta/info.rb +3 -1
  46. data/lib/jsapi/meta/license.rb +11 -5
  47. data/lib/jsapi/meta/model/attributes/class_methods.rb +150 -0
  48. data/lib/jsapi/meta/model/attributes/frozen_error.rb +16 -0
  49. data/lib/jsapi/meta/model/attributes/type_caster.rb +56 -0
  50. data/lib/jsapi/meta/model/attributes.rb +24 -118
  51. data/lib/jsapi/meta/model/base.rb +2 -5
  52. data/lib/jsapi/meta/model/reference.rb +46 -10
  53. data/lib/jsapi/meta/model/wrappable.rb +23 -0
  54. data/lib/jsapi/meta/model/wrapper.rb +26 -0
  55. data/lib/jsapi/meta/model.rb +2 -1
  56. data/lib/jsapi/meta/oauth_flow.rb +1 -1
  57. data/lib/jsapi/meta/openapi/extensions.rb +5 -6
  58. data/lib/jsapi/meta/openapi/version.rb +16 -4
  59. data/lib/jsapi/meta/operation.rb +177 -71
  60. data/lib/jsapi/meta/parameter/base.rb +10 -6
  61. data/lib/jsapi/meta/parameter/wrapper.rb +13 -0
  62. data/lib/jsapi/meta/parameter.rb +3 -0
  63. data/lib/jsapi/meta/path.rb +59 -13
  64. data/lib/jsapi/meta/pathname.rb +6 -3
  65. data/lib/jsapi/meta/property.rb +10 -0
  66. data/lib/jsapi/meta/request_body/base.rb +69 -32
  67. data/lib/jsapi/meta/request_body/wrapper.rb +13 -0
  68. data/lib/jsapi/meta/request_body.rb +3 -0
  69. data/lib/jsapi/meta/rescue_handler.rb +18 -17
  70. data/lib/jsapi/meta/response/base.rb +82 -58
  71. data/lib/jsapi/meta/response/reference.rb +11 -1
  72. data/lib/jsapi/meta/response/wrapper.rb +26 -0
  73. data/lib/jsapi/meta/response.rb +3 -0
  74. data/lib/jsapi/meta/schema/additional_properties.rb +8 -0
  75. data/lib/jsapi/meta/schema/array.rb +20 -8
  76. data/lib/jsapi/meta/schema/base.rb +10 -9
  77. data/lib/jsapi/meta/schema/boundary.rb +1 -0
  78. data/lib/jsapi/meta/schema/numeric.rb +26 -20
  79. data/lib/jsapi/meta/schema/object.rb +60 -44
  80. data/lib/jsapi/meta/schema/reference.rb +1 -8
  81. data/lib/jsapi/meta/schema/string.rb +12 -6
  82. data/lib/jsapi/meta/schema/wrapper.rb +31 -0
  83. data/lib/jsapi/meta/schema.rb +22 -9
  84. data/lib/jsapi/meta/security_requirement.rb +2 -2
  85. data/lib/jsapi/meta/security_scheme/api_key.rb +5 -2
  86. data/lib/jsapi/meta/security_scheme/base.rb +7 -5
  87. data/lib/jsapi/meta/security_scheme/http/basic.rb +5 -7
  88. data/lib/jsapi/meta/security_scheme/http/bearer.rb +5 -5
  89. data/lib/jsapi/meta/security_scheme/http/other.rb +1 -3
  90. data/lib/jsapi/meta/security_scheme/mutual_tls.rb +1 -3
  91. data/lib/jsapi/meta/security_scheme/oauth2.rb +18 -13
  92. data/lib/jsapi/meta/security_scheme/open_id_connect.rb +4 -4
  93. data/lib/jsapi/meta/security_scheme.rb +4 -4
  94. data/lib/jsapi/meta/server.rb +4 -2
  95. data/lib/jsapi/meta/tag.rb +9 -3
  96. data/lib/jsapi/meta.rb +2 -1
  97. data/lib/jsapi/model/base.rb +1 -1
  98. data/lib/jsapi/status/base.rb +35 -0
  99. data/lib/jsapi/status/code.rb +113 -0
  100. data/lib/jsapi/status/default.rb +16 -0
  101. data/lib/jsapi/status/range.rb +35 -0
  102. data/lib/jsapi/status.rb +37 -0
  103. data/lib/jsapi/version.rb +1 -1
  104. data/lib/jsapi.rb +3 -3
  105. metadata +36 -10
  106. data/lib/jsapi/controller/parameters_invalid.rb +0 -27
  107. data/lib/jsapi/dsl/callback.rb +0 -21
  108. data/lib/jsapi/dsl/error.rb +0 -36
  109. data/lib/jsapi/invalid_argument_error.rb +0 -12
  110. data/lib/jsapi/invalid_value_error.rb +0 -12
  111. data/lib/jsapi/invalid_value_helper.rb +0 -17
  112. data/lib/jsapi/meta/model/type_caster.rb +0 -50
  113. data/lib/jsapi/meta/schema/delegator.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 87f305e41facfb84c96e5e2406b636aa9e10a27545a25d42a5029f4d4dd93488
4
- data.tar.gz: 6723179cd4981c590a4e8f0c45808d9b69a0436be48e1e89f06c7ef0a4b56ddb
3
+ metadata.gz: bbb6b0f27ef5b970e527367f2e918502d935765e4201ddbf5376fa4566ed5fde
4
+ data.tar.gz: d4764ceb8c1e32d0368a14d2d09370696f0329eb69f745ccdb9da67e5dcd0b1b
5
5
  SHA512:
6
- metadata.gz: 7ddefbaaa678ed70e29055dbe4452ee0f4f738fa2386e8e3f5dbaa42e682ca973fa0bd04e5c6b22b2570bfd290fd4fcfbb42c8f7e50650ffda50d0cfbb160cc7
7
- data.tar.gz: 3fc210ea9c236f319f1c94dcb741d7d910eb7edb90020aafcbbee2a033bd232a9bb88dd26fc306869f7c3be715487f4655a517e5968f63461a6bbad57074099a
6
+ metadata.gz: d996c4830b339e80c9de07bf379d239734de9f0df4d9f1cd0072ae358c755a07aeee5a54bbbbe6cb2dae4452e3fba0c8691d6a641a74ac8ef7014b00b64d51dd
7
+ data.tar.gz: ea2bf03ff17664bcb434f160e13e17e5d2eb341fb7c17ceddf6667d4fbcd6db297eee0c93c66a8d530a2da1032bf33b983385c05e88bd9a431a68b0062a4b08e
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module Controller
5
+ module Actions
6
+ module ClassMethods
7
+ ##
8
+ # :method: api_action
9
+ # :call-seq:
10
+ # api_action(name, operation_name = nil, action: :index, **options)
11
+ # api_action(operation_name = nil, action: :index, **options, &block)
12
+ #
13
+ # Defines a controller action that performs an API operation by wrapping
14
+ # the given method or block by +api_operation+.
15
+ #
16
+ # # Invoke :foo to perform :bar
17
+ # api_action :foo, :bar
18
+ #
19
+ # # Call the given block to perform :bar
20
+ # api_action :bar do |api_params|
21
+ # # ...
22
+ # end
23
+ #
24
+ # Raises an +ArgumentError+ when neither +name+ nor +block+ is given.
25
+ #
26
+ # +:action+ specifies the name of the controller action to be defined.
27
+ # The default is +:index+.
28
+ #
29
+ # All other options are passed to +api_operation+.
30
+
31
+ ##
32
+ # :method: api_action!
33
+ # :call-seq:
34
+ # api_action!(name, operation_name = nil, action: :index, **options)
35
+ # api_action!(operation_name = nil, action: :index, **options, &block)
36
+ #
37
+ # Like +api_action+, except that +api_operation!+ is used instead of
38
+ # +api_operation+.
39
+
40
+ ['', '!'].each do |suffix|
41
+ method = :"api_operation#{suffix}"
42
+
43
+ define_method(:"api_action#{suffix}") \
44
+ do |name = nil, operation_name = nil, action: nil, **options, &block|
45
+ raise ArgumentError, 'neither name nor block is given' if !name && !block
46
+
47
+ operation_name = name if operation_name.nil?
48
+
49
+ define_method(action || :index, &(
50
+ if block
51
+ -> { send(method, operation_name, **options, &block) }
52
+ else
53
+ -> { send(method, operation_name, **options) { |p| send(name, p) } }
54
+ end
55
+ ))
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'actions/class_methods'
4
+
5
+ module Jsapi
6
+ module Controller
7
+ module Actions
8
+ def self.included(base) # :nodoc:
9
+ base.extend(ClassMethods)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module Controller
5
+ module Authentication
6
+ module ClassMethods
7
+ # :call-seq:
8
+ # api_after_authentication(method_or_proc, **options)
9
+ # api_after_authentication(**options, &block)
10
+ #
11
+ # Registers a callback triggered after a request has been authenticated
12
+ # successfully.
13
+ def api_after_authentication(method_or_proc = nil, **options, &block)
14
+ _api_add_callback(:after_authentication, method_or_proc, **options, &block)
15
+ end
16
+
17
+ # :call-seq:
18
+ # api_authenticate(*scheme_names, with:)
19
+ # api_authenticate(*scheme_names, &block)
20
+ #
21
+ # Registers a handler to authenticate requests according to the specified
22
+ # security schemes.
23
+ #
24
+ # api_authenticate 'basic_auth', with: :authenticate
25
+ #
26
+ # If no scheme names are specified, the handler is used as fallback for all
27
+ # security schemes for which no handler is registered.
28
+ #
29
+ # The +:with+ option specifies the method to be called to authenticate
30
+ # requests. Alternatively, a block can be given as handler.
31
+ #
32
+ # If the handler returns a truthy value, the request is assumed to be
33
+ # authenticated successfully.
34
+ #
35
+ # def authenticate(credentials)
36
+ # credentials.username == 'api_user' &&
37
+ # credentials.password == 'secret'
38
+ # end
39
+ def api_authenticate(*scheme_names, with: nil, &block)
40
+ handler = with || block
41
+ raise ArgumentError, 'either the :with keyword argument or a block ' \
42
+ 'must be specified' unless handler
43
+
44
+ (scheme_names.presence || [nil]).each do |scheme_name|
45
+ _api_authentication_handlers[scheme_name&.to_s] = handler
46
+ end
47
+ end
48
+
49
+ def _api_authentication_handler(scheme_name) # :nodoc:
50
+ scheme_name = scheme_name&.to_s
51
+
52
+ _api_authentication_handlers[scheme_name] ||
53
+ superclass.try(:_api_authentication_handler, scheme_name) ||
54
+ (_api_authentication_handler(nil) unless scheme_name.nil?)
55
+ end
56
+
57
+ private
58
+
59
+ def _api_authentication_handlers
60
+ @_api_authentication_handlers ||= {}
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module Controller
5
+ module Authentication
6
+ module Credentials
7
+ # Holds an API key.
8
+ class APIKey
9
+ # The API key as it was passed.
10
+ attr_reader :api_key
11
+
12
+ def initialize(api_key)
13
+ @api_key = api_key
14
+ end
15
+
16
+ # Returns true if the API Key is not nil.
17
+ def well_formed?
18
+ !api_key.nil?
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module Controller
5
+ module Authentication
6
+ module Credentials
7
+ module HTTP
8
+ # Holds credentials passed according to \HTTP \Authentication.
9
+ class Base
10
+ attr_reader :auth_scheme, :auth_param
11
+
12
+ def initialize(authorization)
13
+ @auth_scheme, @auth_param = authorization.to_s.split(' ', 2)
14
+ end
15
+
16
+ # Returns true if the credentials are syntactically correct.
17
+ def well_formed?
18
+ auth_scheme.present? && auth_param.present?
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Jsapi
6
+ module Controller
7
+ module Authentication
8
+ module Credentials
9
+ module HTTP
10
+ # Holds a username and password passed according to \HTTP
11
+ # \Basic \Authentication.
12
+ class Basic < Base
13
+ # The decoded password.
14
+ attr_reader :password
15
+
16
+ # The decoded username.
17
+ attr_reader :username
18
+
19
+ def initialize(authorization)
20
+ super
21
+ @username, @password =
22
+ Base64.decode64(auth_param).split(':', 2) \
23
+ if auth_scheme == 'Basic' && auth_param.present?
24
+ end
25
+
26
+ def well_formed? # :nodoc:
27
+ !username.nil? && !password.nil?
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Jsapi
6
+ module Controller
7
+ module Authentication
8
+ module Credentials
9
+ module HTTP
10
+ # Holds a token passed according to \HTTP \Bearer \Authentication.
11
+ class Bearer < Base
12
+ # The decoded token.
13
+ attr_reader :token
14
+
15
+ def initialize(authorization)
16
+ super
17
+ @token =
18
+ Base64.decode64(auth_param) \
19
+ if auth_scheme == 'Bearer' && auth_param.present?
20
+ end
21
+
22
+ def well_formed? # :nodoc:
23
+ !token.nil?
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'http/base'
4
+ require_relative 'http/basic'
5
+ require_relative 'http/bearer'
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'credentials/api_key'
4
+ require_relative 'credentials/http'
5
+
6
+ module Jsapi
7
+ module Controller
8
+ module Authentication
9
+ # Provides classes to pass credentials to an authentication handler.
10
+ module Credentials
11
+ class << self
12
+ # Creates credentials from +request+ according to the specified
13
+ # security scheme.
14
+ def create(request, security_scheme)
15
+ case security_scheme
16
+ when Meta::SecurityScheme::APIKey
17
+ name = security_scheme.name
18
+ APIKey.new(
19
+ case security_scheme.in
20
+ when 'header'
21
+ request.headers[name]
22
+ when 'query'
23
+ request.query_parameters[name]
24
+ end
25
+ )
26
+ when Meta::SecurityScheme::HTTP::Basic
27
+ HTTP::Basic.new(request.authorization)
28
+ when Meta::SecurityScheme::HTTP::Bearer
29
+ HTTP::Bearer.new(request.authorization)
30
+ when Meta::SecurityScheme::HTTP::Other
31
+ HTTP::Base.new(request.authorization)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'authentication/credentials'
4
+ require_relative 'authentication/class_methods'
5
+
6
+ module Jsapi
7
+ module Controller
8
+ # The \Authentication add-on.
9
+ #
10
+ # class FooController < Jsapi::Controller::Base
11
+ # include Jsapi::Controller::Authentication
12
+ #
13
+ # api_authenticate 'basic_auth' do |credentials|
14
+ # # Implement authentication handler here
15
+ # end
16
+ #
17
+ # api_security_scheme 'basic_auth', type: 'http', scheme: 'basic'
18
+ #
19
+ # api_security_requirement do
20
+ # scheme 'basic_auth'
21
+ # end
22
+ # end
23
+ module Authentication
24
+ def self.included(base) # :nodoc:
25
+ base.extend(ClassMethods)
26
+ end
27
+
28
+ # Returns true if and only if the current request satisfies at least one of
29
+ # the security requirements applied to the specified operation. A security
30
+ # requirement is satisfied if it is empty ({}) or all of the referred
31
+ # security schemes are satisfied.
32
+ #
33
+ # +operation_name+ can be omitted if the controller handles one operation only.
34
+ # If the operation isn't defined, an OperationNotDefined exception is raised.
35
+ def api_authenticated?(operation_name = nil)
36
+ _api_authenticated?(_api_operation(operation_name))
37
+ end
38
+
39
+ private
40
+
41
+ def _api_authenticated?(operation)
42
+ security_requirements = operation.security_requirements
43
+ return true if security_requirements.blank?
44
+
45
+ security_requirements.any? do |security_requirement|
46
+ security_requirement.schemes.keys.all? do |scheme_name|
47
+ # Find the most appropriate authentication handler
48
+ authentication_handler = self.class._api_authentication_handler(scheme_name)
49
+ next false unless authentication_handler
50
+
51
+ # Find the appropriate security scheme
52
+ security_scheme = api_definitions.find_security_scheme(scheme_name)
53
+ next false unless security_scheme
54
+
55
+ # Create the credentials to be passed to the authenticator
56
+ credentials = Credentials.create(request, security_scheme)
57
+ next false unless credentials&.well_formed?
58
+
59
+ # Call authentication handler
60
+ if authentication_handler.is_a?(Proc)
61
+ instance_exec(credentials, &authentication_handler)
62
+ else
63
+ send(authentication_handler, credentials)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -4,16 +4,17 @@ module Jsapi
4
4
  module Controller
5
5
  # The base API controller class.
6
6
  #
7
+ # A minimal API controller responding with "Hello world" looks like:
8
+ #
7
9
  # class FooController < Jsapi::Controller::Base
8
10
  # api_operation do
9
11
  # response type: 'string'
10
12
  # end
11
13
  #
12
- # def index
13
- # api_operation { 'Hello world' }
14
- # end
15
- # end
14
+ # api_action { 'Hello world' }
15
+ #
16
16
  class Base < ActionController::API
17
+ include Actions
17
18
  include DSL
18
19
  include Methods
19
20
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module Controller
5
+ module Methods
6
+ module Callbacks
7
+ # Represents a callback to be triggered.
8
+ class Callback
9
+ # The method or +Proc+ to be called.
10
+ attr_reader :method_or_proc
11
+
12
+ # Creates a callback that executes +method_or_proc+.
13
+ #
14
+ # The following options can be specified:
15
+ #
16
+ # - +:if+ - The conditions under which the callback is triggered only.
17
+ # - +:unless+ - The conditions under which the callback isn't triggered.
18
+ # - +:only+ - The operations on which the callback is triggered only.
19
+ # - +:except+ - The operations on which the callback isn't triggered.
20
+ #
21
+ # +:if+ and +:unless+ can be a symbol, a +Proc+ or an array of symbols
22
+ # and +Proc+s.
23
+ def initialize(method_or_proc, **options)
24
+ @method_or_proc = method_or_proc
25
+
26
+ @if, @unless =
27
+ %i[if unless].map do |key|
28
+ value = options[key]
29
+ Array.wrap(value) if value
30
+ end
31
+
32
+ @only, @except =
33
+ %i[only except].map do |key|
34
+ value = options[key]
35
+ Array.wrap(value).map(&:to_s) if value
36
+ end
37
+ end
38
+
39
+ def inspect # :nodoc:
40
+ "#<#{self.class} #{@method_or_proc.inspect}#{
41
+ {
42
+ if: @if,
43
+ unless: @unless,
44
+ only: @only,
45
+ except: @except
46
+ }.filter_map do |key, value|
47
+ next if value.nil?
48
+
49
+ value = value.sole if value.one?
50
+ ", #{key}: #{value.inspect}"
51
+ end.join
52
+ }>"
53
+ end
54
+
55
+ # Returns true if and only if the callback must not be triggered on
56
+ # +controller+ and +operation_name+.
57
+ def skip_on?(controller, operation_name)
58
+ operation_name = operation_name.to_s
59
+
60
+ @only&.exclude?(operation_name) ||
61
+ @except&.include?(operation_name) ||
62
+ @if&.any? { |condition| !evaluate(condition, controller) } ||
63
+ @unless&.any? { |condition| evaluate(condition, controller) } ||
64
+ false
65
+ end
66
+
67
+ private
68
+
69
+ def evaluate(condition, context)
70
+ if condition.is_a?(Proc)
71
+ context.instance_exec(&condition)
72
+ else
73
+ context.send(condition)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module Controller
5
+ module Methods
6
+ module Callbacks
7
+ module ClassMethods
8
+ # :call-seq:
9
+ # api_after_validation(method_or_proc, **options)
10
+ # api_after_validation(**options, &block)
11
+ #
12
+ # Registers a callback that is triggered by +api_operation!+ after the
13
+ # parameters have been validated successfully.
14
+ #
15
+ # api_after_validation do |api_params|
16
+ # # ...
17
+ # end
18
+ #
19
+ # When calling an +api_after_validation+ callback, the parameters are
20
+ # passed.
21
+ def api_after_validation(method_or_proc = nil, **options, &block)
22
+ _api_add_callback(:after_validation, method_or_proc, **options, &block)
23
+ end
24
+
25
+ # :call-seq:
26
+ # api_before_rendering(method_or_proc, **options)
27
+ # api_before_rendering(**options, &block)
28
+ #
29
+ # Registers a callback that is triggered by +api_operation+ and
30
+ # +api_operation!+ before the response body is rendered.
31
+ #
32
+ # api_before_rendering do |result, api_params|
33
+ # { request_id: api_params.request_id, payload: result }
34
+ # end
35
+ #
36
+ # When calling an +api_before_rendering+ callback, the result and
37
+ # parameters are passed. The value returned by the callback replaces
38
+ # the result to be rendered.
39
+ def api_before_rendering(method_or_proc = nil, **options, &block)
40
+ _api_add_callback(:before_rendering, method_or_proc, **options, &block)
41
+ end
42
+
43
+ def _api_add_callback(kind, method_or_proc = nil, **options, &block) # :nodoc:
44
+ method_or_proc ||= block
45
+ raise ArgumentError, 'either a method or a block must be ' \
46
+ 'specified' unless method_or_proc
47
+
48
+ ((@_api_callbacks ||= {})[kind] ||= []) <<
49
+ Callback.new(method_or_proc, **options)
50
+ end
51
+
52
+ def _api_callbacks(kind) # :nodoc:
53
+ callbacks = @_api_callbacks&.fetch(kind, nil) || []
54
+ return callbacks unless superclass.respond_to?(:_api_callbacks)
55
+
56
+ superclass._api_callbacks(kind) + callbacks
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'callbacks/callback'
4
+ require_relative 'callbacks/class_methods'
5
+
6
+ module Jsapi
7
+ module Controller
8
+ module Methods
9
+ module Callbacks
10
+ def self.included(base) # :nodoc:
11
+ base.extend(ClassMethods)
12
+ end
13
+
14
+ private
15
+
16
+ def _api_callback(name, operation_name, *args)
17
+ operation_name = operation_name.to_s
18
+
19
+ self.class._api_callbacks(name).each do |callback|
20
+ next if callback.skip_on?(self, operation_name)
21
+
22
+ _api_exec(callback.method_or_proc, operation_name, *args)
23
+ end
24
+ nil
25
+ end
26
+
27
+ def _api_before_rendering(operation_name, result, *args)
28
+ operation_name = operation_name.to_s
29
+ args = [args, operation_name].flatten
30
+
31
+ self.class._api_callbacks(:before_rendering).reduce(result) do |memo, callback|
32
+ next memo if callback.skip_on?(self, operation_name)
33
+
34
+ _api_exec(callback.method_or_proc, memo, operation_name, *args)
35
+ end
36
+ end
37
+
38
+ def _api_exec(method_or_proc, *args)
39
+ if method_or_proc.is_a?(Proc)
40
+ arity = method_or_proc.arity
41
+ args = args.take(arity) if arity < args.count
42
+
43
+ instance_exec(*args, &method_or_proc)
44
+ else
45
+ arity = self.class.instance_method(method_or_proc).arity
46
+ args = args.take(arity) if arity < args.count
47
+
48
+ send(method_or_proc, *args)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end