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,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ class Standard
6
+ class IncludesResolver
7
+ attr_reader :representation_class
8
+
9
+ class << self
10
+ def resolve(representation_class, params = {}, include_always: false)
11
+ new(representation_class).resolve(params, include_always:)
12
+ end
13
+ end
14
+
15
+ def initialize(representation_class)
16
+ @representation_class = representation_class
17
+ end
18
+
19
+ def resolve(params = {}, include_always: false)
20
+ base = include_always ? always_included : {}
21
+ merged = merge(base, from_params(params))
22
+ format(merged)
23
+ end
24
+
25
+ def always_included(visited = Set.new)
26
+ associations = representation_class.associations.select { |_, a| a.include == :always }
27
+ extract_associations(associations, visited)
28
+ end
29
+
30
+ def from_params(params, visited = Set.new)
31
+ extract_from_hash(params, visited)
32
+ end
33
+
34
+ def merge(base, override)
35
+ return base if override.blank?
36
+
37
+ base.deep_merge(override.deep_symbolize_keys)
38
+ end
39
+
40
+ def format(hash)
41
+ return [] if hash.blank?
42
+
43
+ result = hash.map do |key, value|
44
+ if value.blank?
45
+ key
46
+ else
47
+ { key => format(value) }
48
+ end
49
+ end
50
+
51
+ result.size == 1 ? result.first : result
52
+ end
53
+
54
+ private
55
+
56
+ def extract_associations(associations, visited = Set.new)
57
+ return {} if visited.include?(representation_class.name)
58
+
59
+ visited = visited.dup.add(representation_class.name)
60
+
61
+ associations.transform_values do |association|
62
+ if association.representation_class
63
+ self.class.new(association.representation_class).always_included(visited)
64
+ else
65
+ {}
66
+ end
67
+ end
68
+ end
69
+
70
+ def extract_from_hash(hash, visited = Set.new)
71
+ return {} if hash.blank?
72
+ return {} if visited.include?(representation_class.name)
73
+
74
+ visited_with_current = visited.dup.add(representation_class.name)
75
+
76
+ if hash.is_a?(Array)
77
+ return hash.each_with_object({}) do |item, result|
78
+ result.deep_merge!(extract_from_hash(item, visited))
79
+ end
80
+ end
81
+
82
+ hash.each_with_object({}) do |(key, value), result|
83
+ key = key.to_sym
84
+
85
+ if [Capability::Filtering::Constants::OR, Capability::Filtering::Constants::AND].include?(key) && value.is_a?(Array)
86
+ value.each { |item| result.deep_merge!(extract_from_hash(item, visited)) }
87
+ next
88
+ elsif key == Capability::Filtering::Constants::NOT && value.is_a?(Hash)
89
+ result.deep_merge!(extract_from_hash(value, visited))
90
+ next
91
+ end
92
+
93
+ association = representation_class.associations[key]
94
+ next unless association
95
+
96
+ result[key] = if value.is_a?(Hash) && association.representation_class.respond_to?(:associations)
97
+ self.class.new(association.representation_class).from_params(value, visited_with_current)
98
+ else
99
+ {}
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ class Standard < Base
6
+ adapter_name :standard
7
+
8
+ resource_serializer Serializer::Resource::Default
9
+ error_serializer Serializer::Error::Default
10
+
11
+ member_wrapper Wrapper::Member::Default
12
+ collection_wrapper Wrapper::Collection::Default
13
+ error_wrapper Wrapper::Error::Default
14
+
15
+ capability Capability::Filtering
16
+ capability Capability::Including
17
+ capability Capability::Pagination
18
+ capability Capability::Sorting
19
+ capability Capability::Writing
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ module Wrapper
6
+ class Base
7
+ class_attribute :shape_class
8
+ class_attribute :wrapper_type
9
+
10
+ # @api public
11
+ # The data for this wrapper.
12
+ #
13
+ # @return [Hash]
14
+ attr_reader :data
15
+
16
+ class << self
17
+ # @api public
18
+ # Defines the response shape for contract generation.
19
+ #
20
+ # @param klass_or_callable [Class<Shape>, Proc, nil] (nil)
21
+ # A {Shape} subclass or callable.
22
+ # @yield block that defines the shape using the Shape DSL
23
+ # @return [Class<Shape>, nil]
24
+ def shape(klass_or_callable = nil, &block)
25
+ callable = block || klass_or_callable
26
+
27
+ if callable
28
+ self.shape_class = if callable.respond_to?(:call)
29
+ wrap_callable(callable)
30
+ else
31
+ callable
32
+ end
33
+ end
34
+
35
+ shape_class
36
+ end
37
+
38
+ def wrap(...)
39
+ new(...).wrap
40
+ end
41
+
42
+ private
43
+
44
+ def wrap_callable(callable)
45
+ Class.new(Shape) do
46
+ define_singleton_method(:callable) { callable }
47
+
48
+ def apply
49
+ block = self.class.callable
50
+ block.arity.positive? ? block.call(self) : instance_exec(&block)
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ def initialize(data)
57
+ @data = data
58
+ end
59
+
60
+ # @api public
61
+ # Transforms the data into the final response format.
62
+ #
63
+ # @return [Hash]
64
+ def wrap
65
+ raise NotImplementedError
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ module Wrapper
6
+ module Collection
7
+ # @api public
8
+ # Base class for collection response wrappers.
9
+ #
10
+ # Collection wrappers structure responses for index actions that return
11
+ # multiple records. Extend this class to customize how collections are
12
+ # wrapped in your API responses.
13
+ #
14
+ # @example Custom collection wrapper
15
+ # class MyCollectionWrapper < Wrapper::Collection::Base
16
+ # shape do
17
+ # array(root_key.plural.to_sym) do |array|
18
+ # array.reference(data_type)
19
+ # end
20
+ # object?(:meta)
21
+ # metadata_type_names.each { |type_name| merge(type_name) }
22
+ # end
23
+ #
24
+ # def wrap
25
+ # { root_key.plural.to_sym => data, meta: meta.presence, **metadata }.compact
26
+ # end
27
+ # end
28
+ class Base < Wrapper::Base
29
+ self.wrapper_type = :collection
30
+
31
+ # @!attribute [r] meta
32
+ # @api public
33
+ # The meta for this wrapper.
34
+ #
35
+ # @return [Hash]
36
+ # @!attribute [r] metadata
37
+ # @api public
38
+ # The metadata for this wrapper.
39
+ #
40
+ # @return [Hash]
41
+ # @!attribute [r] root_key
42
+ # @api public
43
+ # The root key for this wrapper.
44
+ #
45
+ # @return [RootKey]
46
+ attr_reader :meta,
47
+ :metadata,
48
+ :root_key
49
+
50
+ def initialize(data, metadata, root_key, meta)
51
+ super(data)
52
+ @metadata = metadata
53
+ @root_key = root_key
54
+ @meta = meta
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ module Wrapper
6
+ module Collection
7
+ # @api public
8
+ # Default collection response wrapper.
9
+ #
10
+ # Wraps serialized records under a pluralized root key with optional meta and capability metadata.
11
+ #
12
+ # @example Configuration
13
+ # class MyAdapter < Adapter::Base
14
+ # collection_wrapper Wrapper::Collection::Default
15
+ # end
16
+ #
17
+ # @example Output
18
+ # {
19
+ # "invoices": [
20
+ # { "id": 1, "number": "INV-001" },
21
+ # { "id": 2, "number": "INV-002" }
22
+ # ],
23
+ # "meta": { ... },
24
+ # "pagination": { "current": 1, "total": 5 }
25
+ # }
26
+ class Default < Base
27
+ shape do
28
+ array(root_key.plural.to_sym) do |array|
29
+ array.reference(data_type)
30
+ end
31
+
32
+ object?(:meta)
33
+ metadata_type_names.each { |type_name| merge(type_name) }
34
+ end
35
+
36
+ def wrap
37
+ {
38
+ root_key.plural.to_sym => data,
39
+ meta: meta.presence,
40
+ **metadata,
41
+ }.compact
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ module Wrapper
6
+ module Error
7
+ # @api public
8
+ # Base class for error response wrappers.
9
+ #
10
+ # Error wrappers structure responses for validation errors and other
11
+ # error conditions. Extend this class to customize how errors are
12
+ # wrapped in your API responses.
13
+ #
14
+ # @example Custom error wrapper
15
+ # class MyErrorWrapper < Wrapper::Error::Base
16
+ # shape do
17
+ # extends(data_type)
18
+ # end
19
+ #
20
+ # def wrap
21
+ # data
22
+ # end
23
+ # end
24
+ class Base < Wrapper::Base
25
+ self.wrapper_type = :error
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ module Wrapper
6
+ module Error
7
+ # @api public
8
+ # Default error response wrapper.
9
+ #
10
+ # Passes serialized error data through unchanged.
11
+ #
12
+ # @example Configuration
13
+ # class MyAdapter < Adapter::Base
14
+ # error_wrapper Wrapper::Error::Default
15
+ # end
16
+ #
17
+ # @example Output
18
+ # {
19
+ # "issues": [{ "code": "blank", "detail": "can't be blank", ... }],
20
+ # "layer": "domain"
21
+ # }
22
+ class Default < Base
23
+ shape do
24
+ extends(data_type)
25
+ end
26
+
27
+ def wrap
28
+ data
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ module Wrapper
6
+ module Member
7
+ # @api public
8
+ # Base class for member response wrappers.
9
+ #
10
+ # Member wrappers structure responses for show, create, and update actions
11
+ # that return a single record. Extend this class to customize how individual
12
+ # resources are wrapped in your API responses.
13
+ #
14
+ # @example Custom member wrapper
15
+ # class MyMemberWrapper < Wrapper::Member::Base
16
+ # shape do
17
+ # reference(root_key.singular.to_sym, to: data_type)
18
+ # object?(:meta)
19
+ # metadata_type_names.each { |type_name| merge(type_name) }
20
+ # end
21
+ #
22
+ # def wrap
23
+ # { root_key.singular.to_sym => data, meta: meta.presence, **metadata }.compact
24
+ # end
25
+ # end
26
+ class Base < Wrapper::Base
27
+ self.wrapper_type = :member
28
+
29
+ # @!attribute [r] meta
30
+ # @api public
31
+ # The meta for this wrapper.
32
+ #
33
+ # @return [Hash]
34
+ # @!attribute [r] metadata
35
+ # @api public
36
+ # The metadata for this wrapper.
37
+ #
38
+ # @return [Hash]
39
+ # @!attribute [r] root_key
40
+ # @api public
41
+ # The root key for this wrapper.
42
+ #
43
+ # @return [RootKey]
44
+ attr_reader :meta,
45
+ :metadata,
46
+ :root_key
47
+
48
+ def initialize(data, metadata, root_key, meta)
49
+ super(data)
50
+ @metadata = metadata
51
+ @root_key = root_key
52
+ @meta = meta
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ module Wrapper
6
+ module Member
7
+ # @api public
8
+ # Default member response wrapper.
9
+ #
10
+ # Wraps a serialized record under a singular root key with optional meta and capability metadata.
11
+ #
12
+ # @example Configuration
13
+ # class MyAdapter < Adapter::Base
14
+ # member_wrapper Wrapper::Member::Default
15
+ # end
16
+ #
17
+ # @example Output
18
+ # {
19
+ # "invoice": { "id": 1, "number": "INV-001" },
20
+ # "meta": { ... }
21
+ # }
22
+ class Default < Base
23
+ shape do
24
+ reference(root_key.singular.to_sym, to: data_type)
25
+ object?(:meta)
26
+ metadata_type_names.each { |type_name| merge(type_name) }
27
+ end
28
+
29
+ def wrap
30
+ {
31
+ root_key.singular.to_sym => data,
32
+ meta: meta.presence,
33
+ **metadata,
34
+ }.compact
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Adapter
5
+ module Wrapper
6
+ # @api public
7
+ # Base class for wrapper shapes.
8
+ #
9
+ # Subclass to define response type structure for record or collection wrappers.
10
+ # The block is evaluated via instance_exec, providing access to type DSL methods
11
+ # and helpers like root_key and {#metadata_type_names}.
12
+ #
13
+ # @example Custom shape class
14
+ # class MyShape < Wrapper::Shape
15
+ # def apply
16
+ # reference(:invoice)
17
+ # object?(:meta)
18
+ # metadata_type_names.each { |type_name| merge(type_name) }
19
+ # end
20
+ # end
21
+ #
22
+ # @example Inline shape block
23
+ # shape do
24
+ # reference(root_key.singular.to_sym)
25
+ # object?(:meta)
26
+ # metadata_type_names.each { |type_name| merge(type_name) }
27
+ # end
28
+ class Shape
29
+ class << self
30
+ def apply(target, root_key, capabilities, representation_class, type, data_type: nil)
31
+ metadata_type_names = build_metadata_type_names(capabilities, representation_class, type)
32
+ new(target, root_key, metadata_type_names, data_type:).apply
33
+ end
34
+
35
+ private
36
+
37
+ def build_metadata_type_names(capabilities, representation_class, type)
38
+ capabilities.filter_map { |capability| capability.shape(representation_class, type) }
39
+ end
40
+ end
41
+
42
+ # @!attribute [r] data_type
43
+ # @api public
44
+ # The data type for this shape.
45
+ #
46
+ # @return [Symbol, nil]
47
+ # @!attribute [r] metadata_type_names
48
+ # @api public
49
+ # The metadata type names for this shape.
50
+ #
51
+ # Auto-generated type names from capability {Adapter::Capability::Operation::Base.metadata_shape}
52
+ # definitions. Use with {#merge} to include capability metadata fields in the shape.
53
+ #
54
+ # @return [Array<Symbol>]
55
+ # @!attribute [r] root_key
56
+ # @api public
57
+ # The root key for this shape.
58
+ #
59
+ # @return [RootKey]
60
+ attr_reader :data_type,
61
+ :metadata_type_names,
62
+ :root_key
63
+
64
+ # @!method array(name, **options, &block)
65
+ # @api public
66
+ # @see API::Object#array
67
+ # @!method array?(name, **options, &block)
68
+ # @api public
69
+ # @see API::Object#array?
70
+ # @!method binary(name, **options)
71
+ # @api public
72
+ # @see API::Object#binary
73
+ # @!method binary?(name, **options)
74
+ # @api public
75
+ # @see API::Object#binary?
76
+ # @!method boolean(name, **options)
77
+ # @api public
78
+ # @see API::Object#boolean
79
+ # @!method boolean?(name, **options)
80
+ # @api public
81
+ # @see API::Object#boolean?
82
+ # @!method date(name, **options)
83
+ # @api public
84
+ # @see API::Object#date
85
+ # @!method date?(name, **options)
86
+ # @api public
87
+ # @see API::Object#date?
88
+ # @!method datetime(name, **options)
89
+ # @api public
90
+ # @see API::Object#datetime
91
+ # @!method datetime?(name, **options)
92
+ # @api public
93
+ # @see API::Object#datetime?
94
+ # @!method decimal(name, **options)
95
+ # @api public
96
+ # @see API::Object#decimal
97
+ # @!method decimal?(name, **options)
98
+ # @api public
99
+ # @see API::Object#decimal?
100
+ # @!method extends(type)
101
+ # @api public
102
+ # @see API::Object#extends
103
+ # @!method integer(name, **options)
104
+ # @api public
105
+ # @see API::Object#integer
106
+ # @!method integer?(name, **options)
107
+ # @api public
108
+ # @see API::Object#integer?
109
+ # @!method literal(name, value:, **options)
110
+ # @api public
111
+ # @see API::Object#literal
112
+ # @!method merge(other)
113
+ # @api public
114
+ # @see API::Object#merge
115
+ # @!method number(name, **options)
116
+ # @api public
117
+ # @see API::Object#number
118
+ # @!method number?(name, **options)
119
+ # @api public
120
+ # @see API::Object#number?
121
+ # @!method object(name, **options, &block)
122
+ # @api public
123
+ # @see API::Object#object
124
+ # @!method object?(name, **options, &block)
125
+ # @api public
126
+ # @see API::Object#object?
127
+ # @!method reference(name, **options)
128
+ # @api public
129
+ # @see API::Object#reference
130
+ # @!method reference?(name, **options)
131
+ # @api public
132
+ # @see API::Object#reference?
133
+ # @!method string(name, **options)
134
+ # @api public
135
+ # @see API::Object#string
136
+ # @!method string?(name, **options)
137
+ # @api public
138
+ # @see API::Object#string?
139
+ # @!method time(name, **options)
140
+ # @api public
141
+ # @see API::Object#time
142
+ # @!method time?(name, **options)
143
+ # @api public
144
+ # @see API::Object#time?
145
+ # @!method union(name, **options, &block)
146
+ # @api public
147
+ # @see API::Object#union
148
+ # @!method union?(name, **options, &block)
149
+ # @api public
150
+ # @see API::Object#union?
151
+ # @!method uuid(name, **options)
152
+ # @api public
153
+ # @see API::Object#uuid
154
+ # @!method uuid?(name, **options)
155
+ # @api public
156
+ # @see API::Object#uuid?
157
+ delegate :array,
158
+ :array?,
159
+ :binary,
160
+ :binary?,
161
+ :boolean,
162
+ :boolean?,
163
+ :date,
164
+ :date?,
165
+ :datetime,
166
+ :datetime?,
167
+ :decimal,
168
+ :decimal?,
169
+ :extends,
170
+ :integer,
171
+ :integer?,
172
+ :literal,
173
+ :merge,
174
+ :number,
175
+ :number?,
176
+ :object,
177
+ :object?,
178
+ :reference,
179
+ :reference?,
180
+ :string,
181
+ :string?,
182
+ :time,
183
+ :time?,
184
+ :union,
185
+ :union?,
186
+ :uuid,
187
+ :uuid?,
188
+ to: :@target
189
+
190
+ def initialize(target, root_key, metadata_type_names, data_type: nil)
191
+ @target = target
192
+ @root_key = root_key
193
+ @metadata_type_names = metadata_type_names
194
+ @data_type = data_type
195
+ end
196
+
197
+ def apply
198
+ raise NotImplementedError
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end