apiwork 0.0.0.pre → 0.1.2

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 (202) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +2 -2
  3. data/README.md +117 -1
  4. data/Rakefile +5 -3
  5. data/app/controllers/apiwork/errors_controller.rb +13 -0
  6. data/app/controllers/apiwork/exports_controller.rb +22 -0
  7. data/lib/apiwork/abstractable.rb +26 -0
  8. data/lib/apiwork/adapter/base.rb +369 -0
  9. data/lib/apiwork/adapter/builder/api/base.rb +66 -0
  10. data/lib/apiwork/adapter/builder/contract/base.rb +86 -0
  11. data/lib/apiwork/adapter/capability/api/base.rb +51 -0
  12. data/lib/apiwork/adapter/capability/api/scope.rb +64 -0
  13. data/lib/apiwork/adapter/capability/base.rb +291 -0
  14. data/lib/apiwork/adapter/capability/contract/base.rb +37 -0
  15. data/lib/apiwork/adapter/capability/contract/scope.rb +110 -0
  16. data/lib/apiwork/adapter/capability/operation/base.rb +172 -0
  17. data/lib/apiwork/adapter/capability/operation/metadata_shape.rb +165 -0
  18. data/lib/apiwork/adapter/capability/result.rb +21 -0
  19. data/lib/apiwork/adapter/capability/runner.rb +56 -0
  20. data/lib/apiwork/adapter/capability/transformer/request/base.rb +72 -0
  21. data/lib/apiwork/adapter/capability/transformer/response/base.rb +45 -0
  22. data/lib/apiwork/adapter/registry.rb +16 -0
  23. data/lib/apiwork/adapter/serializer/error/base.rb +72 -0
  24. data/lib/apiwork/adapter/serializer/error/default/api_builder.rb +32 -0
  25. data/lib/apiwork/adapter/serializer/error/default.rb +37 -0
  26. data/lib/apiwork/adapter/serializer/resource/base.rb +84 -0
  27. data/lib/apiwork/adapter/serializer/resource/default/contract_builder.rb +209 -0
  28. data/lib/apiwork/adapter/serializer/resource/default.rb +39 -0
  29. data/lib/apiwork/adapter/standard/capability/filtering/api_builder.rb +75 -0
  30. data/lib/apiwork/adapter/standard/capability/filtering/constants.rb +37 -0
  31. data/lib/apiwork/adapter/standard/capability/filtering/contract_builder.rb +193 -0
  32. data/lib/apiwork/adapter/standard/capability/filtering/operation/filter/builder.rb +47 -0
  33. data/lib/apiwork/adapter/standard/capability/filtering/operation/filter/operator_builder.rb +36 -0
  34. data/lib/apiwork/adapter/standard/capability/filtering/operation/filter.rb +462 -0
  35. data/lib/apiwork/adapter/standard/capability/filtering/operation.rb +22 -0
  36. data/lib/apiwork/adapter/standard/capability/filtering/request_transformer.rb +47 -0
  37. data/lib/apiwork/adapter/standard/capability/filtering.rb +18 -0
  38. data/lib/apiwork/adapter/standard/capability/including/contract_builder.rb +169 -0
  39. data/lib/apiwork/adapter/standard/capability/including/operation.rb +20 -0
  40. data/lib/apiwork/adapter/standard/capability/including.rb +16 -0
  41. data/lib/apiwork/adapter/standard/capability/pagination/api_builder.rb +34 -0
  42. data/lib/apiwork/adapter/standard/capability/pagination/contract_builder.rb +35 -0
  43. data/lib/apiwork/adapter/standard/capability/pagination/operation/paginate/cursor.rb +84 -0
  44. data/lib/apiwork/adapter/standard/capability/pagination/operation/paginate/offset.rb +66 -0
  45. data/lib/apiwork/adapter/standard/capability/pagination/operation/paginate.rb +24 -0
  46. data/lib/apiwork/adapter/standard/capability/pagination/operation.rb +24 -0
  47. data/lib/apiwork/adapter/standard/capability/pagination.rb +21 -0
  48. data/lib/apiwork/adapter/standard/capability/sorting/api_builder.rb +19 -0
  49. data/lib/apiwork/adapter/standard/capability/sorting/contract_builder.rb +84 -0
  50. data/lib/apiwork/adapter/standard/capability/sorting/operation/sort.rb +83 -0
  51. data/lib/apiwork/adapter/standard/capability/sorting/operation.rb +22 -0
  52. data/lib/apiwork/adapter/standard/capability/sorting.rb +17 -0
  53. data/lib/apiwork/adapter/standard/capability/writing/constants.rb +15 -0
  54. data/lib/apiwork/adapter/standard/capability/writing/contract_builder.rb +253 -0
  55. data/lib/apiwork/adapter/standard/capability/writing/operation/issue_mapper.rb +210 -0
  56. data/lib/apiwork/adapter/standard/capability/writing/operation.rb +32 -0
  57. data/lib/apiwork/adapter/standard/capability/writing/request_transformer.rb +37 -0
  58. data/lib/apiwork/adapter/standard/capability/writing.rb +17 -0
  59. data/lib/apiwork/adapter/standard/includes_resolver.rb +106 -0
  60. data/lib/apiwork/adapter/standard.rb +22 -0
  61. data/lib/apiwork/adapter/wrapper/base.rb +70 -0
  62. data/lib/apiwork/adapter/wrapper/collection/base.rb +60 -0
  63. data/lib/apiwork/adapter/wrapper/collection/default.rb +47 -0
  64. data/lib/apiwork/adapter/wrapper/error/base.rb +30 -0
  65. data/lib/apiwork/adapter/wrapper/error/default.rb +34 -0
  66. data/lib/apiwork/adapter/wrapper/member/base.rb +58 -0
  67. data/lib/apiwork/adapter/wrapper/member/default.rb +40 -0
  68. data/lib/apiwork/adapter/wrapper/shape.rb +203 -0
  69. data/lib/apiwork/adapter.rb +50 -0
  70. data/lib/apiwork/api/base.rb +802 -0
  71. data/lib/apiwork/api/element.rb +110 -0
  72. data/lib/apiwork/api/enum_registry/definition.rb +51 -0
  73. data/lib/apiwork/api/enum_registry.rb +98 -0
  74. data/lib/apiwork/api/info/contact.rb +67 -0
  75. data/lib/apiwork/api/info/license.rb +50 -0
  76. data/lib/apiwork/api/info/server.rb +50 -0
  77. data/lib/apiwork/api/info.rb +221 -0
  78. data/lib/apiwork/api/object.rb +235 -0
  79. data/lib/apiwork/api/registry.rb +33 -0
  80. data/lib/apiwork/api/representation_registry.rb +76 -0
  81. data/lib/apiwork/api/resource/action.rb +41 -0
  82. data/lib/apiwork/api/resource.rb +648 -0
  83. data/lib/apiwork/api/router.rb +104 -0
  84. data/lib/apiwork/api/type_registry/definition.rb +117 -0
  85. data/lib/apiwork/api/type_registry.rb +99 -0
  86. data/lib/apiwork/api/union.rb +49 -0
  87. data/lib/apiwork/api.rb +85 -0
  88. data/lib/apiwork/configurable.rb +71 -0
  89. data/lib/apiwork/configuration/option.rb +125 -0
  90. data/lib/apiwork/configuration/validatable.rb +25 -0
  91. data/lib/apiwork/configuration.rb +95 -0
  92. data/lib/apiwork/configuration_error.rb +6 -0
  93. data/lib/apiwork/constraint_error.rb +20 -0
  94. data/lib/apiwork/contract/action/request.rb +79 -0
  95. data/lib/apiwork/contract/action/response.rb +87 -0
  96. data/lib/apiwork/contract/action.rb +258 -0
  97. data/lib/apiwork/contract/base.rb +714 -0
  98. data/lib/apiwork/contract/element.rb +130 -0
  99. data/lib/apiwork/contract/object/coercer.rb +194 -0
  100. data/lib/apiwork/contract/object/deserializer.rb +101 -0
  101. data/lib/apiwork/contract/object/transformer.rb +95 -0
  102. data/lib/apiwork/contract/object/validator/result.rb +27 -0
  103. data/lib/apiwork/contract/object/validator.rb +734 -0
  104. data/lib/apiwork/contract/object.rb +566 -0
  105. data/lib/apiwork/contract/request_parser/result.rb +25 -0
  106. data/lib/apiwork/contract/request_parser.rb +72 -0
  107. data/lib/apiwork/contract/response_parser/result.rb +25 -0
  108. data/lib/apiwork/contract/response_parser.rb +35 -0
  109. data/lib/apiwork/contract/union.rb +56 -0
  110. data/lib/apiwork/contract_error.rb +9 -0
  111. data/lib/apiwork/controller.rb +300 -0
  112. data/lib/apiwork/domain_error.rb +13 -0
  113. data/lib/apiwork/element.rb +386 -0
  114. data/lib/apiwork/engine.rb +20 -0
  115. data/lib/apiwork/error.rb +6 -0
  116. data/lib/apiwork/error_code/definition.rb +63 -0
  117. data/lib/apiwork/error_code/registry.rb +18 -0
  118. data/lib/apiwork/error_code.rb +132 -0
  119. data/lib/apiwork/export/base.rb +291 -0
  120. data/lib/apiwork/export/open_api.rb +600 -0
  121. data/lib/apiwork/export/pipeline/writer.rb +66 -0
  122. data/lib/apiwork/export/pipeline.rb +84 -0
  123. data/lib/apiwork/export/registry.rb +16 -0
  124. data/lib/apiwork/export/surface_resolver.rb +189 -0
  125. data/lib/apiwork/export/type_analysis.rb +170 -0
  126. data/lib/apiwork/export/type_script.rb +23 -0
  127. data/lib/apiwork/export/type_script_mapper.rb +349 -0
  128. data/lib/apiwork/export/zod.rb +39 -0
  129. data/lib/apiwork/export/zod_mapper.rb +421 -0
  130. data/lib/apiwork/export.rb +80 -0
  131. data/lib/apiwork/http_error.rb +16 -0
  132. data/lib/apiwork/introspection/action/request.rb +66 -0
  133. data/lib/apiwork/introspection/action/response.rb +57 -0
  134. data/lib/apiwork/introspection/action.rb +124 -0
  135. data/lib/apiwork/introspection/api/info/contact.rb +59 -0
  136. data/lib/apiwork/introspection/api/info/license.rb +49 -0
  137. data/lib/apiwork/introspection/api/info/server.rb +50 -0
  138. data/lib/apiwork/introspection/api/info.rb +107 -0
  139. data/lib/apiwork/introspection/api/resource.rb +83 -0
  140. data/lib/apiwork/introspection/api.rb +92 -0
  141. data/lib/apiwork/introspection/contract.rb +63 -0
  142. data/lib/apiwork/introspection/dump/action.rb +101 -0
  143. data/lib/apiwork/introspection/dump/api.rb +119 -0
  144. data/lib/apiwork/introspection/dump/contract.rb +129 -0
  145. data/lib/apiwork/introspection/dump/param.rb +486 -0
  146. data/lib/apiwork/introspection/dump/resource.rb +112 -0
  147. data/lib/apiwork/introspection/dump/type.rb +339 -0
  148. data/lib/apiwork/introspection/dump.rb +17 -0
  149. data/lib/apiwork/introspection/enum.rb +63 -0
  150. data/lib/apiwork/introspection/error_code.rb +44 -0
  151. data/lib/apiwork/introspection/param/array.rb +88 -0
  152. data/lib/apiwork/introspection/param/base.rb +285 -0
  153. data/lib/apiwork/introspection/param/binary.rb +73 -0
  154. data/lib/apiwork/introspection/param/boolean.rb +73 -0
  155. data/lib/apiwork/introspection/param/date.rb +73 -0
  156. data/lib/apiwork/introspection/param/date_time.rb +73 -0
  157. data/lib/apiwork/introspection/param/decimal.rb +121 -0
  158. data/lib/apiwork/introspection/param/integer.rb +131 -0
  159. data/lib/apiwork/introspection/param/literal.rb +45 -0
  160. data/lib/apiwork/introspection/param/number.rb +121 -0
  161. data/lib/apiwork/introspection/param/object.rb +59 -0
  162. data/lib/apiwork/introspection/param/reference.rb +45 -0
  163. data/lib/apiwork/introspection/param/string.rb +122 -0
  164. data/lib/apiwork/introspection/param/time.rb +73 -0
  165. data/lib/apiwork/introspection/param/union.rb +57 -0
  166. data/lib/apiwork/introspection/param/unknown.rb +26 -0
  167. data/lib/apiwork/introspection/param/uuid.rb +73 -0
  168. data/lib/apiwork/introspection/param.rb +31 -0
  169. data/lib/apiwork/introspection/type.rb +129 -0
  170. data/lib/apiwork/introspection.rb +28 -0
  171. data/lib/apiwork/issue.rb +80 -0
  172. data/lib/apiwork/json_pointer.rb +21 -0
  173. data/lib/apiwork/object.rb +1618 -0
  174. data/lib/apiwork/reference_generator.rb +638 -0
  175. data/lib/apiwork/registry.rb +56 -0
  176. data/lib/apiwork/representation/association.rb +391 -0
  177. data/lib/apiwork/representation/attribute.rb +335 -0
  178. data/lib/apiwork/representation/base.rb +819 -0
  179. data/lib/apiwork/representation/deserializer.rb +95 -0
  180. data/lib/apiwork/representation/element.rb +128 -0
  181. data/lib/apiwork/representation/inheritance.rb +78 -0
  182. data/lib/apiwork/representation/model_detector.rb +75 -0
  183. data/lib/apiwork/representation/root_key.rb +35 -0
  184. data/lib/apiwork/representation/serializer.rb +127 -0
  185. data/lib/apiwork/request.rb +79 -0
  186. data/lib/apiwork/response.rb +56 -0
  187. data/lib/apiwork/union.rb +102 -0
  188. data/lib/apiwork/version.rb +2 -2
  189. data/lib/apiwork.rb +61 -3
  190. data/lib/generators/apiwork/api_generator.rb +38 -0
  191. data/lib/generators/apiwork/contract_generator.rb +25 -0
  192. data/lib/generators/apiwork/install_generator.rb +27 -0
  193. data/lib/generators/apiwork/representation_generator.rb +25 -0
  194. data/lib/generators/apiwork/templates/api/api.rb.tt +4 -0
  195. data/lib/generators/apiwork/templates/contract/contract.rb.tt +6 -0
  196. data/lib/generators/apiwork/templates/install/application_contract.rb.tt +5 -0
  197. data/lib/generators/apiwork/templates/install/application_representation.rb.tt +5 -0
  198. data/lib/generators/apiwork/templates/representation/representation.rb.tt +6 -0
  199. data/lib/tasks/apiwork.rake +102 -0
  200. metadata +319 -19
  201. data/.rubocop.yml +0 -8
  202. data/sig/apiwork.rbs +0 -4
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Contract
5
+ class ResponseParser
6
+ class << self
7
+ def parse(contract_class, action_name, response)
8
+ new(contract_class, action_name).parse(response)
9
+ end
10
+ end
11
+
12
+ def initialize(contract_class, action_name)
13
+ @contract_class = contract_class
14
+ @action_name = action_name.to_sym
15
+ end
16
+
17
+ def parse(response)
18
+ return Result.new(response:) unless action
19
+
20
+ shape = action.response.body
21
+ return Result.new(response:) unless shape
22
+ return Result.new(response:) unless shape.params.any?
23
+
24
+ validated = shape.validate(response.body)
25
+ Result.new(issues: validated.issues, response: Response.new(body: validated.params))
26
+ end
27
+
28
+ private
29
+
30
+ def action
31
+ @action ||= @contract_class.action_for(@action_name)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Contract
5
+ # @api public
6
+ # Block context for defining inline union types.
7
+ #
8
+ # Accessed via `union :name, discriminator: do` inside contract actions.
9
+ # Use {#variant} to define possible types.
10
+ #
11
+ # @see API::Union Block context for reusable unions
12
+ # @see Contract::Element Block context for variant types
13
+ #
14
+ # @example instance_eval style
15
+ # union :payment_method, discriminator: :type do
16
+ # variant tag: 'card' do
17
+ # object do
18
+ # string :last_four
19
+ # end
20
+ # end
21
+ # variant tag: 'bank' do
22
+ # object do
23
+ # string :account_number
24
+ # end
25
+ # end
26
+ # end
27
+ #
28
+ # @example yield style
29
+ # union :payment_method, discriminator: :type do |union|
30
+ # union.variant tag: 'card' do |variant|
31
+ # variant.object do |object|
32
+ # object.string :last_four
33
+ # end
34
+ # end
35
+ # union.variant tag: 'bank' do |variant|
36
+ # variant.object do |object|
37
+ # object.string :account_number
38
+ # end
39
+ # end
40
+ # end
41
+ class Union < Apiwork::Union
42
+ attr_reader :contract_class
43
+
44
+ def initialize(contract_class, discriminator: nil)
45
+ super(discriminator:)
46
+ @contract_class = contract_class
47
+ end
48
+
49
+ private
50
+
51
+ def build_element
52
+ Element.new(@contract_class)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ class ContractError < ConstraintError
5
+ def layer
6
+ :contract
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,300 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ # @api public
5
+ # Mixin for API controllers that provides request validation and response helpers.
6
+ #
7
+ # Include in controllers to access {#contract}, {#expose}, and {#expose_error}.
8
+ # Automatically validates requests against the contract before actions run.
9
+ #
10
+ # @example Basic controller
11
+ # class InvoicesController < ApplicationController
12
+ # include Apiwork::Controller
13
+ #
14
+ # def index
15
+ # expose Invoice.all
16
+ # end
17
+ #
18
+ # def show
19
+ # invoice = Invoice.find(params[:id])
20
+ # expose invoice
21
+ # end
22
+ #
23
+ # def create
24
+ # invoice = Invoice.create(contract.body[:invoice])
25
+ # expose invoice
26
+ # end
27
+ # end
28
+ module Controller
29
+ extend ActiveSupport::Concern
30
+
31
+ included do
32
+ wrap_parameters false
33
+
34
+ before_action :validate_contract
35
+
36
+ rescue_from ConstraintError do |error|
37
+ render_error error
38
+ end
39
+ end
40
+
41
+ # @!method self.skip_contract_validation!(only: nil, except: nil)
42
+ # @api public
43
+ # Skips contract validation for specified actions.
44
+ #
45
+ # @param except [Array<Symbol>]
46
+ # Skip for all except these actions.
47
+ # @param only [Array<Symbol>]
48
+ # Skip only for these actions.
49
+ #
50
+ # @example Skip for specific actions
51
+ # skip_contract_validation! only: [:ping, :status]
52
+ #
53
+ # @example Skip for all actions
54
+ # skip_contract_validation!
55
+ class_methods do
56
+ def skip_contract_validation!(except: nil, only: nil)
57
+ skip_before_action :validate_contract, except:, only:
58
+ end
59
+ end
60
+
61
+ # @api public
62
+ # The contract for this controller.
63
+ #
64
+ # Contains parsed query parameters and request body with type coercion applied.
65
+ # Access parameters via {Contract::Base#query} and {Contract::Base#body}.
66
+ #
67
+ # @return [Contract::Base]
68
+ # @see Contract::Base
69
+ #
70
+ # @example Access parsed parameters
71
+ # def create
72
+ # invoice = Invoice.new(contract.body)
73
+ # # contract.body contains validated, coerced params
74
+ # end
75
+ #
76
+ # @example Check for specific parameters
77
+ # def index
78
+ # if contract.query[:include]
79
+ # # handle include parameter
80
+ # end
81
+ # end
82
+ def contract
83
+ @contract ||= begin
84
+ api_request = Request.new(
85
+ body: request.request_parameters,
86
+ query: request.query_parameters,
87
+ ).transform(&:deep_symbolize_keys)
88
+ contract_class.new(action_name, api_request, coerce: true)
89
+ end
90
+ end
91
+
92
+ # @api public
93
+ # Exposes data as an API response.
94
+ #
95
+ # When a representation is linked via {Contract::Base.representation}, data is serialized
96
+ # through the representation. Otherwise, data is rendered as-is. Key transformation
97
+ # is applied according to the API's {API::Base.key_format}.
98
+ #
99
+ # @param data [Object, Array]
100
+ # The record(s) to expose.
101
+ # @param meta [Hash] ({})
102
+ # The metadata to include in response (pagination, etc.).
103
+ # @param status [Symbol, Integer, nil] (nil)
104
+ # The HTTP status (:ok, or :created for create action).
105
+ # @see Representation::Base
106
+ #
107
+ # @example Expose a single record
108
+ # def show
109
+ # invoice = Invoice.find(params[:id])
110
+ # expose invoice
111
+ # end
112
+ #
113
+ # @example Expose a collection with metadata
114
+ # def index
115
+ # invoices = Invoice.all
116
+ # expose invoices, meta: { total: invoices.count }
117
+ # end
118
+ #
119
+ # @example Custom status
120
+ # def create
121
+ # invoice = Invoice.create(contract.body[:invoice])
122
+ # expose invoice, status: :created
123
+ # end
124
+ def expose(data, meta: {}, status: nil)
125
+ if contract_class.actions[action_name.to_sym]&.response&.no_content?
126
+ head :no_content
127
+ return
128
+ end
129
+
130
+ representation_class = contract_class.representation_class
131
+
132
+ body = if representation_class
133
+ action = resource.actions[action_name.to_sym]
134
+ if action.collection?
135
+ adapter.process_collection(data, representation_class, contract.request, context:, meta:)
136
+ else
137
+ adapter.process_member(data, representation_class, contract.request, context:, meta:)
138
+ end
139
+ else
140
+ data[:meta] = meta if meta.present?
141
+ data
142
+ end
143
+
144
+ response = Response.new(body:)
145
+
146
+ if Rails.env.development?
147
+ result = contract_class.parse_response(response, action_name)
148
+ result.issues.each { |issue| Rails.logger.warn(issue.to_s) }
149
+ end
150
+
151
+ response = api_class.prepare_response(response)
152
+
153
+ render json: response.body, status: status || (action_name.to_sym == :create ? :created : :ok)
154
+ end
155
+
156
+ # @api public
157
+ # Exposes an error response using a registered error code.
158
+ #
159
+ # Defaults to I18n lookup when detail is not provided.
160
+ #
161
+ # @param code_key [Symbol]
162
+ # The registered error code (:not_found, :unauthorized, etc.).
163
+ # @param detail [String, nil] (nil)
164
+ # The custom error message (uses I18n lookup if nil).
165
+ # @param meta [Hash] ({})
166
+ # The additional metadata to include.
167
+ # @param path [Array<String, Symbol>, nil] (nil)
168
+ # The JSON path to the error.
169
+ # @see ErrorCode
170
+ # @see Issue
171
+ #
172
+ # @example Not found error
173
+ # def show
174
+ # invoice = Invoice.find_by(id: params[:id])
175
+ # return expose_error :not_found unless invoice
176
+ # expose invoice
177
+ # end
178
+ #
179
+ # @example With custom message
180
+ # expose_error :forbidden, detail: 'You cannot access this invoice'
181
+ def expose_error(
182
+ code_key,
183
+ detail: nil,
184
+ path: nil,
185
+ meta: {}
186
+ )
187
+ error_code = ErrorCode.find!(code_key)
188
+
189
+ issue = Issue.new(
190
+ error_code.key,
191
+ detail || error_code.description(locale_key: api_class.locale_key),
192
+ meta:,
193
+ path: path || (error_code.attach_path? ? relative_path.split('/').reject(&:blank?) : []),
194
+ )
195
+
196
+ render_error HttpError.new([issue], error_code)
197
+ end
198
+
199
+ # @api public
200
+ # The context for this controller.
201
+ #
202
+ # Passed to representations during serialization. Override to provide
203
+ # current user, permissions, locale, or feature flags.
204
+ #
205
+ # @return [Hash]
206
+ #
207
+ # @example Provide current user context
208
+ # def context
209
+ # { current_user: current_user }
210
+ # end
211
+ #
212
+ # @example Access context in representation
213
+ # class InvoiceRepresentation < Apiwork::Representation::Base
214
+ # attribute :editable, type: :boolean
215
+ #
216
+ # def editable
217
+ # context[:current_user].admin?
218
+ # end
219
+ # end
220
+ def context
221
+ {}
222
+ end
223
+
224
+ private
225
+
226
+ def validate_contract
227
+ return unless resource
228
+ return if contract.valid?
229
+
230
+ raise ContractError, contract.issues
231
+ end
232
+
233
+ def render_error(error)
234
+ representation_class = resource ? contract_class.representation_class : nil
235
+ json = adapter.process_error(error, representation_class, context:)
236
+ render json:, status: error.status
237
+ end
238
+
239
+ def contract_class
240
+ @contract_class ||= begin
241
+ klass = resource&.resolve_contract_class
242
+ klass || raise_contract_not_found_error
243
+ end
244
+ end
245
+
246
+ def api_class
247
+ @api_class ||= find_api_class || raise_api_not_found_error
248
+ end
249
+
250
+ def adapter
251
+ api_class.adapter
252
+ end
253
+
254
+ def resource
255
+ @resource ||= api_class.root_resource.find_resource_for_path(relative_path)
256
+ end
257
+
258
+ def relative_path
259
+ @relative_path ||= request.path.delete_prefix(api_class.base_path)
260
+ end
261
+
262
+ def raise_api_not_found_error
263
+ path = path_parts.empty? ? '/' : "/#{path_parts[0..1].join('/')}"
264
+ api_file = "config/apis/#{path.split('/').reject(&:blank?).join('_')}.rb"
265
+
266
+ raise ConfigurationError,
267
+ "No API found for #{self.class.name}. " \
268
+ "Create the API: #{api_file} (Apiwork::API.define '#{path}')"
269
+ end
270
+
271
+ def raise_contract_not_found_error
272
+ resource_base = resource.name.to_s.singularize
273
+ namespaces = api_class.namespaces
274
+
275
+ contract_name = [*namespaces.map { |namespace| namespace.to_s.camelize }, "#{resource_base.camelize}Contract"].join('::')
276
+ contract_path = ['app/contracts', *namespaces, "#{resource_base}_contract.rb"].join('/')
277
+
278
+ raise ConfigurationError,
279
+ "No contract found for #{self.class.name}. " \
280
+ "Create the contract: #{contract_path} (#{contract_name})"
281
+ end
282
+
283
+ def find_api_class
284
+ parts = path_parts
285
+ return API.find('/') if parts.empty?
286
+
287
+ (parts.length - 1).downto(1) do |index|
288
+ base_path = "/#{parts[0...index].join('/')}"
289
+ api_class = API.find(base_path)
290
+ return api_class if api_class
291
+ end
292
+
293
+ nil
294
+ end
295
+
296
+ def path_parts
297
+ @path_parts ||= request.path.split('/').reject(&:blank?)
298
+ end
299
+ end
300
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ class DomainError < ConstraintError
5
+ def layer
6
+ :domain
7
+ end
8
+
9
+ def error_code
10
+ @error_code ||= ErrorCode.find!(:unprocessable_entity)
11
+ end
12
+ end
13
+ end