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,291 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Export
5
+ # @api public
6
+ # Base class for exports.
7
+ #
8
+ # Subclass this to create custom export formats. Declare output type
9
+ # and override `#generate` to produce output.
10
+ #
11
+ # @example Hash export (supports json/yaml)
12
+ # class OpenAPI < Apiwork::Export::Base
13
+ # export_name :openapi
14
+ # output :hash
15
+ #
16
+ # def generate
17
+ # { openapi: '3.1.0', ... } # Returns Hash
18
+ # end
19
+ # end
20
+ #
21
+ # @example String export (fixed format)
22
+ # class ProtobufExport < Apiwork::Export::Base
23
+ # export_name :protobuf
24
+ # output :string
25
+ # file_extension '.proto'
26
+ #
27
+ # def generate
28
+ # "syntax = \"proto3\";\n..." # Returns String
29
+ # end
30
+ # end
31
+ #
32
+ # # Register the export
33
+ # Apiwork::Export.register(ProtobufExport)
34
+ class Base
35
+ include Configurable
36
+
37
+ option :key_format, enum: %i[keep camel pascal kebab underscore], type: :symbol
38
+ option :locale, default: nil, type: :symbol
39
+
40
+ # @!attribute [r] api
41
+ # @api public
42
+ # The API introspection for this export.
43
+ #
44
+ # Primary interface for accessing introspection data in export generators.
45
+ #
46
+ # @return [Introspection::API]
47
+ # @see Introspection::API
48
+ attr_reader :api,
49
+ :api_base_path,
50
+ :options
51
+
52
+ class << self
53
+ # @api public
54
+ # The name for this export.
55
+ #
56
+ # @param name [Symbol, nil] (nil)
57
+ # The export name.
58
+ # @return [Symbol, nil]
59
+ def export_name(name = nil)
60
+ @export_name = name.to_sym if name
61
+ @export_name
62
+ end
63
+
64
+ # @api public
65
+ # The output for this export.
66
+ #
67
+ # @param type [Symbol, nil] (nil) [:hash, :string]
68
+ # The output type. :hash for Hash output (json/yaml), :string for String output.
69
+ # @return [Symbol, nil]
70
+ def output(type = nil)
71
+ return @output_type unless type
72
+
73
+ raise ArgumentError, "output must be :hash or :string, got #{type.inspect}" unless %i[hash string].include?(type)
74
+
75
+ @output_type = type
76
+ end
77
+
78
+ # @api public
79
+ # The file extension for this export.
80
+ #
81
+ # Only applies to string exports. Hash exports derive extension from format.
82
+ #
83
+ # @param value [String, nil] (nil)
84
+ # The file extension (e.g., '.ts').
85
+ # @return [String, nil]
86
+ def file_extension(value = nil)
87
+ return @file_extension unless value
88
+
89
+ raise ConfigurationError, 'file_extension not allowed for output :hash exports' if output_type == :hash
90
+
91
+ @file_extension = value
92
+ end
93
+
94
+ attr_reader :output_type
95
+
96
+ def generate(api_base_path, format: nil, **options)
97
+ format ||= :json
98
+
99
+ raise ArgumentError, "#{export_name} export does not support #{format} format" if hash_output? && !supports_format?(format)
100
+
101
+ export = new(api_base_path, **options)
102
+ content = export.generate
103
+ export.serialize(content, format:)
104
+ end
105
+
106
+ def hash_output?
107
+ output_type == :hash
108
+ end
109
+
110
+ def string_output?
111
+ output_type == :string
112
+ end
113
+
114
+ def supports_format?(format)
115
+ return true if hash_output? && %i[json yaml].include?(format)
116
+
117
+ false
118
+ end
119
+
120
+ def file_extension_for(format: nil)
121
+ resolved = format || :json
122
+
123
+ if hash_output?
124
+ resolved == :yaml ? '.yaml' : '.json'
125
+ else
126
+ file_extension
127
+ end
128
+ end
129
+
130
+ def content_type_for(format: nil)
131
+ resolved = format || :json
132
+
133
+ if hash_output?
134
+ resolved == :yaml ? 'application/yaml' : 'application/json'
135
+ else
136
+ 'text/plain; charset=utf-8'
137
+ end
138
+ end
139
+
140
+ def extract_options(source)
141
+ result = {}
142
+
143
+ options.each do |name, option|
144
+ value = source[name] || source[name.to_s]
145
+ next if value.nil?
146
+
147
+ result[name] = if option.nested?
148
+ extract_nested_option(option, value)
149
+ else
150
+ option.cast(value)
151
+ end
152
+ end
153
+
154
+ result
155
+ end
156
+
157
+ def extract_nested_option(option, value)
158
+ return nil unless value.is_a?(Hash)
159
+
160
+ nested = {}
161
+ option.children.each do |child_name, child_option|
162
+ child_value = value[child_name] || value[child_name.to_s]
163
+ next if child_value.nil?
164
+
165
+ nested[child_name] = child_option.cast(child_value)
166
+ end
167
+ nested
168
+ end
169
+
170
+ def extract_options_from_env
171
+ result = {}
172
+
173
+ options.each do |name, option|
174
+ if option.nested?
175
+ nested = extract_nested_option_from_env(name, option)
176
+ result[name] = nested if nested.any?
177
+ else
178
+ env_key = name.to_s.upcase
179
+ value = ENV[env_key]
180
+ result[name] = option.cast(value) unless value.nil?
181
+ end
182
+ end
183
+
184
+ result
185
+ end
186
+
187
+ def extract_nested_option_from_env(parent_name, option)
188
+ nested = {}
189
+ prefix = parent_name.to_s.upcase
190
+
191
+ option.children.each do |child_name, child_option|
192
+ env_key = "#{prefix}_#{child_name.to_s.upcase}"
193
+ value = ENV[env_key]
194
+ next if value.nil?
195
+
196
+ nested[child_name] = child_option.cast(value)
197
+ end
198
+
199
+ nested
200
+ end
201
+ end
202
+
203
+ def initialize(api_base_path, key_format: nil, locale: nil, **options)
204
+ @api_base_path = api_base_path
205
+ @api_class = API.find!(api_base_path)
206
+
207
+ unless @api_class.export_configs.key?(self.class.export_name)
208
+ raise ConfigurationError,
209
+ "Export :#{self.class.export_name} is not declared for #{api_base_path}. " \
210
+ "Add `export :#{self.class.export_name}` to your API definition."
211
+ end
212
+
213
+ config = @api_class.export_configs[self.class.export_name]
214
+ api_config = extract_options_from_config(config)
215
+ all_options = options.merge(key_format:, locale:).compact
216
+ @options = self.class.default_options.merge(api_config).merge(all_options)
217
+ @options[:key_format] ||= @api_class.key_format || :keep
218
+ validate_options!
219
+
220
+ @api = @api_class.introspect(locale: @options[:locale])
221
+ end
222
+
223
+ # @api public
224
+ # Generates the export output.
225
+ #
226
+ # Override this method in subclasses to produce the export format.
227
+ # Access API data via the {#api} method which provides typed access
228
+ # to types, enums, resources, actions, and other introspection data.
229
+ #
230
+ # @return [Hash, String]
231
+ # @see Introspection::API
232
+ def generate
233
+ raise NotImplementedError, "#{self.class} must implement #generate"
234
+ end
235
+
236
+ # @api public
237
+ # The key format for this export.
238
+ #
239
+ # @return [Symbol]
240
+ def key_format
241
+ @options[:key_format]
242
+ end
243
+
244
+ # @api public
245
+ # Transforms a key according to the configured key format.
246
+ #
247
+ # @param key [String, Symbol]
248
+ # The key to transform.
249
+ # @return [String]
250
+ # @see #key_format
251
+ def transform_key(key)
252
+ key_string = key.to_s
253
+
254
+ return key_string if key_string.match?(/\A[A-Z]+\z/)
255
+
256
+ case key_format
257
+ when :camel then key_string.camelize(:lower)
258
+ when :pascal then key_string.camelize
259
+ when :kebab then key_string.dasherize
260
+ when :underscore then key_string.underscore
261
+ else key_string
262
+ end
263
+ end
264
+
265
+ def extract_options_from_config(config)
266
+ self.class.options.keys.each_with_object({}) do |key, hash|
267
+ value = config.public_send(key)
268
+ hash[key] = value unless value.nil?
269
+ end
270
+ end
271
+
272
+ def validate_options!
273
+ @options.each do |name, value|
274
+ option = self.class.options[name]
275
+ option&.validate!(value)
276
+ end
277
+ end
278
+
279
+ def serialize(content, format:)
280
+ return content unless content.is_a?(Hash)
281
+
282
+ case format
283
+ when :yaml
284
+ content.deep_stringify_keys.to_yaml
285
+ else
286
+ JSON.pretty_generate(content)
287
+ end
288
+ end
289
+ end
290
+ end
291
+ end