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,349 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Export
5
+ class TypeScriptMapper
6
+ class << self
7
+ def map(export, surface)
8
+ new(export).map(surface)
9
+ end
10
+ end
11
+
12
+ def initialize(export)
13
+ @export = export
14
+ end
15
+
16
+ def map(surface)
17
+ types = build_enum_types(surface) +
18
+ build_type_definitions(surface) +
19
+ build_action_types
20
+
21
+ types.sort_by { |entry| entry[:name] }.map { |entry| entry[:code] }.join("\n\n")
22
+ end
23
+
24
+ def build_interface(type_name, type)
25
+ type_name = pascal_case(type_name)
26
+
27
+ properties = type.shape.sort_by { |name, _param| name.to_s }.map do |name, param|
28
+ key = @export.transform_key(name)
29
+ ts_type = map_field(param)
30
+ optional_marker = param.optional? ? '?' : ''
31
+
32
+ prop_jsdoc = jsdoc(description: param.description, example: param.example)
33
+ if prop_jsdoc
34
+ indented_jsdoc = prop_jsdoc.lines.map { |line| " #{line.chomp}" }.join("\n")
35
+ "#{indented_jsdoc}\n #{key}#{optional_marker}: #{ts_type};"
36
+ else
37
+ " #{key}#{optional_marker}: #{ts_type};"
38
+ end
39
+ end.join("\n")
40
+
41
+ type_jsdoc = jsdoc(description: type.description, example: type.example)
42
+
43
+ code = build_interface_code(type_name, properties, type.extends)
44
+ type_jsdoc ? "#{type_jsdoc}\n#{code}" : code
45
+ end
46
+
47
+ def build_interface_code(type_name, properties, extends)
48
+ base_types = extends.map { |type| pascal_case(type) }
49
+
50
+ if properties.empty? && base_types.any?
51
+ "export type #{type_name} = #{base_types.join(' & ')};"
52
+ elsif base_types.any?
53
+ "export interface #{type_name} extends #{base_types.join(', ')} {\n#{properties}\n}"
54
+ else
55
+ "export interface #{type_name} {\n#{properties}\n}"
56
+ end
57
+ end
58
+
59
+ def build_union_type(type_name, type)
60
+ type_name = pascal_case(type_name)
61
+
62
+ variant_types = type.variants.map do |variant|
63
+ base_type = map_param(variant)
64
+
65
+ if type.discriminator && variant.tag && !reference_contains_discriminator?(variant, type.discriminator)
66
+ "{ #{@export.transform_key(type.discriminator)}: '#{variant.tag}' } & #{base_type}"
67
+ else
68
+ base_type
69
+ end
70
+ end
71
+
72
+ code = "export type #{type_name} = #{variant_types.join(' | ')};"
73
+ type_jsdoc = jsdoc(description: type.description)
74
+ type_jsdoc ? "#{type_jsdoc}\n#{code}" : code
75
+ end
76
+
77
+ def build_enum_type(enum_name, enum)
78
+ code = "export type #{pascal_case(enum_name)} = #{enum.values.sort.map { |value| "'#{value}'" }.join(' | ')};"
79
+ type_jsdoc = jsdoc(description: enum.description)
80
+ type_jsdoc ? "#{type_jsdoc}\n#{code}" : code
81
+ end
82
+
83
+ def build_action_request_query_type(resource_name, action_name, query_params, parent_identifiers: [])
84
+ properties = query_params.sort_by { |name, _param| name.to_s }.map do |param_name, param|
85
+ key = @export.transform_key(param_name)
86
+ ts_type = map_field(param)
87
+ optional_marker = param.optional? ? '?' : ''
88
+ " #{key}#{optional_marker}: #{ts_type};"
89
+ end.join("\n")
90
+
91
+ "export interface #{action_type_name(resource_name, action_name, 'RequestQuery', parent_identifiers:)} {\n#{properties}\n}"
92
+ end
93
+
94
+ def build_action_request_body_type(resource_name, action_name, body_params, parent_identifiers: [])
95
+ properties = body_params.sort_by { |name, _param| name.to_s }.map do |param_name, param|
96
+ key = @export.transform_key(param_name)
97
+ ts_type = map_field(param)
98
+ optional_marker = param.optional? ? '?' : ''
99
+ " #{key}#{optional_marker}: #{ts_type};"
100
+ end.join("\n")
101
+
102
+ "export interface #{action_type_name(resource_name, action_name, 'RequestBody', parent_identifiers:)} {\n#{properties}\n}"
103
+ end
104
+
105
+ def build_action_request_type(resource_name, action_name, request, parent_identifiers: [])
106
+ nested_properties = []
107
+
108
+ nested_properties << " query: #{action_type_name(resource_name, action_name, 'RequestQuery', parent_identifiers:)};" if request[:query].any?
109
+
110
+ nested_properties << " body: #{action_type_name(resource_name, action_name, 'RequestBody', parent_identifiers:)};" if request[:body].any?
111
+
112
+ "export interface #{action_type_name(resource_name, action_name, 'Request', parent_identifiers:)} {\n#{nested_properties.join("\n")}\n}"
113
+ end
114
+
115
+ def build_action_response_body_type(resource_name, action_name, response_body_definition, parent_identifiers: [])
116
+ "export type #{action_type_name(resource_name, action_name, 'ResponseBody', parent_identifiers:)} = #{map_param(response_body_definition)};"
117
+ end
118
+
119
+ def build_action_response_type(resource_name, action_name, response, parent_identifiers: [])
120
+ "export interface #{action_type_name(
121
+ resource_name,
122
+ action_name,
123
+ 'Response',
124
+ parent_identifiers:,
125
+ )} {\n body: #{action_type_name(
126
+ resource_name,
127
+ action_name,
128
+ 'ResponseBody',
129
+ parent_identifiers:,
130
+ )};\n}"
131
+ end
132
+
133
+ def action_type_name(resource_name, action_name, suffix, parent_identifiers: [])
134
+ "#{pascal_case((parent_identifiers + [resource_name.to_s, action_name.to_s]).join('_'))}#{suffix.camelize}"
135
+ end
136
+
137
+ def map_field(param)
138
+ base_type = if param.reference? && type_or_enum_reference?(param.reference)
139
+ type_reference(param.reference)
140
+ elsif param.scalar? && param.enum?
141
+ if param.enum_reference?
142
+ pascal_case(param.enum)
143
+ else
144
+ param.enum.sort.map { |value| "'#{value}'" }.join(' | ')
145
+ end
146
+ else
147
+ map_param(param)
148
+ end
149
+
150
+ base_type = [base_type, 'null'].sort.join(' | ') if param.nullable?
151
+
152
+ base_type
153
+ end
154
+
155
+ def map_param(param)
156
+ if param.object?
157
+ map_object_type(param)
158
+ elsif param.array?
159
+ map_array_type(param)
160
+ elsif param.union?
161
+ map_union_type(param)
162
+ elsif param.literal?
163
+ map_literal_type(param)
164
+ elsif param.reference? && type_or_enum_reference?(param.reference)
165
+ type_reference(param.reference)
166
+ else
167
+ map_primitive(param)
168
+ end
169
+ end
170
+
171
+ def map_object_type(param)
172
+ return 'Record<string, unknown>' if param.shape.empty?
173
+
174
+ partial = param.object? && param.partial?
175
+
176
+ properties = param.shape.sort_by { |name, _field| name.to_s }.map do |name, field|
177
+ key = @export.transform_key(name)
178
+ ts_type = map_field(field)
179
+ optional_marker = partial || field.optional? ? '?' : ''
180
+ "#{key}#{optional_marker}: #{ts_type}"
181
+ end.join('; ')
182
+
183
+ "{ #{properties} }"
184
+ end
185
+
186
+ def map_array_type(param)
187
+ items_type = param.of
188
+
189
+ return "#{map_object_type(param)}[]" if items_type.nil? && param.shape.any?
190
+
191
+ return 'unknown[]' unless items_type
192
+
193
+ element_type = map_param(items_type)
194
+
195
+ if element_type.include?(' | ') || element_type.include?(' & ')
196
+ "(#{element_type})[]"
197
+ else
198
+ "#{element_type}[]"
199
+ end
200
+ end
201
+
202
+ def map_union_type(param)
203
+ param.variants.map { |variant| map_param(variant) }.sort.join(' | ')
204
+ end
205
+
206
+ def map_literal_type(param)
207
+ case param.value
208
+ when nil then 'null'
209
+ when String then "'#{param.value}'"
210
+ when Numeric, TrueClass, FalseClass then param.value.to_s
211
+ else "'#{param.value}'"
212
+ end
213
+ end
214
+
215
+ def map_primitive(param)
216
+ return 'unknown' if param.unknown?
217
+ return 'string' if param.string? || param.uuid? || param.date? || param.datetime? || param.time? || param.binary?
218
+ return 'number' if param.numeric?
219
+ return 'boolean' if param.boolean?
220
+
221
+ 'unknown'
222
+ end
223
+
224
+ def type_reference(symbol)
225
+ pascal_case(symbol)
226
+ end
227
+
228
+ def pascal_case(name)
229
+ name.to_s.camelize(:upper)
230
+ end
231
+
232
+ def jsdoc(description: nil, example: nil)
233
+ return nil if description.nil? && example.nil?
234
+ return "/** #{description} */" if description && example.nil?
235
+
236
+ lines = ['/**']
237
+ lines << " * #{description}" if description
238
+ lines << " * @example #{format_example(example)}" if example
239
+ lines << ' */'
240
+ lines.join("\n")
241
+ end
242
+
243
+ def format_example(value)
244
+ case value
245
+ when Hash, Array
246
+ value.to_json
247
+ when String
248
+ "\"#{value}\""
249
+ else
250
+ value.to_s
251
+ end
252
+ end
253
+
254
+ private
255
+
256
+ def build_enum_types(surface)
257
+ surface.enums.map do |name, enum|
258
+ { code: build_enum_type(name, enum), name: pascal_case(name) }
259
+ end
260
+ end
261
+
262
+ def build_type_definitions(surface)
263
+ TypeAnalysis.topological_sort_types(surface.types.transform_values(&:to_h)).map(&:first).map do |type_name|
264
+ type = surface.types[type_name]
265
+ code = type.union? ? build_union_type(type_name, type) : build_interface(type_name, type)
266
+ { code:, name: pascal_case(type_name) }
267
+ end
268
+ end
269
+
270
+ def build_action_types
271
+ types = []
272
+
273
+ traverse_resources do |resource|
274
+ resource_name = resource.identifier.to_sym
275
+ parent_identifiers = resource.parent_identifiers
276
+
277
+ resource.actions.each do |action_name, action|
278
+ types.concat(build_request_types(resource_name, action_name, action.request, parent_identifiers:))
279
+ types.concat(build_response_types(resource_name, action_name, action.response, parent_identifiers:))
280
+ end
281
+ end
282
+
283
+ types
284
+ end
285
+
286
+ def build_request_types(resource_name, action_name, request, parent_identifiers:)
287
+ types = []
288
+ return types unless request && (request.query? || request.body?)
289
+
290
+ if request.query?
291
+ type_name = action_type_name(resource_name, action_name, 'RequestQuery', parent_identifiers:)
292
+ code = build_action_request_query_type(resource_name, action_name, request.query, parent_identifiers:)
293
+ types << { code:, name: type_name }
294
+ end
295
+
296
+ if request.body?
297
+ type_name = action_type_name(resource_name, action_name, 'RequestBody', parent_identifiers:)
298
+ code = build_action_request_body_type(resource_name, action_name, request.body, parent_identifiers:)
299
+ types << { code:, name: type_name }
300
+ end
301
+
302
+ type_name = action_type_name(resource_name, action_name, 'Request', parent_identifiers:)
303
+ code = build_action_request_type(resource_name, action_name, { body: request.body, query: request.query }, parent_identifiers:)
304
+ types << { code:, name: type_name }
305
+
306
+ types
307
+ end
308
+
309
+ def build_response_types(resource_name, action_name, response, parent_identifiers:)
310
+ types = []
311
+
312
+ if response.no_content?
313
+ type_name = action_type_name(resource_name, action_name, 'Response', parent_identifiers:)
314
+ types << { code: "export type #{type_name} = never;", name: type_name }
315
+ elsif response.body?
316
+ type_name = action_type_name(resource_name, action_name, 'ResponseBody', parent_identifiers:)
317
+ code = build_action_response_body_type(resource_name, action_name, response.body, parent_identifiers:)
318
+ types << { code:, name: type_name }
319
+
320
+ type_name = action_type_name(resource_name, action_name, 'Response', parent_identifiers:)
321
+ code = build_action_response_type(resource_name, action_name, { body: response.body }, parent_identifiers:)
322
+ types << { code:, name: type_name }
323
+ end
324
+
325
+ types
326
+ end
327
+
328
+ def traverse_resources(resources: @export.api.resources, &block)
329
+ resources.each_value do |resource|
330
+ yield(resource)
331
+ traverse_resources(resources: resource.resources, &block) if resource.resources.any?
332
+ end
333
+ end
334
+
335
+ def type_or_enum_reference?(symbol)
336
+ @export.api.types.key?(symbol) || @export.api.enums.key?(symbol)
337
+ end
338
+
339
+ def reference_contains_discriminator?(variant, discriminator)
340
+ return false unless variant.reference?
341
+
342
+ referenced_type = @export.api.types[variant.reference]
343
+ return false unless referenced_type
344
+
345
+ referenced_type.shape.key?(discriminator)
346
+ end
347
+ end
348
+ end
349
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Export
5
+ class Zod < Base
6
+ export_name :zod
7
+ output :string
8
+ file_extension '.ts'
9
+
10
+ option :version, default: '4', enum: %w[4], type: :string
11
+
12
+ def generate
13
+ parts = []
14
+
15
+ parts << "import { z } from 'zod';\n"
16
+
17
+ zod_schemas = ZodMapper.map(self, surface)
18
+ if zod_schemas.present?
19
+ parts << zod_schemas
20
+ parts << ''
21
+ end
22
+
23
+ typescript_types = TypeScriptMapper.map(self, surface)
24
+ if typescript_types.present?
25
+ parts << typescript_types
26
+ parts << ''
27
+ end
28
+
29
+ parts.join("\n")
30
+ end
31
+
32
+ private
33
+
34
+ def surface
35
+ @surface ||= SurfaceResolver.resolve(api)
36
+ end
37
+ end
38
+ end
39
+ end