apiwork 0.1.2 → 0.3.0
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.
- checksums.yaml +4 -4
- data/README.md +8 -0
- data/lib/apiwork/adapter/base.rb +5 -43
- data/lib/apiwork/api/base.rb +4 -6
- data/lib/apiwork/contract/action/request.rb +22 -0
- data/lib/apiwork/contract/action/response.rb +22 -3
- data/lib/apiwork/export/open_api.rb +20 -46
- data/lib/apiwork/export/sorbus.rb +21 -0
- data/lib/apiwork/export/sorbus_mapper.rb +159 -0
- data/lib/apiwork/export/surface_resolver.rb +1 -0
- data/lib/apiwork/export/type_script_mapper.rb +71 -55
- data/lib/apiwork/export/zod_mapper.rb +54 -29
- data/lib/apiwork/export.rb +1 -0
- data/lib/apiwork/introspection/action/request.rb +9 -0
- data/lib/apiwork/introspection/action/response.rb +9 -1
- data/lib/apiwork/introspection/dump/action.rb +9 -7
- data/lib/apiwork/introspection/dump/param.rb +1 -36
- data/lib/apiwork/version.rb +1 -1
- metadata +8 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eb5f681a0f08e4bebe2d0f5ebf4edac733f3787e4144f019bc4babfa7a7ef84a
|
|
4
|
+
data.tar.gz: e5d1d87115d2902295d66567c324d73ef15d01b0ac106c1fa9739b56a276aeb3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bb539d87fcd3b065d643711616f7f9f5589d574cbf12bcc06c69aafe7000f6fafa4e19d7fd34cd165d2734076d3b192472a6f51459074241fabbf5dd3faece42
|
|
7
|
+
data.tar.gz: 692745d73d86c3f5c377ae2a7962b38efe351b1fc893df6af45bd8de025622f61257307dedcd1b6aac15df8f3112874e92c54d3b3e316ef9aaa59ef6fcca0338
|
data/README.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Apiwork
|
|
2
2
|
|
|
3
|
+
[](https://rubygems.org/gems/apiwork)
|
|
4
|
+
[](https://github.com/skiftle/apiwork/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE.txt)
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
3
11
|
Typed APIs for Rails.
|
|
4
12
|
|
|
5
13
|
Apiwork lets you define your API once and derive validation, serialization, querying, and typed exports from the same definition.
|
data/lib/apiwork/adapter/base.rb
CHANGED
|
@@ -210,18 +210,16 @@ module Apiwork
|
|
|
210
210
|
|
|
211
211
|
error_serializer_class = self.class.error_serializer
|
|
212
212
|
error_serializer_class.new.api_types(api_class)
|
|
213
|
-
|
|
214
|
-
build_error_response_body(api_class, error_serializer_class)
|
|
215
213
|
end
|
|
216
214
|
|
|
217
|
-
def register_contract(contract_class, representation_class,
|
|
215
|
+
def register_contract(contract_class, representation_class, resource_actions: {})
|
|
218
216
|
capabilities.each do |capability|
|
|
219
|
-
capability.contract_types(contract_class, representation_class,
|
|
217
|
+
capability.contract_types(contract_class, representation_class, resource_actions)
|
|
220
218
|
end
|
|
221
219
|
|
|
222
220
|
self.class.resource_serializer.new(representation_class).contract_types(contract_class)
|
|
223
221
|
|
|
224
|
-
build_action_responses(contract_class, representation_class,
|
|
222
|
+
build_action_responses(contract_class, representation_class, resource_actions) if resource_actions.any?
|
|
225
223
|
end
|
|
226
224
|
|
|
227
225
|
def apply_request_transformers(request, phase:)
|
|
@@ -250,8 +248,8 @@ module Apiwork
|
|
|
250
248
|
)
|
|
251
249
|
end
|
|
252
250
|
|
|
253
|
-
def build_action_responses(contract_class, representation_class,
|
|
254
|
-
|
|
251
|
+
def build_action_responses(contract_class, representation_class, resource_actions)
|
|
252
|
+
resource_actions.each_value do |action|
|
|
255
253
|
build_action_response(contract_class, representation_class, action)
|
|
256
254
|
end
|
|
257
255
|
end
|
|
@@ -273,12 +271,10 @@ module Apiwork
|
|
|
273
271
|
end
|
|
274
272
|
|
|
275
273
|
def build_member_action_response(contract_class, representation_class, action, contract_action)
|
|
276
|
-
result_wrapper = build_result_wrapper(contract_class, representation_class, action.name, :member)
|
|
277
274
|
member_shape_class = self.class.member_wrapper.shape_class
|
|
278
275
|
data_type = resolve_resource_data_type(representation_class)
|
|
279
276
|
|
|
280
277
|
contract_action.response do |response|
|
|
281
|
-
response.result_wrapper = result_wrapper
|
|
282
278
|
response.body do |body|
|
|
283
279
|
member_shape_class.apply(body, representation_class.root_key, capabilities, representation_class, :member, data_type:)
|
|
284
280
|
end
|
|
@@ -286,12 +282,10 @@ module Apiwork
|
|
|
286
282
|
end
|
|
287
283
|
|
|
288
284
|
def build_collection_action_response(contract_class, representation_class, action, contract_action)
|
|
289
|
-
result_wrapper = build_result_wrapper(contract_class, representation_class, action.name, :collection)
|
|
290
285
|
collection_shape_class = self.class.collection_wrapper.shape_class
|
|
291
286
|
data_type = resolve_resource_data_type(representation_class)
|
|
292
287
|
|
|
293
288
|
contract_action.response do |response|
|
|
294
|
-
response.result_wrapper = result_wrapper
|
|
295
289
|
response.body do |body|
|
|
296
290
|
collection_shape_class.apply(body, representation_class.root_key, capabilities, representation_class, :collection, data_type:)
|
|
297
291
|
end
|
|
@@ -308,42 +302,10 @@ module Apiwork
|
|
|
308
302
|
end
|
|
309
303
|
end
|
|
310
304
|
|
|
311
|
-
def build_result_wrapper(contract_class, representation_class, action_name, response_type)
|
|
312
|
-
success_type_name = [action_name, 'success_response_body'].join('_').to_sym
|
|
313
|
-
|
|
314
|
-
unless contract_class.type?(success_type_name)
|
|
315
|
-
shape_class = if response_type == :collection
|
|
316
|
-
self.class.collection_wrapper.shape_class
|
|
317
|
-
else
|
|
318
|
-
self.class.member_wrapper.shape_class
|
|
319
|
-
end
|
|
320
|
-
data_type = resolve_resource_data_type(representation_class)
|
|
321
|
-
|
|
322
|
-
contract_class.object(success_type_name) do |object|
|
|
323
|
-
shape_class.apply(object, representation_class.root_key, capabilities, representation_class, response_type, data_type:)
|
|
324
|
-
end
|
|
325
|
-
end
|
|
326
|
-
|
|
327
|
-
{ error_type: :error_response_body, success_type: contract_class.scoped_type_name(success_type_name) }
|
|
328
|
-
end
|
|
329
|
-
|
|
330
305
|
def resolve_resource_data_type(representation_class)
|
|
331
306
|
self.class.resource_serializer.data_type.call(representation_class)
|
|
332
307
|
end
|
|
333
308
|
|
|
334
|
-
def build_error_response_body(api_class, error_serializer_class)
|
|
335
|
-
return if api_class.type?(:error_response_body)
|
|
336
|
-
|
|
337
|
-
shape_class = self.class.error_wrapper.shape_class
|
|
338
|
-
return unless shape_class
|
|
339
|
-
|
|
340
|
-
data_type = error_serializer_class.data_type
|
|
341
|
-
|
|
342
|
-
api_class.object(:error_response_body) do |object|
|
|
343
|
-
shape_class.apply(object, nil, [], nil, :error, data_type:)
|
|
344
|
-
end
|
|
345
|
-
end
|
|
346
|
-
|
|
347
309
|
def run_capability_request_transformers(request, phase:)
|
|
348
310
|
transformers = capability_request_transformers.select { |transformer_class| transformer_class.phase == phase }
|
|
349
311
|
result = request
|
data/lib/apiwork/api/base.rb
CHANGED
|
@@ -727,9 +727,9 @@ module Apiwork
|
|
|
727
727
|
@built_contracts.add(contract_class)
|
|
728
728
|
|
|
729
729
|
resource = @root_resource.find_resource { |resource| resource.resolve_contract_class == contract_class }
|
|
730
|
-
|
|
730
|
+
resource_actions = resource ? resource.actions : {}
|
|
731
731
|
|
|
732
|
-
adapter.register_contract(contract_class, representation_class,
|
|
732
|
+
adapter.register_contract(contract_class, representation_class, resource_actions:)
|
|
733
733
|
end
|
|
734
734
|
|
|
735
735
|
def ensure_pre_pass_complete!
|
|
@@ -788,13 +788,11 @@ module Apiwork
|
|
|
788
788
|
contract_class = resource.resolve_contract_class
|
|
789
789
|
return unless contract_class
|
|
790
790
|
return if @built_contracts.include?(contract_class)
|
|
791
|
-
|
|
792
|
-
representation_class = contract_class.representation_class
|
|
793
|
-
return unless representation_class
|
|
791
|
+
return unless contract_class.representation_class
|
|
794
792
|
|
|
795
793
|
@built_contracts.add(contract_class)
|
|
796
794
|
|
|
797
|
-
adapter.register_contract(contract_class, representation_class, resource.actions)
|
|
795
|
+
adapter.register_contract(contract_class, contract_class.representation_class, resource_actions: resource.actions)
|
|
798
796
|
end
|
|
799
797
|
end
|
|
800
798
|
end
|
|
@@ -16,6 +16,28 @@ module Apiwork
|
|
|
16
16
|
@action_name = action_name
|
|
17
17
|
@query = nil
|
|
18
18
|
@body = nil
|
|
19
|
+
@description = nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @api public
|
|
23
|
+
# The description for this request.
|
|
24
|
+
#
|
|
25
|
+
# Metadata included in exports.
|
|
26
|
+
#
|
|
27
|
+
# @param value [String, nil] (nil)
|
|
28
|
+
# The description.
|
|
29
|
+
# @return [String, nil]
|
|
30
|
+
#
|
|
31
|
+
# @example
|
|
32
|
+
# action :create do
|
|
33
|
+
# request do
|
|
34
|
+
# description 'The invoice to create'
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
def description(value = nil)
|
|
38
|
+
return @description if value.nil?
|
|
39
|
+
|
|
40
|
+
@description = value
|
|
19
41
|
end
|
|
20
42
|
|
|
21
43
|
# @api public
|
|
@@ -11,16 +11,35 @@ module Apiwork
|
|
|
11
11
|
attr_reader :action_name,
|
|
12
12
|
:contract_class
|
|
13
13
|
|
|
14
|
-
attr_accessor :result_wrapper
|
|
15
|
-
|
|
16
14
|
def initialize(contract_class, action_name)
|
|
17
15
|
@contract_class = contract_class
|
|
18
16
|
@action_name = action_name
|
|
19
17
|
@body = nil
|
|
20
|
-
@
|
|
18
|
+
@description = nil
|
|
21
19
|
@no_content = false
|
|
22
20
|
end
|
|
23
21
|
|
|
22
|
+
# @api public
|
|
23
|
+
# The description for this response.
|
|
24
|
+
#
|
|
25
|
+
# Metadata included in exports.
|
|
26
|
+
#
|
|
27
|
+
# @param value [String, nil] (nil)
|
|
28
|
+
# The description.
|
|
29
|
+
# @return [String, nil]
|
|
30
|
+
#
|
|
31
|
+
# @example
|
|
32
|
+
# action :show do
|
|
33
|
+
# response do
|
|
34
|
+
# description 'Returns the invoice'
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
def description(value = nil)
|
|
38
|
+
return @description if value.nil?
|
|
39
|
+
|
|
40
|
+
@description = value
|
|
41
|
+
end
|
|
42
|
+
|
|
24
43
|
# @api public
|
|
25
44
|
# Whether this response has no content.
|
|
26
45
|
#
|
|
@@ -106,7 +106,11 @@ module Apiwork
|
|
|
106
106
|
query_params = request.query? ? build_query_parameters(request.query) : []
|
|
107
107
|
all_params = path_params + query_params
|
|
108
108
|
operation[:parameters] = all_params if all_params.any?
|
|
109
|
-
|
|
109
|
+
if request.body?
|
|
110
|
+
request_body = build_request_body(request.body)
|
|
111
|
+
request_body[:description] = request.description if request.description
|
|
112
|
+
operation[:requestBody] = request_body
|
|
113
|
+
end
|
|
110
114
|
elsif path_params.any?
|
|
111
115
|
operation[:parameters] = path_params
|
|
112
116
|
end
|
|
@@ -200,42 +204,23 @@ module Apiwork
|
|
|
200
204
|
def build_responses(action_name, response, raises: [])
|
|
201
205
|
responses = {}
|
|
202
206
|
|
|
207
|
+
response_description = response.description || ''
|
|
208
|
+
|
|
203
209
|
if response.no_content?
|
|
204
|
-
responses[:'204'] = { description:
|
|
210
|
+
responses[:'204'] = { description: response_description }
|
|
205
211
|
elsif response.body
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
error_variant = body.variants[1]
|
|
211
|
-
|
|
212
|
-
responses[:'200'] = {
|
|
213
|
-
content: {
|
|
214
|
-
'application/json': {
|
|
215
|
-
schema: map_param(success_variant),
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
description: 'Successful response',
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
raises.each do |code|
|
|
222
|
-
error_code = api.error_codes[code]
|
|
223
|
-
responses[error_code.status.to_s.to_sym] = build_union_error_response(error_code.description, error_variant)
|
|
224
|
-
end
|
|
225
|
-
else
|
|
226
|
-
responses[:'200'] = {
|
|
227
|
-
content: {
|
|
228
|
-
'application/json': {
|
|
229
|
-
schema: map_param(body),
|
|
230
|
-
},
|
|
212
|
+
responses[:'200'] = {
|
|
213
|
+
content: {
|
|
214
|
+
'application/json': {
|
|
215
|
+
schema: map_param(response.body),
|
|
231
216
|
},
|
|
232
|
-
|
|
233
|
-
|
|
217
|
+
},
|
|
218
|
+
description: response_description,
|
|
219
|
+
}
|
|
234
220
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
end
|
|
221
|
+
raises.each do |code|
|
|
222
|
+
error_code = api.error_codes[code]
|
|
223
|
+
responses[error_code.status.to_s.to_sym] = build_error_response(error_code.description)
|
|
239
224
|
end
|
|
240
225
|
elsif response
|
|
241
226
|
responses[:'200'] = {
|
|
@@ -249,10 +234,10 @@ module Apiwork
|
|
|
249
234
|
},
|
|
250
235
|
},
|
|
251
236
|
},
|
|
252
|
-
description:
|
|
237
|
+
description: response_description,
|
|
253
238
|
}
|
|
254
239
|
else
|
|
255
|
-
responses[:'204'] = { description: '
|
|
240
|
+
responses[:'204'] = { description: '' }
|
|
256
241
|
end
|
|
257
242
|
|
|
258
243
|
responses
|
|
@@ -280,17 +265,6 @@ module Apiwork
|
|
|
280
265
|
}
|
|
281
266
|
end
|
|
282
267
|
|
|
283
|
-
def build_union_error_response(description, error_variant)
|
|
284
|
-
{
|
|
285
|
-
description:,
|
|
286
|
-
content: {
|
|
287
|
-
'application/json': {
|
|
288
|
-
schema: map_param(error_variant),
|
|
289
|
-
},
|
|
290
|
-
},
|
|
291
|
-
}
|
|
292
|
-
end
|
|
293
|
-
|
|
294
268
|
def surface
|
|
295
269
|
@surface ||= SurfaceResolver.resolve(api)
|
|
296
270
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Export
|
|
5
|
+
class Sorbus < Base
|
|
6
|
+
export_name :sorbus
|
|
7
|
+
output :string
|
|
8
|
+
file_extension '.ts'
|
|
9
|
+
|
|
10
|
+
def generate
|
|
11
|
+
SorbusMapper.map(self, surface)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def surface
|
|
17
|
+
@surface ||= SurfaceResolver.resolve(api)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Export
|
|
5
|
+
class SorbusMapper
|
|
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
|
+
@zod_mapper = ZodMapper.new(export)
|
|
15
|
+
@type_script_mapper = TypeScriptMapper.new(export)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def map(surface)
|
|
19
|
+
@surface = surface
|
|
20
|
+
|
|
21
|
+
[
|
|
22
|
+
"import { z } from 'zod';",
|
|
23
|
+
@zod_mapper.build_enum_schemas(surface.enums).presence,
|
|
24
|
+
@zod_mapper.build_type_schemas(surface.types).presence,
|
|
25
|
+
build_typescript_types.presence,
|
|
26
|
+
build_contract,
|
|
27
|
+
].compact.join("\n\n")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def build_typescript_types
|
|
33
|
+
types = @type_script_mapper.build_enum_types(@surface.enums) +
|
|
34
|
+
@type_script_mapper.build_type_definitions(@surface.types)
|
|
35
|
+
|
|
36
|
+
types.sort_by { |entry| entry[:name] }.map { |entry| entry[:code] }.join("\n\n")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def build_contract
|
|
40
|
+
contract = {
|
|
41
|
+
endpoints: build_endpoint_tree(@export.api.resources),
|
|
42
|
+
error: build_error_schema,
|
|
43
|
+
}
|
|
44
|
+
"export const contract = #{format_object(contract, indent: 0)} as const;"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def build_endpoint_tree(resources, parent_identifiers: [])
|
|
48
|
+
resources.each_with_object({}) do |(name, resource), tree|
|
|
49
|
+
resource_key = @export.transform_key(name)
|
|
50
|
+
identifiers = parent_identifiers + [resource.identifier]
|
|
51
|
+
|
|
52
|
+
endpoints = resource.actions.each_with_object({}) do |(action_name, action), actions|
|
|
53
|
+
actions[@export.transform_key(action_name)] = build_endpoint(
|
|
54
|
+
resource.identifier.to_sym, action_name, action
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
endpoints.merge!(build_endpoint_tree(resource.resources, parent_identifiers: identifiers))
|
|
59
|
+
tree[resource_key] = endpoints
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def build_endpoint(resource_name, action_name, action)
|
|
64
|
+
path = transform_path(action.path)
|
|
65
|
+
endpoint = { path:, method: action.method.to_s.upcase }
|
|
66
|
+
|
|
67
|
+
path_params = extract_path_params(action.path)
|
|
68
|
+
endpoint[:pathParams] = build_path_params_schema(path_params) if path_params.any?
|
|
69
|
+
|
|
70
|
+
request = build_request(action.request)
|
|
71
|
+
endpoint[:request] = request if request
|
|
72
|
+
|
|
73
|
+
response = build_response(action.response)
|
|
74
|
+
endpoint[:response] = response if response
|
|
75
|
+
|
|
76
|
+
errors = resolve_errors(action)
|
|
77
|
+
endpoint[:errors] = errors if errors.any?
|
|
78
|
+
|
|
79
|
+
endpoint
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def transform_path(path)
|
|
83
|
+
path.gsub(%r{(/:?)(\w+)}) do
|
|
84
|
+
"#{::Regexp.last_match(1)}#{@export.transform_key(::Regexp.last_match(2))}"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def extract_path_params(path)
|
|
89
|
+
path.scan(/:(\w+)/).flatten
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def build_path_params_schema(params)
|
|
93
|
+
properties = params.map { |param| "#{@export.transform_key(param)}: z.string()" }.join(', ')
|
|
94
|
+
"z.object({ #{properties} })"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def build_request(request)
|
|
98
|
+
return unless request.query? || request.body?
|
|
99
|
+
|
|
100
|
+
hash = {}
|
|
101
|
+
hash[:query] = build_params_schema(request.query) if request.query?
|
|
102
|
+
hash[:body] = build_params_schema(request.body) if request.body?
|
|
103
|
+
hash
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def build_response(response)
|
|
107
|
+
return unless response.body?
|
|
108
|
+
|
|
109
|
+
{ body: @zod_mapper.map_param(response.body) }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def build_params_schema(params)
|
|
113
|
+
properties = params.sort_by { |name, _| name.to_s }.map do |name, param|
|
|
114
|
+
"#{@export.transform_key(name)}: #{@zod_mapper.map_field(param)}"
|
|
115
|
+
end.join(', ')
|
|
116
|
+
"z.object({ #{properties} })"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def build_error_schema
|
|
120
|
+
'ErrorSchema'
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def resolve_errors(action)
|
|
124
|
+
action.raises.map { |code| @export.api.error_codes[code].status }.sort.uniq
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def format_object(hash, indent:)
|
|
128
|
+
return '{}' if hash.empty?
|
|
129
|
+
|
|
130
|
+
padding = ' ' * (indent + 1)
|
|
131
|
+
closing_padding = ' ' * indent
|
|
132
|
+
|
|
133
|
+
entries = hash.map do |key, value|
|
|
134
|
+
formatted_value = format_value(value, indent: indent + 1)
|
|
135
|
+
"#{padding}#{key}: #{formatted_value},"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
"{\n#{entries.join("\n")}\n#{closing_padding}}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def format_value(value, indent:)
|
|
142
|
+
case value
|
|
143
|
+
when Hash
|
|
144
|
+
format_object(value, indent:)
|
|
145
|
+
when String
|
|
146
|
+
if value.start_with?('z.') || value.end_with?('Schema')
|
|
147
|
+
value
|
|
148
|
+
else
|
|
149
|
+
"'#{value}'"
|
|
150
|
+
end
|
|
151
|
+
when Array
|
|
152
|
+
"[#{value.join(', ')}]"
|
|
153
|
+
else
|
|
154
|
+
value.to_s
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -56,6 +56,7 @@ module Apiwork
|
|
|
56
56
|
action.request.query.each_value { |param| collect_types_from_param(param, type_names) }
|
|
57
57
|
action.request.body.each_value { |param| collect_types_from_param(param, type_names) }
|
|
58
58
|
collect_types_from_param(action.response.body, type_names) if action.response.body
|
|
59
|
+
type_names << :error if action.raises.any? && @api.types.key?(:error)
|
|
59
60
|
end
|
|
60
61
|
|
|
61
62
|
def collect_types_from_param(param, type_names)
|
|
@@ -14,9 +14,10 @@ module Apiwork
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def map(surface)
|
|
17
|
-
types = build_enum_types(surface) +
|
|
18
|
-
build_type_definitions(surface) +
|
|
19
|
-
build_action_types
|
|
17
|
+
types = build_enum_types(surface.enums) +
|
|
18
|
+
build_type_definitions(surface.types) +
|
|
19
|
+
build_action_types +
|
|
20
|
+
build_action_response_types
|
|
20
21
|
|
|
21
22
|
types.sort_by { |entry| entry[:name] }.map { |entry| entry[:code] }.join("\n\n")
|
|
22
23
|
end
|
|
@@ -116,18 +117,25 @@ module Apiwork
|
|
|
116
117
|
"export type #{action_type_name(resource_name, action_name, 'ResponseBody', parent_identifiers:)} = #{map_param(response_body_definition)};"
|
|
117
118
|
end
|
|
118
119
|
|
|
119
|
-
def build_action_response_type(resource_name, action_name, response, parent_identifiers: [])
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
120
|
+
def build_action_response_type(resource_name, action_name, response, parent_identifiers: [], raises:)
|
|
121
|
+
type_name = action_type_name(resource_name, action_name, 'Response', parent_identifiers:)
|
|
122
|
+
|
|
123
|
+
success_variant = if response.no_content?
|
|
124
|
+
'{ status: 204 }'
|
|
125
|
+
else
|
|
126
|
+
body_ref = action_type_name(resource_name, action_name, 'ResponseBody', parent_identifiers:)
|
|
127
|
+
"{ status: 200; body: #{body_ref} }"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
error_statuses = resolve_error_statuses(raises)
|
|
131
|
+
|
|
132
|
+
if error_statuses.empty?
|
|
133
|
+
"export type #{type_name} = #{success_variant};"
|
|
134
|
+
else
|
|
135
|
+
error_variants = error_statuses.map { |status| "{ status: #{status}; body: #{pascal_case(:error)} }" }
|
|
136
|
+
all_variants = ([success_variant] + error_variants).map { |variant| " | #{variant}" }.join("\n")
|
|
137
|
+
"export type #{type_name} =\n#{all_variants};"
|
|
138
|
+
end
|
|
131
139
|
end
|
|
132
140
|
|
|
133
141
|
def action_type_name(resource_name, action_name, suffix, parent_identifiers: [])
|
|
@@ -251,23 +259,29 @@ module Apiwork
|
|
|
251
259
|
end
|
|
252
260
|
end
|
|
253
261
|
|
|
254
|
-
|
|
262
|
+
def build_action_types
|
|
263
|
+
types = []
|
|
255
264
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
end
|
|
260
|
-
end
|
|
265
|
+
traverse_resources do |resource|
|
|
266
|
+
resource_name = resource.identifier.to_sym
|
|
267
|
+
parent_identifiers = resource.parent_identifiers
|
|
261
268
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
269
|
+
resource.actions.each do |action_name, action|
|
|
270
|
+
types.concat(build_request_types(resource_name, action_name, action.request, parent_identifiers:))
|
|
271
|
+
|
|
272
|
+
response = action.response
|
|
273
|
+
next unless response.body?
|
|
274
|
+
|
|
275
|
+
type_name = action_type_name(resource_name, action_name, 'ResponseBody', parent_identifiers:)
|
|
276
|
+
code = build_action_response_body_type(resource_name, action_name, response.body, parent_identifiers:)
|
|
277
|
+
types << { code:, name: type_name }
|
|
278
|
+
end
|
|
267
279
|
end
|
|
280
|
+
|
|
281
|
+
types
|
|
268
282
|
end
|
|
269
283
|
|
|
270
|
-
def
|
|
284
|
+
def build_action_response_types
|
|
271
285
|
types = []
|
|
272
286
|
|
|
273
287
|
traverse_resources do |resource|
|
|
@@ -275,14 +289,42 @@ module Apiwork
|
|
|
275
289
|
parent_identifiers = resource.parent_identifiers
|
|
276
290
|
|
|
277
291
|
resource.actions.each do |action_name, action|
|
|
278
|
-
|
|
279
|
-
|
|
292
|
+
type_name = action_type_name(resource_name, action_name, 'Response', parent_identifiers:)
|
|
293
|
+
code = build_action_response_type(resource_name, action_name, action.response, parent_identifiers:, raises: action.raises)
|
|
294
|
+
types << { code:, name: type_name }
|
|
280
295
|
end
|
|
281
296
|
end
|
|
282
297
|
|
|
283
298
|
types
|
|
284
299
|
end
|
|
285
300
|
|
|
301
|
+
def traverse_resources(resources: @export.api.resources, &block)
|
|
302
|
+
resources.each_value do |resource|
|
|
303
|
+
yield(resource)
|
|
304
|
+
traverse_resources(resources: resource.resources, &block) if resource.resources.any?
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def build_enum_types(enums)
|
|
309
|
+
enums.map do |name, enum|
|
|
310
|
+
{ code: build_enum_type(name, enum), name: pascal_case(name) }
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def build_type_definitions(types)
|
|
315
|
+
TypeAnalysis.topological_sort_types(types.transform_values(&:to_h)).map(&:first).map do |type_name|
|
|
316
|
+
type = types[type_name]
|
|
317
|
+
code = type.union? ? build_union_type(type_name, type) : build_interface(type_name, type)
|
|
318
|
+
{ code:, name: pascal_case(type_name) }
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
private
|
|
323
|
+
|
|
324
|
+
def resolve_error_statuses(raises)
|
|
325
|
+
raises.map { |code| @export.api.error_codes[code].status }.uniq.sort
|
|
326
|
+
end
|
|
327
|
+
|
|
286
328
|
def build_request_types(resource_name, action_name, request, parent_identifiers:)
|
|
287
329
|
types = []
|
|
288
330
|
return types unless request && (request.query? || request.body?)
|
|
@@ -306,32 +348,6 @@ module Apiwork
|
|
|
306
348
|
types
|
|
307
349
|
end
|
|
308
350
|
|
|
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
351
|
def type_or_enum_reference?(symbol)
|
|
336
352
|
@export.api.types.key?(symbol) || @export.api.enums.key?(symbol)
|
|
337
353
|
end
|
|
@@ -28,18 +28,20 @@ module Apiwork
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def map(surface)
|
|
31
|
-
@surface = surface
|
|
32
31
|
parts = []
|
|
33
32
|
|
|
34
|
-
enum_schemas = build_enum_schemas
|
|
33
|
+
enum_schemas = build_enum_schemas(surface.enums)
|
|
35
34
|
parts << enum_schemas if enum_schemas.present?
|
|
36
35
|
|
|
37
|
-
type_schemas = build_type_schemas
|
|
36
|
+
type_schemas = build_type_schemas(surface.types)
|
|
38
37
|
parts << type_schemas if type_schemas.present?
|
|
39
38
|
|
|
40
39
|
action_schemas = build_action_schemas
|
|
41
40
|
parts << action_schemas if action_schemas.present?
|
|
42
41
|
|
|
42
|
+
action_response_schemas = build_action_response_schemas
|
|
43
|
+
parts << action_response_schemas if action_response_schemas.present?
|
|
44
|
+
|
|
43
45
|
parts.join("\n\n")
|
|
44
46
|
end
|
|
45
47
|
|
|
@@ -153,18 +155,27 @@ module Apiwork
|
|
|
153
155
|
"export const #{action_type_name(resource_name, action_name, 'ResponseBody', parent_identifiers:)}Schema = #{map_param(response_body)};"
|
|
154
156
|
end
|
|
155
157
|
|
|
156
|
-
def build_action_response_schema(resource_name, action_name, response, parent_identifiers: [])
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
158
|
+
def build_action_response_schema(resource_name, action_name, response, parent_identifiers: [], raises:)
|
|
159
|
+
schema_name = action_type_name(resource_name, action_name, 'Response', parent_identifiers:)
|
|
160
|
+
|
|
161
|
+
success_variant = if response.no_content?
|
|
162
|
+
'z.object({ status: z.literal(204) })'
|
|
163
|
+
else
|
|
164
|
+
body_ref = "#{action_type_name(resource_name, action_name, 'ResponseBody', parent_identifiers:)}Schema"
|
|
165
|
+
"z.object({ status: z.literal(200), body: #{body_ref} })"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
error_statuses = resolve_error_statuses(raises)
|
|
169
|
+
|
|
170
|
+
if error_statuses.empty?
|
|
171
|
+
"export const #{schema_name}Schema = #{success_variant};"
|
|
172
|
+
else
|
|
173
|
+
error_variants = error_statuses.map do |status|
|
|
174
|
+
"z.object({ status: z.literal(#{status}), body: #{pascal_case(:error)}Schema })"
|
|
175
|
+
end
|
|
176
|
+
all_variants = ([success_variant] + error_variants).map { |variant| " #{variant}" }.join(",\n")
|
|
177
|
+
"export const #{schema_name}Schema = z.discriminatedUnion('status', [\n#{all_variants}\n]);"
|
|
178
|
+
end
|
|
168
179
|
end
|
|
169
180
|
|
|
170
181
|
def action_type_name(resource_name, action_name, suffix, parent_identifiers: [])
|
|
@@ -306,22 +317,20 @@ module Apiwork
|
|
|
306
317
|
name.to_s.camelize(:upper)
|
|
307
318
|
end
|
|
308
319
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
def build_enum_schemas
|
|
312
|
-
return '' if @surface.enums.empty?
|
|
320
|
+
def build_enum_schemas(enums)
|
|
321
|
+
return '' if enums.empty?
|
|
313
322
|
|
|
314
|
-
|
|
323
|
+
enums.map do |name, enum|
|
|
315
324
|
"export const #{pascal_case(name)}Schema = z.enum([#{enum.values.sort.map { |value| "'#{value}'" }.join(', ')}]);"
|
|
316
325
|
end.join("\n\n")
|
|
317
326
|
end
|
|
318
327
|
|
|
319
|
-
def build_type_schemas
|
|
320
|
-
types_hash =
|
|
328
|
+
def build_type_schemas(types)
|
|
329
|
+
types_hash = types.transform_values(&:to_h)
|
|
321
330
|
lazy_types = TypeAnalysis.cycle_breaking_types(types_hash)
|
|
322
331
|
|
|
323
332
|
TypeAnalysis.topological_sort_types(types_hash).map(&:first).map do |type_name|
|
|
324
|
-
type =
|
|
333
|
+
type = types[type_name]
|
|
325
334
|
recursive = lazy_types.include?(type_name)
|
|
326
335
|
|
|
327
336
|
if type.union?
|
|
@@ -367,12 +376,22 @@ module Apiwork
|
|
|
367
376
|
end
|
|
368
377
|
|
|
369
378
|
response = action.response
|
|
370
|
-
if response.
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
379
|
+
schemas << build_action_response_body_schema(resource_name, action_name, response.body, parent_identifiers:) if response.body?
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
schemas.join("\n\n")
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def build_action_response_schemas
|
|
387
|
+
schemas = []
|
|
388
|
+
|
|
389
|
+
traverse_resources do |resource|
|
|
390
|
+
resource_name = resource.identifier.to_sym
|
|
391
|
+
parent_identifiers = resource.parent_identifiers
|
|
392
|
+
|
|
393
|
+
resource.actions.each do |action_name, action|
|
|
394
|
+
schemas << build_action_response_schema(resource_name, action_name, action.response, parent_identifiers:, raises: action.raises)
|
|
376
395
|
end
|
|
377
396
|
end
|
|
378
397
|
|
|
@@ -386,6 +405,8 @@ module Apiwork
|
|
|
386
405
|
end
|
|
387
406
|
end
|
|
388
407
|
|
|
408
|
+
private
|
|
409
|
+
|
|
389
410
|
def type_or_enum_reference?(symbol)
|
|
390
411
|
@export.api.types.key?(symbol) || @export.api.enums.key?(symbol)
|
|
391
412
|
end
|
|
@@ -399,6 +420,10 @@ module Apiwork
|
|
|
399
420
|
referenced_type.shape.key?(discriminator)
|
|
400
421
|
end
|
|
401
422
|
|
|
423
|
+
def resolve_error_statuses(raises)
|
|
424
|
+
raises.map { |code| @export.api.error_codes[code].status }.uniq.sort
|
|
425
|
+
end
|
|
426
|
+
|
|
402
427
|
def resolve_enum_schema(param)
|
|
403
428
|
return nil unless param.scalar? && param.enum?
|
|
404
429
|
|
data/lib/apiwork/export.rb
CHANGED
|
@@ -18,6 +18,14 @@ module Apiwork
|
|
|
18
18
|
@dump = dump
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
# @api public
|
|
22
|
+
# The description for this request.
|
|
23
|
+
#
|
|
24
|
+
# @return [String, nil]
|
|
25
|
+
def description
|
|
26
|
+
@dump[:description]
|
|
27
|
+
end
|
|
28
|
+
|
|
21
29
|
# @api public
|
|
22
30
|
# The query for this request.
|
|
23
31
|
#
|
|
@@ -56,6 +64,7 @@ module Apiwork
|
|
|
56
64
|
# @return [Hash]
|
|
57
65
|
def to_h
|
|
58
66
|
{
|
|
67
|
+
description:,
|
|
59
68
|
body: body.transform_values(&:to_h),
|
|
60
69
|
query: query.transform_values(&:to_h),
|
|
61
70
|
}
|
|
@@ -28,6 +28,14 @@ module Apiwork
|
|
|
28
28
|
@body ||= @dump[:body] ? Param.build(@dump[:body]) : nil
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
# @api public
|
|
32
|
+
# The description for this response.
|
|
33
|
+
#
|
|
34
|
+
# @return [String, nil]
|
|
35
|
+
def description
|
|
36
|
+
@dump[:description]
|
|
37
|
+
end
|
|
38
|
+
|
|
31
39
|
# @api public
|
|
32
40
|
# Whether this response has no content.
|
|
33
41
|
#
|
|
@@ -49,7 +57,7 @@ module Apiwork
|
|
|
49
57
|
#
|
|
50
58
|
# @return [Hash]
|
|
51
59
|
def to_h
|
|
52
|
-
{ body: body&.to_h, no_content: no_content? }
|
|
60
|
+
{ description:, body: body&.to_h, no_content: no_content? }
|
|
53
61
|
end
|
|
54
62
|
end
|
|
55
63
|
end
|
|
@@ -23,7 +23,7 @@ module Apiwork
|
|
|
23
23
|
|
|
24
24
|
private
|
|
25
25
|
|
|
26
|
-
def i18n_lookup(
|
|
26
|
+
def i18n_lookup(*segments)
|
|
27
27
|
contract_class = @contract_action.contract_class
|
|
28
28
|
return nil unless contract_class.name
|
|
29
29
|
|
|
@@ -32,15 +32,16 @@ module Apiwork
|
|
|
32
32
|
contract_class.name.demodulize.delete_suffix('Contract').underscore,
|
|
33
33
|
:actions,
|
|
34
34
|
@contract_action.name,
|
|
35
|
-
|
|
35
|
+
*segments,
|
|
36
36
|
)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def build_request(request)
|
|
40
|
-
return { body: {}, query: {} } unless request
|
|
40
|
+
return { body: {}, description: i18n_lookup(:request, :description), query: {} } unless request
|
|
41
41
|
|
|
42
42
|
{
|
|
43
43
|
body: build_param(request.body),
|
|
44
|
+
description: request.description || i18n_lookup(:request, :description),
|
|
44
45
|
query: build_param(request.query),
|
|
45
46
|
}
|
|
46
47
|
end
|
|
@@ -50,13 +51,14 @@ module Apiwork
|
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
def build_response(response)
|
|
53
|
-
return { body: {}, no_content: false } unless response
|
|
54
|
-
return { body: {}, no_content: true } if response.no_content?
|
|
54
|
+
return { body: {}, description: i18n_lookup(:response, :description), no_content: false } unless response
|
|
55
|
+
return { body: {}, description: response.description || i18n_lookup(:response, :description), no_content: true } if response.no_content?
|
|
55
56
|
|
|
57
|
+
description = response.description || i18n_lookup(:response, :description)
|
|
56
58
|
body_shape = response.body
|
|
57
|
-
return { body: {}, no_content: false } unless body_shape
|
|
59
|
+
return { description:, body: {}, no_content: false } unless body_shape
|
|
58
60
|
|
|
59
|
-
{ body: Param.new(body_shape
|
|
61
|
+
{ description:, body: Param.new(body_shape).to_h, no_content: false }
|
|
60
62
|
end
|
|
61
63
|
|
|
62
64
|
def raises
|
|
@@ -4,9 +4,8 @@ module Apiwork
|
|
|
4
4
|
module Introspection
|
|
5
5
|
module Dump
|
|
6
6
|
class Param
|
|
7
|
-
def initialize(contract_param,
|
|
7
|
+
def initialize(contract_param, visited: Set.new)
|
|
8
8
|
@contract_param = contract_param
|
|
9
|
-
@result_wrapper = result_wrapper
|
|
10
9
|
@visited = visited
|
|
11
10
|
@import_prefix_cache = {}
|
|
12
11
|
end
|
|
@@ -14,8 +13,6 @@ module Apiwork
|
|
|
14
13
|
def to_h
|
|
15
14
|
return nil unless @contract_param
|
|
16
15
|
|
|
17
|
-
return build_result_wrapped if @result_wrapper
|
|
18
|
-
|
|
19
16
|
result = {}
|
|
20
17
|
|
|
21
18
|
@contract_param.params.sort_by { |name, _options| name.to_s }.each do |name, param_options|
|
|
@@ -31,38 +28,6 @@ module Apiwork
|
|
|
31
28
|
|
|
32
29
|
private
|
|
33
30
|
|
|
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
31
|
def build_param(name, options)
|
|
67
32
|
return build_union_param(options) if options[:type] == :union
|
|
68
33
|
return build_custom_type_param(options) if options[:custom_type]
|
data/lib/apiwork/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: apiwork
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- skiftle
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-02-
|
|
11
|
+
date: 2026-02-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -122,7 +122,7 @@ dependencies:
|
|
|
122
122
|
- - "~>"
|
|
123
123
|
- !ruby/object:Gem::Version
|
|
124
124
|
version: '0.9'
|
|
125
|
-
description:
|
|
125
|
+
description: Define your API once — and generate everything from it
|
|
126
126
|
email:
|
|
127
127
|
executables: []
|
|
128
128
|
extensions: []
|
|
@@ -252,6 +252,8 @@ files:
|
|
|
252
252
|
- lib/apiwork/export/pipeline.rb
|
|
253
253
|
- lib/apiwork/export/pipeline/writer.rb
|
|
254
254
|
- lib/apiwork/export/registry.rb
|
|
255
|
+
- lib/apiwork/export/sorbus.rb
|
|
256
|
+
- lib/apiwork/export/sorbus_mapper.rb
|
|
255
257
|
- lib/apiwork/export/surface_resolver.rb
|
|
256
258
|
- lib/apiwork/export/type_analysis.rb
|
|
257
259
|
- lib/apiwork/export/type_script.rb
|
|
@@ -330,7 +332,9 @@ homepage: https://apiwork.dev
|
|
|
330
332
|
licenses:
|
|
331
333
|
- MIT
|
|
332
334
|
metadata:
|
|
335
|
+
bug_tracker_uri: https://github.com/skiftle/apiwork/issues
|
|
333
336
|
changelog_uri: https://github.com/skiftle/apiwork/blob/main/CHANGELOG.md
|
|
337
|
+
documentation_uri: https://apiwork.dev/guide/introduction
|
|
334
338
|
homepage_uri: https://apiwork.dev
|
|
335
339
|
rubygems_mfa_required: 'true'
|
|
336
340
|
source_code_uri: https://github.com/skiftle/apiwork
|
|
@@ -352,5 +356,5 @@ requirements: []
|
|
|
352
356
|
rubygems_version: 3.4.19
|
|
353
357
|
signing_key:
|
|
354
358
|
specification_version: 4
|
|
355
|
-
summary:
|
|
359
|
+
summary: Typed APIs for Rails
|
|
356
360
|
test_files: []
|