apiwork 0.0.0.pre → 0.1.1

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 +622 -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,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ module Builder
6
+ module Contract
7
+ # @api public
8
+ # Base class for Contract-phase type builders.
9
+ #
10
+ # Contract phase runs once per contract with representation at registration time.
11
+ # Use it to generate contract-specific types based on the representation.
12
+ #
13
+ # @example
14
+ # module Builder
15
+ # class Contract < Adapter::Builder::Contract::Base
16
+ # def build
17
+ # object(representation_class.root_key.singular.to_sym) do |object|
18
+ # object.string(:id)
19
+ # object.string(:name)
20
+ # end
21
+ # end
22
+ # end
23
+ # end
24
+ class Base
25
+ attr_reader :representation_class
26
+
27
+ # @!method api_class
28
+ # @api public
29
+ # @see Contract::Base.api_class
30
+ # @!method enum(name, values:, **options, &block)
31
+ # @api public
32
+ # @see Contract::Base#enum
33
+ # @!method enum?(name)
34
+ # @api public
35
+ # @see Contract::Base#enum?
36
+ # @!method contract_for(representation_class)
37
+ # @api public
38
+ # @see Contract::Base.contract_for
39
+ # @!method import(type_name, from:)
40
+ # @api public
41
+ # @see Contract::Base#import
42
+ # @!method object(name, **options, &block)
43
+ # @api public
44
+ # @see Contract::Base#object
45
+ # @!method scoped_enum_name(name)
46
+ # @api public
47
+ # @see Contract::Base#scoped_enum_name
48
+ # @!method scoped_type_name(name)
49
+ # @api public
50
+ # @see Contract::Base#scoped_type_name
51
+ # @!method type?(name)
52
+ # @api public
53
+ # @see Contract::Base#type?
54
+ # @!method union(name, **options, &block)
55
+ # @api public
56
+ # @see Contract::Base#union
57
+ delegate :api_class,
58
+ :contract_for,
59
+ :enum,
60
+ :enum?,
61
+ :import,
62
+ :object,
63
+ :scoped_enum_name,
64
+ :scoped_type_name,
65
+ :type?,
66
+ :union,
67
+ to: :@contract_class
68
+
69
+ def initialize(contract_class, representation_class)
70
+ @contract_class = contract_class
71
+ @representation_class = representation_class
72
+ end
73
+
74
+ # @api public
75
+ # Builds contract-level types.
76
+ #
77
+ # Override this method to generate types based on the representation.
78
+ # @return [void]
79
+ def build
80
+ raise NotImplementedError
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ module Capability
6
+ module API
7
+ # @api public
8
+ # Base class for capability API builders.
9
+ #
10
+ # Provides access to capability options and aggregated configuration
11
+ # across all representations.
12
+ class Base < Builder::API::Base
13
+ # @!attribute [r] scope
14
+ # @api public
15
+ # The scope for this API.
16
+ #
17
+ # @return [Scope]
18
+ # @!attribute [r] options
19
+ # @api public
20
+ # The options for this API.
21
+ #
22
+ # @return [Configuration]
23
+ attr_reader :options,
24
+ :scope
25
+
26
+ def initialize(api_class, capability_name: nil, options: nil)
27
+ super(api_class)
28
+ @capability_name = capability_name
29
+ @scope = Scope.new(api_class)
30
+ @options = options
31
+ end
32
+
33
+ # @api public
34
+ # The configured values for a key.
35
+ #
36
+ # @param key [Symbol]
37
+ # The configuration key.
38
+ # @return [Set]
39
+ #
40
+ # @example Check if any representation uses cursor pagination
41
+ # if configured(:strategy).include?(:cursor)
42
+ # # build cursor pagination schema
43
+ # end
44
+ def configured(key)
45
+ scope.configured(@capability_name, key)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ module Capability
6
+ module API
7
+ # @api public
8
+ # Aggregated scope for capability API builders.
9
+ #
10
+ # Provides access to data collected across all representations in the API.
11
+ # Use this to query API-wide state when building shared types.
12
+ class Scope
13
+ def initialize(api_class)
14
+ @representation_registry = api_class.representation_registry
15
+ @root_resource = api_class.root_resource
16
+ end
17
+
18
+ # @!method index_actions?
19
+ # @api public
20
+ # Returns whether any resource has index actions.
21
+ # @return [Boolean]
22
+ delegate :index_actions?, to: :@root_resource
23
+
24
+ # @!method filter_types
25
+ # @api public
26
+ # Returns all filterable types across representations.
27
+ # @return [Set<Symbol>]
28
+ #
29
+ # @!method nullable_filter_types
30
+ # @api public
31
+ # Returns filterable types that can be null.
32
+ # @return [Set<Symbol>]
33
+ #
34
+ # @!method filterable?
35
+ # @api public
36
+ # Returns whether any representation has filterable attributes.
37
+ # @return [Boolean]
38
+ #
39
+ # @!method sortable?
40
+ # @api public
41
+ # Returns whether any representation has sortable attributes.
42
+ # @return [Boolean]
43
+ delegate :filter_types,
44
+ :filterable?,
45
+ :nullable_filter_types,
46
+ :sortable?,
47
+ to: :@representation_registry
48
+
49
+ # @api public
50
+ # The configured values for a capability.
51
+ #
52
+ # @param capability [Symbol]
53
+ # The capability name.
54
+ # @param key [Symbol]
55
+ # The configuration key.
56
+ # @return [Set]
57
+ def configured(capability, key)
58
+ @representation_registry.options_for(capability, key)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,291 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ module Capability
6
+ # @api public
7
+ # Base class for adapter capabilities.
8
+ #
9
+ # A capability encapsulates a specific feature (filtering, pagination, sorting)
10
+ # with its own configuration, transformers, builders, and operations. While each
11
+ # capability is self-contained, all capabilities operate on the same response data
12
+ # in sequence, so their effects combine.
13
+ #
14
+ # @see Adapter::Base#capability
15
+ # @see Configurable#option
16
+ #
17
+ # @example Filtering capability
18
+ # class Filtering < Adapter::Capability::Base
19
+ # capability_name :filtering
20
+ #
21
+ # option :strategy, type: :symbol, default: :simple
22
+ #
23
+ # request_transformer RequestTransformer
24
+ # api_builder APIBuilder
25
+ # contract_builder ContractBuilder
26
+ # operation Operation
27
+ # end
28
+ class Base
29
+ include Configurable
30
+
31
+ class_attribute :_api_builder
32
+ class_attribute :_api_builder_block
33
+ class_attribute :_contract_builder
34
+ class_attribute :_contract_builder_block
35
+ class_attribute :_operation_class
36
+ class_attribute :_operation_block
37
+
38
+ class << self
39
+ # @api public
40
+ # The name for this capability.
41
+ #
42
+ # Used for configuration options, translation keys, and {Adapter::Base.skip_capability}.
43
+ #
44
+ # @param value [Symbol, nil] (nil)
45
+ # The capability name.
46
+ # @return [Symbol, nil]
47
+ def capability_name(value = nil)
48
+ @capability_name = value.to_sym if value
49
+ @capability_name
50
+ end
51
+
52
+ # @api public
53
+ # Registers a request transformer for this capability.
54
+ #
55
+ # @param transformer_class [Class<Transformer::Request::Base>]
56
+ # The transformer class.
57
+ # @return [void]
58
+ # @see Transformer::Request::Base
59
+ def request_transformer(transformer_class)
60
+ @request_transformers ||= []
61
+ @request_transformers << transformer_class
62
+ end
63
+
64
+ # @api public
65
+ # Registers a response transformer for this capability.
66
+ #
67
+ # @param transformer_class [Class<Transformer::Response::Base>]
68
+ # The transformer class.
69
+ # @return [void]
70
+ # @see Transformer::Response::Base
71
+ def response_transformer(transformer_class)
72
+ @response_transformers ||= []
73
+ @response_transformers << transformer_class
74
+ end
75
+
76
+ # @api public
77
+ # Registers an API builder for this capability.
78
+ #
79
+ # API builders run once per API at initialization time to register
80
+ # shared types used across all contracts.
81
+ #
82
+ # @param klass [Class<Builder::API::Base>, nil] (nil)
83
+ # The builder class.
84
+ # @yield block evaluated in {Builder::API::Base} context
85
+ # @return [void]
86
+ # @see Builder::API::Base
87
+ def api_builder(klass = nil, &block)
88
+ if klass
89
+ self._api_builder = klass
90
+ elsif block
91
+ self._api_builder_block = block
92
+ end
93
+ end
94
+
95
+ # @api public
96
+ # Registers a contract builder for this capability.
97
+ #
98
+ # Contract builders run per contract to add capability-specific
99
+ # parameters and response shapes.
100
+ #
101
+ # @param klass [Class<Builder::Contract::Base>, nil] (nil)
102
+ # The builder class.
103
+ # @yield block evaluated in {Builder::Contract::Base} context
104
+ # @return [void]
105
+ # @see Builder::Contract::Base
106
+ def contract_builder(klass = nil, &block)
107
+ if klass
108
+ self._contract_builder = klass
109
+ elsif block
110
+ self._contract_builder_block = block
111
+ end
112
+ end
113
+
114
+ # @api public
115
+ # Registers an operation for this capability.
116
+ #
117
+ # Operations run at request time to process data based on
118
+ # request parameters.
119
+ #
120
+ # @param klass [Class<Operation::Base>, nil] (nil)
121
+ # The operation class.
122
+ # @yield block evaluated in {Operation::Base} context
123
+ # @return [void]
124
+ # @see Operation::Base
125
+ def operation(klass = nil, &block)
126
+ if klass
127
+ self._operation_class = klass
128
+ elsif block
129
+ self._operation_block = block
130
+ end
131
+ end
132
+
133
+ def request_transformers
134
+ @request_transformers || []
135
+ end
136
+
137
+ def response_transformers
138
+ @response_transformers || []
139
+ end
140
+
141
+ def wrap_api_builder_block(callable)
142
+ Class.new(Builder::API::Base) do
143
+ define_method(:build) do
144
+ callable.arity.positive? ? callable.call(self) : instance_exec(&callable)
145
+ end
146
+ end
147
+ end
148
+
149
+ def wrap_contract_builder_block(callable)
150
+ Class.new(Builder::Contract::Base) do
151
+ define_method(:build) do
152
+ callable.arity.positive? ? callable.call(self) : instance_exec(&callable)
153
+ end
154
+ end
155
+ end
156
+
157
+ def wrap_operation_block(callable)
158
+ Class.new(Operation::Base) do
159
+ define_method(:apply) do
160
+ callable.arity.positive? ? callable.call(self) : instance_exec(&callable)
161
+ end
162
+ end
163
+ end
164
+
165
+ def api_builder_class
166
+ return _api_builder if _api_builder
167
+ return wrap_api_builder_block(_api_builder_block) if _api_builder_block
168
+
169
+ nil
170
+ end
171
+
172
+ def contract_builder_class
173
+ return _contract_builder if _contract_builder
174
+ return wrap_contract_builder_block(_contract_builder_block) if _contract_builder_block
175
+
176
+ nil
177
+ end
178
+
179
+ def operation_class
180
+ return _operation_class if _operation_class
181
+ return wrap_operation_block(_operation_block) if _operation_block
182
+
183
+ nil
184
+ end
185
+ end
186
+
187
+ attr_reader :adapter_name,
188
+ :config
189
+
190
+ def initialize(config = {}, adapter_name: nil)
191
+ merged = self.class.default_options.deep_merge(config)
192
+ @config = Configuration.new(self.class, merged)
193
+ @adapter_name = adapter_name
194
+ end
195
+
196
+ def api_types(api_class)
197
+ builder_class = self.class.api_builder_class
198
+ return unless builder_class
199
+
200
+ builder_class.new(
201
+ api_class,
202
+ capability_name: self.class.capability_name,
203
+ options: config,
204
+ ).build
205
+ end
206
+
207
+ def contract_types(contract_class, representation_class, actions)
208
+ builder_class = self.class.contract_builder_class
209
+ return unless builder_class
210
+
211
+ builder_class.new(contract_class, representation_class, actions, merged_config(representation_class)).build
212
+ end
213
+
214
+ def shape(representation_class, type)
215
+ klass = self.class.operation_class
216
+ return nil unless klass
217
+
218
+ metadata_shape_class = klass.metadata_shape
219
+ return nil unless metadata_shape_class
220
+
221
+ scope = klass.target
222
+ return nil if scope && scope != type
223
+
224
+ object = ::Apiwork::API::Object.new
225
+ metadata_shape_class.apply(object, merged_config(representation_class))
226
+ return nil if object.params.empty?
227
+
228
+ type_name = resolve_metadata_type_name(representation_class)
229
+ register_metadata_fragment(
230
+ representation_class.api_class,
231
+ type_name,
232
+ metadata_shape_class,
233
+ merged_config(representation_class),
234
+ )
235
+ type_name
236
+ end
237
+
238
+ def apply(data, representation_class, request, wrapper_type:)
239
+ klass = self.class.operation_class
240
+ return Result.new(data:) unless klass
241
+
242
+ scope = klass.target
243
+ return Result.new(data:) if scope && scope != wrapper_type
244
+
245
+ klass.new(
246
+ data,
247
+ representation_class,
248
+ merged_config(representation_class),
249
+ request,
250
+ translation_context: build_translation_context(representation_class),
251
+ ).apply
252
+ end
253
+
254
+ private
255
+
256
+ def merged_config(representation_class)
257
+ capability_name = self.class.capability_name
258
+ return config unless capability_name
259
+
260
+ representation_config = representation_class.adapter_config.public_send(capability_name).to_h
261
+ config.merge(representation_config)
262
+ rescue ConfigurationError
263
+ config
264
+ end
265
+
266
+ def resolve_metadata_type_name(representation_class)
267
+ prefix = representation_class.name.demodulize.delete_suffix('Representation').underscore
268
+ [prefix, self.class.capability_name, 'metadata'].join('_').to_sym
269
+ end
270
+
271
+ def register_metadata_fragment(api_class, type_name, metadata_shape_class, config)
272
+ return if api_class.type_registry.key?(type_name)
273
+
274
+ api_class.type_registry.register(type_name, fragment: true, kind: :object) do |shape|
275
+ metadata_shape_class.apply(shape, config)
276
+ end
277
+ end
278
+
279
+ def build_translation_context(representation_class)
280
+ locale_key = representation_class.api_class.locale_key
281
+
282
+ {
283
+ locale_key:,
284
+ adapter_name: adapter_name,
285
+ capability_name: self.class.capability_name,
286
+ }
287
+ end
288
+ end
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ module Capability
6
+ module Contract
7
+ # @api public
8
+ # Base class for capability Contract phase.
9
+ #
10
+ # Contract phase runs once per contract with representation at registration time.
11
+ # Use it to generate contract-specific types based on the representation.
12
+ class Base < Builder::Contract::Base
13
+ # @!attribute [r] scope
14
+ # @api public
15
+ # The scope for this contract.
16
+ #
17
+ # @return [Scope]
18
+ # @!attribute [r] options
19
+ # @api public
20
+ # The options for this contract.
21
+ #
22
+ # @return [Configuration]
23
+ attr_reader :options,
24
+ :scope
25
+
26
+ delegate :action, to: :@contract_class
27
+
28
+ def initialize(contract_class, representation_class, actions, options)
29
+ super(contract_class, representation_class)
30
+ @scope = Scope.new(representation_class, actions)
31
+ @options = options
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ module Capability
6
+ module Contract
7
+ # @api public
8
+ # Scope for capability contract builders.
9
+ #
10
+ # Provides access to the representation and actions for this contract.
11
+ # Use this to query contract-specific state when building types.
12
+ class Scope
13
+ # @api public
14
+ # The actions for this scope.
15
+ #
16
+ # @return [Hash{Symbol => Resource::Action}]
17
+ attr_reader :actions
18
+
19
+ attr_reader :representation_class
20
+
21
+ def initialize(representation_class, actions)
22
+ @representation_class = representation_class
23
+ @actions = actions
24
+ end
25
+
26
+ # @api public
27
+ # The collection actions for this scope.
28
+ #
29
+ # @return [Hash{Symbol => Resource::Action}]
30
+ def collection_actions
31
+ @collection_actions ||= actions.select { |_name, action| action.collection? }
32
+ end
33
+
34
+ # @api public
35
+ # The member actions for this scope.
36
+ #
37
+ # @return [Hash{Symbol => Resource::Action}]
38
+ def member_actions
39
+ @member_actions ||= actions.select { |_name, action| action.member? }
40
+ end
41
+
42
+ # @api public
43
+ # The CRUD actions for this scope.
44
+ #
45
+ # @return [Hash{Symbol => Resource::Action}]
46
+ def crud_actions
47
+ @crud_actions ||= actions.select { |_name, action| action.crud? }
48
+ end
49
+
50
+ # @api public
51
+ # Whether this scope includes the given action.
52
+ #
53
+ # @param name [Symbol]
54
+ # The action name.
55
+ # @return [Boolean]
56
+ def action?(name)
57
+ actions.key?(name.to_sym)
58
+ end
59
+
60
+ # @api public
61
+ # The filterable attributes for this scope.
62
+ #
63
+ # @return [Array<Representation::Attribute>]
64
+ def filterable_attributes
65
+ @filterable_attributes ||= attributes.values.select(&:filterable?)
66
+ end
67
+
68
+ # @api public
69
+ # The sortable attributes for this scope.
70
+ #
71
+ # @return [Array<Representation::Attribute>]
72
+ def sortable_attributes
73
+ @sortable_attributes ||= attributes.values.select(&:sortable?)
74
+ end
75
+
76
+ # @api public
77
+ # The writable attributes for this scope.
78
+ #
79
+ # @return [Array<Representation::Attribute>]
80
+ def writable_attributes
81
+ @writable_attributes ||= attributes.values.select(&:writable?)
82
+ end
83
+
84
+ # @!method associations
85
+ # @api public
86
+ # The associations for this scope.
87
+ #
88
+ # @return [Hash{Symbol => Representation::Association}]
89
+ #
90
+ # @!method attributes
91
+ # @api public
92
+ # The attributes for this scope.
93
+ #
94
+ # @return [Hash{Symbol => Representation::Attribute}]
95
+ #
96
+ # @!method root_key
97
+ # @api public
98
+ # The root key for this scope.
99
+ #
100
+ # @return [Representation::RootKey]
101
+ delegate :adapter_config,
102
+ :associations,
103
+ :attributes,
104
+ :root_key,
105
+ to: :representation_class
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end