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,486 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Introspection
5
+ module Dump
6
+ class Param
7
+ def initialize(contract_param, result_wrapper: nil, visited: Set.new)
8
+ @contract_param = contract_param
9
+ @result_wrapper = result_wrapper
10
+ @visited = visited
11
+ @import_prefix_cache = {}
12
+ end
13
+
14
+ def to_h
15
+ return nil unless @contract_param
16
+
17
+ return build_result_wrapped if @result_wrapper
18
+
19
+ result = {}
20
+
21
+ @contract_param.params.sort_by { |name, _options| name.to_s }.each do |name, param_options|
22
+ result[name] = build_param(name, param_options)
23
+ end
24
+
25
+ if @contract_param.wrapped?
26
+ { shape: result, type: :object }
27
+ else
28
+ result
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def build_result_wrapped
35
+ success_type = @result_wrapper[:success_type]
36
+ error_type = @result_wrapper[:error_type]
37
+
38
+ success_variant = if success_type
39
+ { reference: success_type, type: :reference }
40
+ else
41
+ { reference: nil, shape: build_success_params, type: :object }
42
+ end
43
+
44
+ error_variant = if error_type
45
+ { reference: error_type, type: :reference }
46
+ else
47
+ { reference: nil, shape: {}, type: :object }
48
+ end
49
+
50
+ {
51
+ type: :union,
52
+ variants: [success_variant, error_variant],
53
+ }
54
+ end
55
+
56
+ def build_success_params
57
+ success_params = {}
58
+ @contract_param.params.sort_by { |name, _options| name.to_s }.each do |name, param_options|
59
+ dumped = build_param(name, param_options)
60
+ dumped[:optional] = true if param_options[:optional]
61
+ success_params[name] = dumped
62
+ end
63
+ success_params
64
+ end
65
+
66
+ def build_param(name, options)
67
+ return build_union_param(options) if options[:type] == :union
68
+ return build_custom_type_param(options) if options[:custom_type]
69
+
70
+ reference = resolve_type_reference(options[:type])
71
+
72
+ {
73
+ reference:,
74
+ as: options[:as],
75
+ default: options[:default],
76
+ deprecated: options[:deprecated] == true,
77
+ description: resolve_attribute_description(options),
78
+ discriminator: nil,
79
+ enum: resolve_enum(options),
80
+ example: options[:example],
81
+ format: options[:format],
82
+ max: options[:max],
83
+ min: options[:min],
84
+ nullable: options[:nullable] == true,
85
+ of: resolve_of(options),
86
+ optional: options[:optional] == true,
87
+ partial: options[:partial] == true,
88
+ shape: build_shape(options) || {},
89
+ tag: nil,
90
+ type: reference ? :reference : (options[:type] || :unknown),
91
+ value: options[:type] == :literal ? options[:value] : nil,
92
+ variants: [],
93
+ }
94
+ end
95
+
96
+ def build_union_param(options)
97
+ union_dump = build_union(options[:union])
98
+
99
+ {
100
+ as: options[:as],
101
+ default: options[:default],
102
+ deprecated: options[:deprecated] == true,
103
+ description: resolve_attribute_description(options),
104
+ discriminator: union_dump[:discriminator],
105
+ enum: nil,
106
+ example: options[:example],
107
+ format: options[:format],
108
+ max: nil,
109
+ min: nil,
110
+ nullable: options[:nullable] == true,
111
+ of: nil,
112
+ optional: options[:optional] == true,
113
+ partial: false,
114
+ reference: nil,
115
+ shape: {},
116
+ tag: nil,
117
+ type: :union,
118
+ value: nil,
119
+ variants: union_dump[:variants],
120
+ }
121
+ end
122
+
123
+ def build_custom_type_param(options)
124
+ custom_type_name = options[:custom_type]
125
+ custom_type_name = qualified_name(custom_type_name, @contract_param) if @contract_param.contract_class.resolve_custom_type(custom_type_name)
126
+
127
+ {
128
+ as: options[:as],
129
+ default: options[:default],
130
+ deprecated: options[:deprecated] == true,
131
+ description: resolve_attribute_description(options),
132
+ discriminator: nil,
133
+ enum: nil,
134
+ example: options[:example],
135
+ format: options[:format],
136
+ max: nil,
137
+ min: nil,
138
+ nullable: options[:nullable] == true,
139
+ of: nil,
140
+ optional: options[:optional] == true,
141
+ partial: false,
142
+ reference: custom_type_name,
143
+ shape: {},
144
+ tag: nil,
145
+ type: :reference,
146
+ value: nil,
147
+ variants: [],
148
+ }
149
+ end
150
+
151
+ def resolve_type_reference(type_value)
152
+ return nil unless type_value
153
+ return nil unless registered_type?(type_value)
154
+
155
+ qualified_name(type_value, @contract_param)
156
+ end
157
+
158
+ def registered_type?(type_value)
159
+ return false unless type_value.is_a?(Symbol)
160
+
161
+ contract_class = @contract_param.contract_class
162
+ return true if contract_class.resolve_custom_type(type_value)
163
+ return true if contract_class.enum?(type_value)
164
+
165
+ api_class = contract_class.api_class
166
+ return false unless api_class
167
+
168
+ scoped_name = api_class.scoped_type_name(contract_class, type_value)
169
+ return true if api_class.type_registry.key?(scoped_name) || api_class.type_registry.key?(type_value)
170
+ return true if api_class.enum_registry.key?(scoped_name) || api_class.enum_registry.key?(type_value)
171
+
172
+ false
173
+ end
174
+
175
+ def resolve_enum(options)
176
+ return nil unless options[:enum]
177
+
178
+ if options[:enum].is_a?(Symbol)
179
+ scope = @contract_param.contract_class
180
+ api_class = @contract_param.contract_class.api_class
181
+ api_class.scoped_enum_name(scope, options[:enum])
182
+ else
183
+ options[:enum]
184
+ end
185
+ end
186
+
187
+ def resolve_of(options)
188
+ of = options[:of]
189
+ return nil unless of
190
+
191
+ build_of_from_element(of)
192
+ end
193
+
194
+ def build_of_from_element(element)
195
+ type_value = element.type
196
+ reference = registered_type?(type_value) ? qualified_name(type_value, @contract_param) : nil
197
+ resolved_shape = element.shape ? build_nested_shape(element.shape) : {}
198
+
199
+ result = {
200
+ reference:,
201
+ enum: element.enum,
202
+ format: element.format,
203
+ max: element.max,
204
+ min: element.min,
205
+ shape: resolved_shape,
206
+ type: reference ? :reference : type_value,
207
+ }
208
+
209
+ result[:of] = build_of_from_element(element.inner) if element.type == :array && element.inner
210
+
211
+ result
212
+ end
213
+
214
+ def build_union(union)
215
+ {
216
+ discriminator: union.discriminator,
217
+ variants: union.variants.map { |variant| build_variant(variant) },
218
+ }
219
+ end
220
+
221
+ def build_variant(variant)
222
+ variant_type = variant[:custom_type] || variant[:type]
223
+ is_registered = registered_type?(variant_type)
224
+
225
+ reference = is_registered ? qualified_name(variant_type, @contract_param) : nil
226
+ resolved_type = is_registered ? :reference : (variant[:type] || :unknown)
227
+
228
+ {
229
+ reference:,
230
+ as: nil,
231
+ default: nil,
232
+ deprecated: false,
233
+ description: nil,
234
+ discriminator: nil,
235
+ enum: resolve_variant_enum(variant),
236
+ example: nil,
237
+ format: nil,
238
+ max: nil,
239
+ min: nil,
240
+ nullable: false,
241
+ of: resolve_variant_of(variant),
242
+ optional: false,
243
+ partial: variant[:partial] == true,
244
+ shape: resolve_variant_shape(variant, variant_type),
245
+ tag: variant[:tag],
246
+ type: resolved_type,
247
+ value: variant[:value],
248
+ variants: [],
249
+ }
250
+ end
251
+
252
+ def resolve_variant_enum(variant)
253
+ return nil unless variant[:enum]
254
+
255
+ if variant[:enum].is_a?(Symbol)
256
+ scope = @contract_param.contract_class
257
+ api_class = @contract_param.contract_class.api_class
258
+ api_class.scoped_enum_name(scope, variant[:enum])
259
+ else
260
+ variant[:enum]
261
+ end
262
+ end
263
+
264
+ def resolve_variant_of(variant)
265
+ of = variant[:of]
266
+ return nil unless of
267
+
268
+ build_of_from_element(of)
269
+ end
270
+
271
+ def resolve_variant_shape(variant, variant_type)
272
+ if variant[:shape]
273
+ build_nested_shape(variant[:shape])
274
+ else
275
+ {}
276
+ end
277
+ end
278
+
279
+ def build_nested_shape(shape)
280
+ if shape.is_a?(Apiwork::API::Object)
281
+ dump_api_object(shape)
282
+ elsif shape.is_a?(Apiwork::API::Union)
283
+ dump_api_union(shape)
284
+ else
285
+ Param.new(shape, visited: @visited).to_h
286
+ end
287
+ end
288
+
289
+ def dump_api_object(api_object)
290
+ result = {}
291
+ api_object.params.sort_by { |name, _| name.to_s }.each do |name, options|
292
+ result[name] = build_api_param(options)
293
+ end
294
+ result
295
+ end
296
+
297
+ def dump_api_union(api_union)
298
+ {
299
+ discriminator: api_union.discriminator,
300
+ variants: api_union.variants.map { |variant| build_api_variant(variant) },
301
+ }
302
+ end
303
+
304
+ def build_api_param(options)
305
+ {
306
+ as: options[:as],
307
+ default: options[:default],
308
+ deprecated: options[:deprecated] == true,
309
+ description: options[:description],
310
+ discriminator: options[:discriminator],
311
+ enum: options[:enum],
312
+ example: options[:example],
313
+ format: options[:format],
314
+ max: options[:max],
315
+ min: options[:min],
316
+ nullable: options[:nullable] == true,
317
+ of: build_api_of(options),
318
+ optional: options[:optional] == true,
319
+ partial: options[:partial] == true,
320
+ reference: nil,
321
+ shape: options[:shape] ? build_nested_shape(options[:shape]) : {},
322
+ tag: nil,
323
+ type: options[:type] || :unknown,
324
+ value: options[:type] == :literal ? options[:value] : nil,
325
+ variants: [],
326
+ }
327
+ end
328
+
329
+ def build_api_of(options)
330
+ of = options[:of]
331
+ return nil unless of
332
+
333
+ result = {
334
+ enum: of.enum,
335
+ format: of.format,
336
+ max: of.max,
337
+ min: of.min,
338
+ reference: nil,
339
+ shape: of.shape ? build_nested_shape(of.shape) : {},
340
+ type: of.type,
341
+ }
342
+ result[:of] = build_api_of({ of: of.inner }) if of.type == :array && of.inner
343
+ result
344
+ end
345
+
346
+ def build_api_variant(variant)
347
+ {
348
+ as: nil,
349
+ default: nil,
350
+ deprecated: false,
351
+ description: nil,
352
+ discriminator: nil,
353
+ enum: variant[:enum],
354
+ example: nil,
355
+ format: nil,
356
+ max: nil,
357
+ min: nil,
358
+ nullable: false,
359
+ of: build_api_variant_of(variant),
360
+ optional: false,
361
+ partial: variant[:partial] == true,
362
+ reference: nil,
363
+ shape: variant[:shape] ? build_nested_shape(variant[:shape]) : {},
364
+ tag: variant[:tag],
365
+ type: variant[:type] || :object,
366
+ value: variant[:value],
367
+ variants: [],
368
+ }
369
+ end
370
+
371
+ def build_api_variant_of(variant)
372
+ of = variant[:of]
373
+ return nil unless of
374
+
375
+ result = {
376
+ enum: of.enum,
377
+ format: of.format,
378
+ max: of.max,
379
+ min: of.min,
380
+ reference: nil,
381
+ shape: of.shape ? build_nested_shape(of.shape) : {},
382
+ type: of.type,
383
+ }
384
+ result[:of] = build_api_variant_of({ of: of.inner }) if of.type == :array && of.inner
385
+ result
386
+ end
387
+
388
+ def build_shape(options)
389
+ dumped = options[:shape] ? build_nested_shape(options[:shape]) : nil
390
+
391
+ return dumped || {} if [:object, :array].include?(options[:type])
392
+
393
+ dumped
394
+ end
395
+
396
+ def resolve_attribute_description(options)
397
+ return options[:description] if options[:description]
398
+
399
+ param_name = options[:name]
400
+ return nil unless param_name
401
+
402
+ representation_class = @contract_param.contract_class.representation_class
403
+ return nil unless representation_class
404
+
405
+ if (attribute = representation_class.attributes[param_name])
406
+ description = i18n_attribute_description(attribute)
407
+ return description if description
408
+ end
409
+
410
+ if (association = representation_class.associations[param_name])
411
+ description = i18n_association_description(association)
412
+ return description if description
413
+ end
414
+
415
+ nil
416
+ end
417
+
418
+ def i18n_attribute_description(attribute)
419
+ api_class = @contract_param.contract_class.api_class
420
+ return nil unless api_class
421
+
422
+ representation_name = attribute.representation_class_name
423
+ attribute_name = attribute.name
424
+
425
+ api_class.translate(:representations, representation_name, :attributes, attribute_name, :description)
426
+ end
427
+
428
+ def i18n_association_description(association)
429
+ api_class = @contract_param.contract_class.api_class
430
+ return nil unless api_class
431
+
432
+ representation_name = association.representation_class_name
433
+ association_name = association.name
434
+
435
+ api_class.translate(:representations, representation_name, :associations, association_name, :description)
436
+ end
437
+
438
+ def qualified_name(type_name, contract_param)
439
+ return type_name if global_type?(type_name, contract_param)
440
+ return type_name if global_enum?(type_name, contract_param)
441
+ return type_name if imported_type?(type_name, contract_param)
442
+
443
+ scope = contract_param.contract_class
444
+ api_class = contract_param.contract_class.api_class
445
+ api_class.scoped_type_name(scope, type_name)
446
+ end
447
+
448
+ def global_type?(type_name, contract_param)
449
+ api_class = contract_param.contract_class.api_class
450
+ return false unless api_class
451
+
452
+ type_definition = api_class.type_registry[type_name]
453
+ return false unless type_definition
454
+
455
+ type_definition.scope.nil?
456
+ end
457
+
458
+ def global_enum?(enum_name, contract_param)
459
+ api_class = contract_param.contract_class.api_class
460
+ return false unless api_class
461
+
462
+ enum_definition = api_class.enum_registry[enum_name]
463
+ return false unless enum_definition
464
+
465
+ enum_definition.scope.nil?
466
+ end
467
+
468
+ def imported_type?(type_name, contract_param)
469
+ import_prefixes = import_prefix_cache(contract_param.contract_class)
470
+
471
+ return true if import_prefixes[:direct].include?(type_name)
472
+
473
+ type_name = type_name.to_s
474
+ import_prefixes[:prefixes].any? { |prefix| type_name.start_with?(prefix) }
475
+ end
476
+
477
+ def import_prefix_cache(contract_class)
478
+ @import_prefix_cache[contract_class] ||= begin
479
+ direct = Set.new(contract_class.imports.keys)
480
+ { direct:, prefixes: contract_class.imports.keys.map { |alias_name| "#{alias_name}_" } }
481
+ end
482
+ end
483
+ end
484
+ end
485
+ end
486
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Introspection
5
+ module Dump
6
+ class Resource
7
+ def initialize(resource, api_class, parent_identifiers: [], parent_param: nil)
8
+ @resource = resource
9
+ @api_class = api_class
10
+ @parent_identifiers = parent_identifiers
11
+ @parent_param = parent_param
12
+ end
13
+
14
+ def to_h
15
+ formatted_segment = @api_class.transform_path(
16
+ @resource.path || (@resource.singular ? @resource.name.to_s.singularize : @resource.name.to_s),
17
+ )
18
+
19
+ resource_path = build_resource_path(formatted_segment)
20
+
21
+ {
22
+ actions: build_actions(resolve_contract_class, resource_path),
23
+ identifier: @resource.name.to_s,
24
+ parent_identifiers: @parent_identifiers,
25
+ path: resource_path,
26
+ resources: build_nested_resources(resource_path),
27
+ }
28
+ end
29
+
30
+ private
31
+
32
+ def build_actions(contract_class, resource_path)
33
+ actions = {}
34
+
35
+ @resource.actions.each do |action_name, adapter_action|
36
+ actions[action_name] = { method: adapter_action.method, path: build_full_action_path(resource_path, action_name, adapter_action) }
37
+
38
+ contract_action = contract_class&.action_for(action_name)
39
+ unless contract_action
40
+ actions[action_name].merge!(
41
+ description: nil,
42
+ operation_id: nil,
43
+ raises: [],
44
+ request: { body: {}, query: {} },
45
+ response: { body: {}, no_content: false },
46
+ summary: nil,
47
+ tags: [],
48
+ )
49
+ next
50
+ end
51
+
52
+ actions[action_name].merge!(Action.new(contract_action).to_h)
53
+ end
54
+
55
+ actions
56
+ end
57
+
58
+ def build_resource_path(formatted_segment)
59
+ return formatted_segment if @parent_identifiers.empty?
60
+
61
+ ":#{@parent_param || "#{@parent_identifiers.last.singularize}_id"}/#{formatted_segment}"
62
+ end
63
+
64
+ def build_nested_resources(resource_path)
65
+ return {} unless @resource.resources.any?
66
+
67
+ child_parent_identifiers = @parent_identifiers + [@resource.name.to_s]
68
+ child_parent_param = @resource.param&.to_s || "#{@resource.name.to_s.singularize}_id"
69
+
70
+ @resource.resources.transform_values do |nested_resource|
71
+ Resource.new(
72
+ nested_resource,
73
+ @api_class,
74
+ parent_identifiers: child_parent_identifiers,
75
+ parent_param: child_parent_param,
76
+ ).to_h
77
+ end
78
+ end
79
+
80
+ def build_full_action_path(resource_path, action_name, adapter_action)
81
+ "/#{resource_path}#{action_path_segment(action_name, adapter_action)}".delete_suffix('/')
82
+ end
83
+
84
+ def action_path_segment(action_name, adapter_action)
85
+ if adapter_action.crud?
86
+ case action_name
87
+ when :index, :create
88
+ ''
89
+ when :show, :update, :destroy
90
+ '/:id'
91
+ else
92
+ ''
93
+ end
94
+ elsif adapter_action.member?
95
+ "/:id/#{@api_class.transform_path(action_name)}"
96
+ elsif adapter_action.collection?
97
+ "/#{@api_class.transform_path(action_name)}"
98
+ else
99
+ ''
100
+ end
101
+ end
102
+
103
+ def resolve_contract_class
104
+ contract_class = @resource.resolve_contract_class
105
+ return nil unless contract_class
106
+
107
+ contract_class if contract_class < Apiwork::Contract::Base
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end