taro 1.1.0 → 1.2.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/CHANGELOG.md +29 -0
- data/README.md +75 -7
- data/lib/taro/errors.rb +1 -1
- data/lib/taro/export/open_api_v3.rb +52 -22
- data/lib/taro/rails/declaration.rb +44 -20
- data/lib/taro/rails/generators/install_generator.rb +1 -1
- data/lib/taro/rails/generators/templates/errors_type.erb +15 -10
- data/lib/taro/rails/normalized_route.rb +8 -0
- data/lib/taro/rails/tasks/export.rake +5 -1
- data/lib/taro/types/base_type.rb +2 -0
- data/lib/taro/types/coercion.rb +24 -14
- data/lib/taro/types/field.rb +8 -16
- data/lib/taro/types/field_validation.rb +1 -1
- data/lib/taro/types/list_type.rb +4 -6
- data/lib/taro/types/object_types/free_form_type.rb +1 -0
- data/lib/taro/types/object_types/no_content_type.rb +1 -0
- data/lib/taro/types/object_types/page_info_type.rb +2 -0
- data/lib/taro/types/object_types/page_type.rb +15 -25
- data/lib/taro/types/scalar/iso8601_date_type.rb +1 -0
- data/lib/taro/types/scalar/iso8601_datetime_type.rb +1 -0
- data/lib/taro/types/scalar/timestamp_type.rb +1 -0
- data/lib/taro/types/scalar/uuid_v4_type.rb +1 -0
- data/lib/taro/types/shared/deprecation.rb +3 -0
- data/lib/taro/types/shared/derived_types.rb +27 -0
- data/lib/taro/types/shared/errors.rb +3 -1
- data/lib/taro/types/shared/fields.rb +6 -5
- data/lib/taro/types/shared/item_type.rb +1 -0
- data/lib/taro/types/shared/object_coercion.rb +13 -0
- data/lib/taro/types/shared/openapi_name.rb +8 -6
- data/lib/taro/types/shared/rendering.rb +4 -4
- data/lib/taro/version.rb +1 -1
- data/tasks/benchmark.rake +1 -1
- metadata +6 -5
- data/lib/taro/types/shared/derivable_types.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5007f07dcb3230a1a45011138c19cc0f233e0a0e0fa81d64d554ee3d5cc4a82e
|
4
|
+
data.tar.gz: 99dde57ec71bb2724a29005e644a0bb23d642df11fe4555bd9203340169814df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: abb5fb481da01a4a50b19e891c3373b42372c97993788ed24c51b07146e087e25d040bc4ea077606a14013633b30f6254f6ced2e939ba9b039b341d95f918211
|
7
|
+
data.tar.gz: b5dd6c2222598ad3d8ee64c64fd0fc6c40299c3f071b08317d17aa29c3f786924ae4b064f587b1e548ba102e0cc802b8a5adf7ccbadf033795974bfdaae5329a
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,37 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.2.0] - 2024-11-18
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Improved error messages
|
8
|
+
- Option to define custom derived types
|
9
|
+
- Option to use custom keys in paginated content
|
10
|
+
- Option to deprecate individual fields, params, and types
|
11
|
+
|
12
|
+
### Fixed
|
13
|
+
|
14
|
+
- Fixed nullable enum fields raising for null input
|
15
|
+
- Fixed auto-loading of return types
|
16
|
+
- Fixed console spam when inspecting declarations
|
17
|
+
- Fixed resolver method not being used when rendering a Hash
|
18
|
+
- Fixed the ErrorsType template
|
19
|
+
- Many fixes for OpenAPI export
|
20
|
+
- Fixed export of parameters for http methods without body
|
21
|
+
- Fixed export for PageType
|
22
|
+
- Fixed export for arrays of UUIDs, Dates, and Times
|
23
|
+
- Fixed export YML keys for namespaced controllers
|
24
|
+
- Reference plain types for repeated flat return types
|
25
|
+
- Made order of paths, verbs, responses and schemas deterministic
|
26
|
+
|
3
27
|
## [1.1.0] - 2024-11-16
|
4
28
|
|
29
|
+
### Added
|
30
|
+
|
5
31
|
- Response validation refined
|
32
|
+
|
33
|
+
### Fixed
|
34
|
+
|
6
35
|
- Bugfix for openapi export
|
7
36
|
|
8
37
|
## [1.0.0] - 2024-11-14
|
data/README.md
CHANGED
@@ -131,16 +131,18 @@ Taro.config.validate_responses = false
|
|
131
131
|
The following type names are available by default and can be used as `type:`/`array_of:`/`page_of:` arguments:
|
132
132
|
|
133
133
|
- `'Boolean'` - accepts and renders `true` or `false`
|
134
|
+
- `'Date'` - accepts and renders a date string in ISO8601 format
|
135
|
+
- `'DateTime'` - an alias for `'Time'`
|
134
136
|
- `'Float'`
|
135
137
|
- `'FreeForm'` - accepts and renders any JSON-serializable object, use with care
|
136
138
|
- `'Integer'`
|
137
139
|
- `'NoContent'` - renders an empty object, for use with `status: :no_content`
|
138
140
|
- `'String'`
|
141
|
+
- `'Time'` - accepts and renders a time string in ISO8601 format
|
139
142
|
- `'Timestamp'` - renders a `Time` as unix timestamp integer and turns incoming integers into a `Time`
|
140
143
|
- `'UUID'` - accepts and renders UUIDs
|
141
|
-
|
142
|
-
|
143
|
-
- `'DateTime'` - an alias for `'Time'`
|
144
|
+
|
145
|
+
Also, when using the generator, `ErrorsType` and `ErrorDetailsType` are generated as a starting point for unified error presentation. `ErrorsType` can render invalid `ActiveRecord` instances, `ActiveModel::Errors` and other data structures.
|
144
146
|
|
145
147
|
### Enums
|
146
148
|
|
@@ -168,7 +170,7 @@ end
|
|
168
170
|
|
169
171
|
### FAQ
|
170
172
|
|
171
|
-
#### How
|
173
|
+
#### How do I avoid repeating common error declarations?
|
172
174
|
|
173
175
|
Hook into the DSL in your base controller(s):
|
174
176
|
|
@@ -197,7 +199,7 @@ class AuthenticatedApiController < ApiBaseController
|
|
197
199
|
end
|
198
200
|
```
|
199
201
|
|
200
|
-
#### How
|
202
|
+
#### How do I use context in my types?
|
201
203
|
|
202
204
|
Use [ActiveSupport::CurrentAttributes](https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html).
|
203
205
|
|
@@ -211,13 +213,80 @@ class BikeType < ObjectType
|
|
211
213
|
end
|
212
214
|
```
|
213
215
|
|
216
|
+
#### How do I migrate from apipie-rails?
|
217
|
+
|
218
|
+
First of all, if you don't need a better OpenAPI export, or better support for hashes and arrays, it might not be worth it.
|
219
|
+
|
220
|
+
If you do:
|
221
|
+
|
222
|
+
- note that `taro` currently only supports the latest OpenAPI standard (instead of v2 like `apipie-rails`)
|
223
|
+
- extract complex param declarations into InputTypes
|
224
|
+
- extract complex response declarations into ObjectTypes
|
225
|
+
- replace `required: true` with `null: false` and `required: false` with `null: true`
|
226
|
+
|
227
|
+
For a step-by-step migration, you might want to make `taro` use a different DSL then `apipie`:
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
# config/initializers/taro.rb
|
231
|
+
%i[api param returns].each do |m|
|
232
|
+
Taro::Rails::DSL.alias_method("taro_#{m}", m) # `taro_api` etc.
|
233
|
+
Taro::Rails::DSL.define_method(m) { |*a, **k, &b| super(*a, **k, &b) }
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
237
|
+
#### How do I keep lengthy API descriptions out of my controller?
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
module BikeUpdateDesc
|
241
|
+
extend ActiveSupport::Concern
|
242
|
+
|
243
|
+
included do
|
244
|
+
api 'Update a bike', description: 'Long description', tags: ['Bikes']
|
245
|
+
# lots of params and returns ...
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
class BikesController < ApplicationController
|
250
|
+
include BikeUpdateDesc
|
251
|
+
def update # ...
|
252
|
+
end
|
253
|
+
```
|
254
|
+
|
214
255
|
#### Why do I have to use type name strings instead of the type constants?
|
215
256
|
|
216
257
|
Why e.g. `field :id, type: 'UUID'` instead of `field :id, type: UUID`?
|
217
258
|
|
218
259
|
The purpose of this is to reduce unnecessary autoloading of the whole type dependency tree in dev and test environments.
|
219
260
|
|
220
|
-
|
261
|
+
#### Can I define my own derived types like `page_of` or `array_of`?
|
262
|
+
|
263
|
+
Yes.
|
264
|
+
|
265
|
+
```ruby
|
266
|
+
# Implement ::derive_from in your custom type.
|
267
|
+
class PreviewType < Taro::Types::Scalar::StringType
|
268
|
+
singleton_class.attr_reader :type_to_preview
|
269
|
+
|
270
|
+
def self.derive_from(other_type)
|
271
|
+
self.type_to_preview = other_type
|
272
|
+
end
|
273
|
+
|
274
|
+
def coerce_response
|
275
|
+
type_to_preview.new(object).coerce_response.to_s.truncate(100)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# Make it available in the DSL, e.g. in an initializer.
|
280
|
+
Taro::Types::BaseType.define_derived_type :preview, 'PreviewType'
|
281
|
+
|
282
|
+
# Usage:
|
283
|
+
class MyController < ApplicationController
|
284
|
+
returns code: :ok, preview_of: 'BikeType'
|
285
|
+
def show
|
286
|
+
render json: BikeType.preview.render(Bike.find(params[:id]))
|
287
|
+
end
|
288
|
+
end
|
289
|
+
```
|
221
290
|
|
222
291
|
## Possible future features
|
223
292
|
|
@@ -227,7 +296,6 @@ This already works fo type classes – they don't trigger loading of referenced
|
|
227
296
|
- sum types
|
228
297
|
- api doc rendering based on export (e.g. rails engine with web ui)
|
229
298
|
- [query logs metadata](https://github.com/rmosolgo/graphql-ruby/blob/dcaaed1cea47394fad61fceadf291ff3cb5f2932/lib/generators/graphql/install_generator.rb#L48-L52)
|
230
|
-
- deprecation feature
|
231
299
|
- maybe make `type:` optional for path params as they're always strings anyway
|
232
300
|
- various openapi features
|
233
301
|
- non-JSON content types (e.g. for file uploads)
|
data/lib/taro/errors.rb
CHANGED
@@ -11,13 +11,13 @@ class Taro::Export::OpenAPIv3 < Taro::Export::Base # rubocop:disable Metrics/Cla
|
|
11
11
|
def call(declarations:, title:, version:)
|
12
12
|
@result = { openapi: '3.1.0', info: { title:, version: } }
|
13
13
|
paths = export_paths(declarations)
|
14
|
-
@result[:paths] = paths if paths.any?
|
15
|
-
@result[:components] = { schemas: } if schemas.any?
|
14
|
+
@result[:paths] = paths.sort.to_h if paths.any?
|
15
|
+
@result[:components] = { schemas: schemas.sort.to_h } if schemas.any?
|
16
16
|
self
|
17
17
|
end
|
18
18
|
|
19
19
|
def export_paths(declarations)
|
20
|
-
declarations.each_with_object({}) do |declaration, paths|
|
20
|
+
declarations.sort.each_with_object({}) do |declaration, paths|
|
21
21
|
declaration.routes.each do |route|
|
22
22
|
paths[route.openapi_path] ||= {}
|
23
23
|
paths[route.openapi_path].merge! export_route(route, declaration)
|
@@ -31,30 +31,51 @@ class Taro::Export::OpenAPIv3 < Taro::Export::Base # rubocop:disable Metrics/Cla
|
|
31
31
|
description: declaration.desc,
|
32
32
|
summary: declaration.summary,
|
33
33
|
tags: declaration.tags,
|
34
|
-
parameters:
|
34
|
+
parameters: route_parameters(declaration, route),
|
35
35
|
requestBody: request_body(declaration, route),
|
36
36
|
responses: responses(declaration),
|
37
37
|
}.compact,
|
38
38
|
}
|
39
39
|
end
|
40
40
|
|
41
|
+
def route_parameters(declaration, route)
|
42
|
+
path_parameters(declaration, route) + query_parameters(declaration, route)
|
43
|
+
end
|
44
|
+
|
41
45
|
def path_parameters(declaration, route)
|
42
46
|
route.path_params.map do |param_name|
|
43
47
|
param_field = declaration.params.fields[param_name] || raise(<<~MSG)
|
44
48
|
Declaration missing for path param #{param_name} of route #{route.endpoint}
|
45
49
|
MSG
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
in: 'path',
|
50
|
-
description: param_field.desc,
|
51
|
-
required: true, # path params are always required in rails
|
52
|
-
schema: { type: param_field.openapi_type },
|
53
|
-
}.compact
|
51
|
+
# path params are always required in rails
|
52
|
+
export_parameter(param_field).merge(in: 'path', required: true)
|
54
53
|
end
|
55
54
|
end
|
56
55
|
|
56
|
+
def query_parameters(declaration, route)
|
57
|
+
return [] if route.can_have_request_body?
|
58
|
+
|
59
|
+
declaration.params.fields.filter_map do |name, param_field|
|
60
|
+
next if route.path_params.include?(name)
|
61
|
+
|
62
|
+
export_parameter(param_field).merge(in: 'query')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def export_parameter(field)
|
67
|
+
{
|
68
|
+
name: field.name,
|
69
|
+
deprecated: field.deprecated,
|
70
|
+
description: field.desc,
|
71
|
+
required: !field.null,
|
72
|
+
schema: { type: field.openapi_type },
|
73
|
+
}.compact
|
74
|
+
end
|
75
|
+
|
57
76
|
def request_body(declaration, route)
|
77
|
+
return unless route.can_have_request_body?
|
78
|
+
|
58
79
|
params = declaration.params
|
59
80
|
body_param_fields = params.fields.reject do |name, _field|
|
60
81
|
route.path_params.include?(name)
|
@@ -82,7 +103,7 @@ class Taro::Export::OpenAPIv3 < Taro::Export::Base # rubocop:disable Metrics/Cla
|
|
82
103
|
end
|
83
104
|
|
84
105
|
def responses(declaration)
|
85
|
-
declaration.returns.to_h do |code, type|
|
106
|
+
declaration.returns.sort.to_h do |code, type|
|
86
107
|
[
|
87
108
|
code.to_s,
|
88
109
|
{
|
@@ -115,23 +136,29 @@ class Taro::Export::OpenAPIv3 < Taro::Export::Base # rubocop:disable Metrics/Cla
|
|
115
136
|
# as it puts props like format together with the main type.
|
116
137
|
# https://github.com/OAI/OpenAPI-Specification/issues/3148
|
117
138
|
base = { oneOf: [base, { type: 'null' }] } if field.null
|
118
|
-
base
|
119
|
-
base[:default] = field.default if field.default_specified?
|
120
|
-
base[:enum] = field.enum if field.enum
|
121
|
-
base
|
139
|
+
base.merge(field_metadata(field))
|
122
140
|
end
|
123
141
|
|
124
142
|
def export_complex_field_ref(field)
|
125
143
|
ref = extract_component_ref(field.type)
|
144
|
+
return ref if field_metadata(field).empty? && !field.null
|
145
|
+
|
126
146
|
if field.null
|
127
147
|
# RE nullable: https://stackoverflow.com/a/70658334
|
128
|
-
{
|
129
|
-
|
148
|
+
{ oneOf: [ref, { type: 'null' }] }
|
149
|
+
else # i.e. with metadata such as description or deprecated
|
130
150
|
# https://github.com/OAI/OpenAPI-Specification/issues/2033
|
131
|
-
{
|
132
|
-
|
133
|
-
|
134
|
-
|
151
|
+
{ allOf: [ref] }
|
152
|
+
end.merge(field_metadata(field))
|
153
|
+
end
|
154
|
+
|
155
|
+
def field_metadata(field)
|
156
|
+
meta = {}
|
157
|
+
meta[:description] = field.desc if field.desc
|
158
|
+
meta[:deprecated] = field.deprecated unless field.deprecated.nil?
|
159
|
+
meta[:default] = field.default if field.default_specified?
|
160
|
+
meta[:enum] = field.enum if field.enum
|
161
|
+
meta
|
135
162
|
end
|
136
163
|
|
137
164
|
def extract_component_ref(type)
|
@@ -156,6 +183,7 @@ class Taro::Export::OpenAPIv3 < Taro::Export::Base # rubocop:disable Metrics/Cla
|
|
156
183
|
required = type.fields.values.reject(&:null).map(&:name)
|
157
184
|
{
|
158
185
|
type: type.openapi_type,
|
186
|
+
deprecated: type.deprecated,
|
159
187
|
description: type.desc,
|
160
188
|
required: (required if required.any?),
|
161
189
|
properties: type.fields.to_h { |name, f| [name, export_field(f)] },
|
@@ -166,6 +194,7 @@ class Taro::Export::OpenAPIv3 < Taro::Export::Base # rubocop:disable Metrics/Cla
|
|
166
194
|
def enum_type_details(enum)
|
167
195
|
{
|
168
196
|
type: enum.item_type.openapi_type,
|
197
|
+
deprecated: enum.deprecated,
|
169
198
|
description: enum.desc,
|
170
199
|
enum: enum.values,
|
171
200
|
}.compact
|
@@ -174,6 +203,7 @@ class Taro::Export::OpenAPIv3 < Taro::Export::Base # rubocop:disable Metrics/Cla
|
|
174
203
|
def list_type_details(list)
|
175
204
|
{
|
176
205
|
type: 'array',
|
206
|
+
deprecated: list.deprecated,
|
177
207
|
description: list.desc,
|
178
208
|
items: export_type(list.item_type),
|
179
209
|
}.compact
|
@@ -1,9 +1,9 @@
|
|
1
1
|
class Taro::Rails::Declaration
|
2
|
-
attr_reader :desc, :summary, :params, :
|
2
|
+
attr_reader :desc, :summary, :params, :return_defs, :return_descriptions, :return_nestings, :routes, :tags
|
3
3
|
|
4
4
|
def initialize
|
5
5
|
@params = Class.new(Taro::Types::InputType)
|
6
|
-
@
|
6
|
+
@return_defs = {}
|
7
7
|
@return_descriptions = {}
|
8
8
|
@return_nestings = {}
|
9
9
|
end
|
@@ -24,8 +24,11 @@ class Taro::Rails::Declaration
|
|
24
24
|
status = self.class.coerce_status_to_int(code)
|
25
25
|
raise_if_already_declared(status)
|
26
26
|
|
27
|
+
kwargs[:nesting] = nesting
|
28
|
+
check_return_kwargs(kwargs)
|
29
|
+
|
27
30
|
kwargs[:defined_at] = caller_locations(1..2)[1]
|
28
|
-
|
31
|
+
return_defs[status] = kwargs
|
29
32
|
|
30
33
|
# response desc is required in openapi 3 – fall back to status code
|
31
34
|
return_descriptions[status] = desc || code.to_s
|
@@ -34,14 +37,19 @@ class Taro::Rails::Declaration
|
|
34
37
|
return_nestings[status] = nesting if nesting
|
35
38
|
end
|
36
39
|
|
40
|
+
# Return types are evaluated lazily to avoid unnecessary autoloading
|
41
|
+
# of all types in dev/test envs.
|
42
|
+
def returns
|
43
|
+
@returns ||= evaluate_return_defs
|
44
|
+
end
|
45
|
+
|
37
46
|
def raise_if_already_declared(status)
|
38
|
-
|
47
|
+
return_defs[status] &&
|
39
48
|
raise(Taro::ArgumentError, "response for status #{status} already declared")
|
40
49
|
end
|
41
50
|
|
42
51
|
def parse_params(rails_params)
|
43
|
-
|
44
|
-
hash
|
52
|
+
params.new(rails_params.to_unsafe_h).coerce_input
|
45
53
|
end
|
46
54
|
|
47
55
|
def finalize(controller_class:, action_name:)
|
@@ -67,10 +75,15 @@ class Taro::Rails::Declaration
|
|
67
75
|
# TODO: these change when the controller class is renamed.
|
68
76
|
# We might need a way to set `base`. Perhaps as a kwarg to `::api`?
|
69
77
|
def add_openapi_names(controller_class:, action_name:)
|
70
|
-
base = "#{controller_class.name.chomp('Controller').
|
78
|
+
base = "#{controller_class.name.chomp('Controller').gsub('::', '_')}_#{action_name}"
|
71
79
|
params.openapi_name = "#{base}_Input"
|
80
|
+
params.define_singleton_method(:name) { openapi_name }
|
81
|
+
|
72
82
|
returns.each do |status, return_type|
|
83
|
+
next if return_type.openapi_name? # only set for ad-hoc / nested return types
|
84
|
+
|
73
85
|
return_type.openapi_name = "#{base}_#{status}_Response"
|
86
|
+
return_type.define_singleton_method(:name) { openapi_name }
|
74
87
|
end
|
75
88
|
end
|
76
89
|
|
@@ -86,19 +99,11 @@ class Taro::Rails::Declaration
|
|
86
99
|
|
87
100
|
private
|
88
101
|
|
89
|
-
def return_type_from(nesting, **kwargs)
|
90
|
-
if nesting
|
91
|
-
# ad-hoc return type, requiring the actual return type to be nested
|
92
|
-
Class.new(Taro::Types::ObjectType).tap do |type|
|
93
|
-
type.field(nesting, null: false, **kwargs)
|
94
|
-
end
|
95
|
-
else
|
96
|
-
check_return_kwargs(kwargs)
|
97
|
-
Taro::Types::Coercion.call(kwargs)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
102
|
def check_return_kwargs(kwargs)
|
103
|
+
# For nested returns, evaluate_return_def calls ::field, which validates
|
104
|
+
# field options, but does not trigger type autoloading.
|
105
|
+
return evaluate_return_def(**kwargs) if kwargs[:nesting]
|
106
|
+
|
102
107
|
if kwargs.key?(:null)
|
103
108
|
raise Taro::ArgumentError, <<~MSG
|
104
109
|
`null:` is not supported for top-level returns. If you want a nullable return
|
@@ -106,13 +111,32 @@ class Taro::Rails::Declaration
|
|
106
111
|
MSG
|
107
112
|
end
|
108
113
|
|
109
|
-
bad_keys = kwargs.keys - (Taro::Types::Coercion
|
114
|
+
bad_keys = kwargs.keys - (Taro::Types::Coercion.keys + %i[code desc nesting])
|
110
115
|
return if bad_keys.empty?
|
111
116
|
|
112
117
|
raise Taro::ArgumentError, "Invalid `returns` options: #{bad_keys.join(', ')}"
|
113
118
|
end
|
114
119
|
|
120
|
+
def evaluate_return_defs
|
121
|
+
return_defs.transform_values { |defi| evaluate_return_def(**defi) }
|
122
|
+
end
|
123
|
+
|
124
|
+
def evaluate_return_def(nesting:, **kwargs)
|
125
|
+
if nesting
|
126
|
+
# ad-hoc return type, requiring the actual return type to be nested
|
127
|
+
Class.new(Taro::Types::ObjectType).tap do |type|
|
128
|
+
type.field(nesting, null: false, **kwargs)
|
129
|
+
end
|
130
|
+
else
|
131
|
+
Taro::Types::Coercion.call(kwargs)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
115
135
|
def raise_missing_route(controller_class, action_name)
|
116
136
|
raise(Taro::ArgumentError, "No route found for #{controller_class}##{action_name}")
|
117
137
|
end
|
138
|
+
|
139
|
+
def <=>(other)
|
140
|
+
params.openapi_name <=> other.params.openapi_name
|
141
|
+
end
|
118
142
|
end
|
@@ -12,7 +12,7 @@ class Taro::Rails::Generators::InstallGenerator < ::Rails::Generators::Base
|
|
12
12
|
def create_type_files
|
13
13
|
Dir["#{self.class.source_root}/**/*.erb"].each do |tmpl|
|
14
14
|
dest_dir = options[:dir].chomp('/')
|
15
|
-
|
15
|
+
template tmpl, "#{dest_dir}/#{File.basename(tmpl).sub('erb', 'rb')}"
|
16
16
|
end
|
17
17
|
end
|
18
18
|
# :nocov:
|
@@ -11,15 +11,20 @@ class ErrorsType < Taro::Types::ListType
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def coerce_response
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
14
|
+
list =
|
15
|
+
case object<%- if defined?(ActiveRecord) %>
|
16
|
+
when ActiveModel::Errors
|
17
|
+
object.errors
|
18
|
+
when ActiveRecord::Base
|
19
|
+
object.errors.errors<%- end %>
|
20
|
+
when Array
|
21
|
+
object
|
22
|
+
when Hash<%- if defined?(Interactor::Context) %>, Interactor::Context<%- end %>
|
23
|
+
object.to_h.fetch(:errors)
|
24
|
+
else
|
25
|
+
response_error("must be an Enumerable or an object with errors")
|
26
|
+
end
|
27
|
+
|
28
|
+
list.map { |el| self.class.item_type.new(el).coerce_response }
|
24
29
|
end
|
25
30
|
end
|
@@ -26,4 +26,12 @@ Taro::Rails::NormalizedRoute = Data.define(:rails_route) do
|
|
26
26
|
controller, action = rails_route.requirements.values_at(:controller, :action)
|
27
27
|
"#{controller}##{action}"
|
28
28
|
end
|
29
|
+
|
30
|
+
def can_have_request_body?
|
31
|
+
%w[patch post put].include?(verb)
|
32
|
+
end
|
33
|
+
|
34
|
+
def inspect
|
35
|
+
%(#<#{self.class} "#{verb} #{openapi_path}">)
|
36
|
+
end
|
29
37
|
end
|
@@ -10,6 +10,10 @@ task 'taro:export' => :environment do
|
|
10
10
|
version: Taro.config.api_version,
|
11
11
|
)
|
12
12
|
|
13
|
-
data = export.
|
13
|
+
data = export.send("to_#{Taro.config.export_format}")
|
14
|
+
|
15
|
+
FileUtils.mkdir_p(File.dirname(Taro.config.export_path))
|
14
16
|
File.write(Taro.config.export_path, data)
|
17
|
+
|
18
|
+
puts "Exported #{Taro.config.api_name} to #{Taro.config.export_path}"
|
15
19
|
end
|
data/lib/taro/types/base_type.rb
CHANGED
@@ -9,6 +9,8 @@
|
|
9
9
|
Taro::Types::BaseType = Data.define(:object) do
|
10
10
|
require_relative "shared"
|
11
11
|
extend Taro::Types::Shared::AdditionalProperties
|
12
|
+
extend Taro::Types::Shared::Deprecation
|
13
|
+
extend Taro::Types::Shared::DerivedTypes
|
12
14
|
extend Taro::Types::Shared::Description
|
13
15
|
extend Taro::Types::Shared::OpenAPIName
|
14
16
|
extend Taro::Types::Shared::OpenAPIType
|
data/lib/taro/types/coercion.rb
CHANGED
@@ -1,42 +1,52 @@
|
|
1
1
|
module Taro::Types::Coercion
|
2
|
-
KEYS = %i[type array_of page_of].freeze
|
3
|
-
|
4
2
|
class << self
|
5
3
|
def call(arg)
|
6
4
|
validate_hash(arg)
|
7
5
|
from_hash(arg)
|
8
6
|
end
|
9
7
|
|
8
|
+
# Coercion keys can be expanded by the DerivedTypes module.
|
9
|
+
def keys
|
10
|
+
@keys ||= %i[type]
|
11
|
+
end
|
12
|
+
|
13
|
+
def derived_suffix
|
14
|
+
'_of'
|
15
|
+
end
|
16
|
+
|
10
17
|
private
|
11
18
|
|
12
19
|
def validate_hash(arg)
|
13
20
|
arg.is_a?(Hash) || raise(Taro::ArgumentError, <<~MSG)
|
14
|
-
Type coercion argument must be a Hash, got: #{arg.
|
21
|
+
Type coercion argument must be a Hash, got: #{arg.class}
|
15
22
|
MSG
|
16
23
|
|
17
|
-
types = arg.slice(*
|
24
|
+
types = arg.slice(*keys)
|
18
25
|
types.size == 1 || raise(Taro::ArgumentError, <<~MSG)
|
19
|
-
Exactly one of
|
26
|
+
Exactly one of #{keys.join(', ')} must be given, got: #{types}
|
20
27
|
MSG
|
21
28
|
end
|
22
29
|
|
23
30
|
def from_hash(hash)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
31
|
+
keys.each do |key|
|
32
|
+
next unless (value = hash[key])
|
33
|
+
|
34
|
+
# e.g. `returns type: 'MyType'` -> MyType
|
35
|
+
return from_string(value) if key == :type
|
36
|
+
|
37
|
+
# DerivedTypes
|
38
|
+
# e.g. `returns array_of: 'MyType'` -> MyType.array
|
39
|
+
return from_string(value).send(key.to_s.chomp(derived_suffix))
|
32
40
|
end
|
41
|
+
|
42
|
+
raise NotImplementedError, "Unsupported type coercion #{hash}"
|
33
43
|
end
|
34
44
|
|
35
45
|
def from_string(arg)
|
36
46
|
shortcuts[arg] || from_class(Object.const_get(arg.to_s))
|
37
47
|
rescue NameError
|
38
48
|
raise Taro::ArgumentError, <<~MSG
|
39
|
-
|
49
|
+
No such type: #{arg}. It should be a type-class name
|
40
50
|
or one of #{shortcuts.keys.map(&:inspect).join(', ')}.
|
41
51
|
MSG
|
42
52
|
end
|
data/lib/taro/types/field.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
require_relative 'field_validation'
|
2
2
|
|
3
|
-
Taro::Types::Field = Data.define(:name, :type, :null, :method, :default, :enum, :defined_at, :desc) do
|
3
|
+
Taro::Types::Field = Data.define(:name, :type, :null, :method, :default, :enum, :defined_at, :desc, :deprecated) do
|
4
4
|
include Taro::Types::FieldValidation
|
5
5
|
|
6
|
-
def initialize(name:, type:, null:, method: name, default: :none, enum: nil, defined_at: nil, desc: nil)
|
6
|
+
def initialize(name:, type:, null:, method: name, default: :none, enum: nil, defined_at: nil, desc: nil, deprecated: nil)
|
7
7
|
enum = coerce_to_enum(enum)
|
8
|
-
super(name:, type:, null:, method:, default:, enum:, defined_at:, desc:)
|
8
|
+
super(name:, type:, null:, method:, default:, enum:, defined_at:, desc:, deprecated:)
|
9
9
|
end
|
10
10
|
|
11
11
|
def value_for_input(object)
|
@@ -40,14 +40,15 @@ Taro::Types::Field = Data.define(:name, :type, :null, :method, :default, :enum,
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def retrieve_response_value(object, context, object_is_hash)
|
43
|
-
if
|
44
|
-
retrieve_hash_value(object)
|
45
|
-
elsif context&.resolve?(method)
|
43
|
+
if context&.resolve?(method)
|
46
44
|
context.public_send(method)
|
45
|
+
elsif object_is_hash
|
46
|
+
retrieve_hash_value(object)
|
47
47
|
elsif object.respond_to?(method, true)
|
48
48
|
object.public_send(method)
|
49
49
|
else
|
50
|
-
|
50
|
+
# Note that the ObjectCoercion module rescues this and adds context.
|
51
|
+
raise Taro::ResponseError, "No such method or resolver `:#{method}`."
|
51
52
|
end
|
52
53
|
end
|
53
54
|
|
@@ -65,14 +66,5 @@ Taro::Types::Field = Data.define(:name, :type, :null, :method, :default, :enum,
|
|
65
66
|
|
66
67
|
type_obj = type.new(value)
|
67
68
|
from_input ? type_obj.coerce_input : type_obj.coerce_response
|
68
|
-
rescue Taro::Error => e
|
69
|
-
raise e.class, "#{e.message}, after using method/key `:#{method}` to resolve field `#{name}`"
|
70
|
-
end
|
71
|
-
|
72
|
-
def raise_response_coercion_error(object)
|
73
|
-
raise Taro::ResponseError, <<~MSG
|
74
|
-
Failed to coerce value #{object.inspect} for field `#{name}` using method/key `:#{method}`.
|
75
|
-
It is not a valid #{type} value.
|
76
|
-
MSG
|
77
69
|
end
|
78
70
|
end
|
@@ -18,7 +18,7 @@ module Taro::Types::FieldValidation
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def validate_enum_inclusion(value, for_input)
|
21
|
-
return if enum.nil? || enum.include?(value)
|
21
|
+
return if enum.nil? || null && value.nil? || enum.include?(value)
|
22
22
|
|
23
23
|
raise for_input ? Taro::InputError : Taro::ResponseError, <<~MSG
|
24
24
|
Field #{name} has an invalid value #{value.inspect} (expected one of #{enum.inspect})
|
data/lib/taro/types/list_type.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
# Unlike other types, this one should not be manually inherited from,
|
3
3
|
# but is used indirectly via `array_of: SomeType`.
|
4
4
|
class Taro::Types::ListType < Taro::Types::BaseType
|
5
|
-
extend Taro::Types::Shared::DerivableType
|
6
5
|
extend Taro::Types::Shared::ItemType
|
7
6
|
|
8
7
|
self.openapi_type = :array
|
@@ -20,11 +19,10 @@ class Taro::Types::ListType < Taro::Types::BaseType
|
|
20
19
|
item_type = self.class.item_type
|
21
20
|
object.map { |el| item_type.new(el).coerce_response }
|
22
21
|
end
|
23
|
-
end
|
24
22
|
|
25
|
-
|
26
|
-
|
27
|
-
def self.array
|
28
|
-
Taro::Types::ListType.for(self)
|
23
|
+
def self.default_openapi_name
|
24
|
+
"#{item_type.openapi_name}_List"
|
29
25
|
end
|
26
|
+
|
27
|
+
define_derived_type :array, 'Taro::Types::ListType'
|
30
28
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
class Taro::Types::ObjectTypes::FreeFormType < Taro::Types::ObjectType
|
2
2
|
self.desc = 'An arbitrary, unvalidated Hash or JSON object. Use with care.'
|
3
3
|
self.additional_properties = true
|
4
|
+
self.openapi_name = 'FreeForm'
|
4
5
|
|
5
6
|
def coerce_input
|
6
7
|
object.is_a?(Hash) && object || input_error('must be a Hash')
|
@@ -1,4 +1,6 @@
|
|
1
1
|
class Taro::Types::ObjectTypes::PageInfoType < Taro::Types::ObjectType
|
2
|
+
self.openapi_name = 'PageInfo'
|
3
|
+
|
2
4
|
field :has_previous_page, type: 'Boolean', null: false, desc: 'Whether there is a previous page of results'
|
3
5
|
field :has_next_page, type: 'Boolean', null: false, desc: 'Whether there is another page of results'
|
4
6
|
field :start_cursor, type: 'String', null: true, desc: 'The first cursor in the current page of results (null if zero results)'
|
@@ -4,42 +4,32 @@
|
|
4
4
|
#
|
5
5
|
# The gem rails_cursor_pagination must be installed to use this.
|
6
6
|
#
|
7
|
-
class Taro::Types::ObjectTypes::PageType < Taro::Types::
|
8
|
-
extend Taro::Types::Shared::DerivableType
|
7
|
+
class Taro::Types::ObjectTypes::PageType < Taro::Types::ObjectType
|
9
8
|
extend Taro::Types::Shared::ItemType
|
10
9
|
|
10
|
+
def self.derive_from(from_type)
|
11
|
+
super
|
12
|
+
field(:page, array_of: from_type.name, null: false)
|
13
|
+
field(:page_info, type: 'Taro::Types::ObjectTypes::PageInfoType', null: false)
|
14
|
+
end
|
15
|
+
|
11
16
|
def coerce_input
|
12
17
|
input_error 'PageTypes cannot be used as input types'
|
13
18
|
end
|
14
19
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
20
|
+
def self.render(relation, after:, limit: 20, order_by: nil, order: nil)
|
21
|
+
result = RailsCursorPagination::Paginator.new(
|
22
|
+
relation, limit:, order_by:, order:, after:
|
18
23
|
).fetch
|
19
|
-
coerce_paginated_list(list)
|
20
|
-
end
|
21
24
|
|
22
|
-
|
23
|
-
item_type = self.class.item_type
|
24
|
-
items = list[:page].map do |item|
|
25
|
-
item_type.new(item[:data]).coerce_response
|
26
|
-
end
|
25
|
+
result[:page].map! { |el| el.fetch(:data) }
|
27
26
|
|
28
|
-
|
29
|
-
self.class.items_key => items,
|
30
|
-
page_info: Taro::Types::ObjectTypes::PageInfoType.new(list[:page_info]).coerce_response,
|
31
|
-
}
|
27
|
+
super(result)
|
32
28
|
end
|
33
29
|
|
34
|
-
|
35
|
-
|
36
|
-
:page
|
30
|
+
def self.default_openapi_name
|
31
|
+
"#{item_type.openapi_name}_Page"
|
37
32
|
end
|
38
|
-
end
|
39
33
|
|
40
|
-
|
41
|
-
class Taro::Types::BaseType
|
42
|
-
def self.page
|
43
|
-
Taro::Types::ObjectTypes::PageType.for(self)
|
44
|
-
end
|
34
|
+
define_derived_type :page, 'Taro::Types::ObjectTypes::PageType'
|
45
35
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
class Taro::Types::Scalar::ISO8601DateTimeType < Taro::Types::ScalarType
|
2
2
|
self.desc = 'Represents a time as DateTime in ISO8601 format.'
|
3
|
+
self.openapi_name = 'ISO8601DateTime'
|
3
4
|
self.openapi_type = :string
|
4
5
|
|
5
6
|
PATTERN = /\A\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):[0-5]\d:[0-5]\d(Z|[+-](0[0-9]|1[0-4]):[0-5]\d)\z/
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Taro::Types::Shared::DerivedTypes
|
2
|
+
# Adds `name` as a method to all type classes and adds
|
3
|
+
# `name`_of as a supported key to the Coercion module.
|
4
|
+
# When `name` is called on a type class T, it returns a new subclass
|
5
|
+
# S inheriting from `type` and passes T to S::derive_from.
|
6
|
+
def define_derived_type(name, type)
|
7
|
+
root = Taro::Types::BaseType
|
8
|
+
raise ArgumentError, "#{name} is already in use" if root.respond_to?(name)
|
9
|
+
|
10
|
+
ckey = :"#{name}#{Taro::Types::Coercion.derived_suffix}"
|
11
|
+
ckeys = Taro::Types::Coercion.keys
|
12
|
+
raise ArgumentError, "#{ckey} is already in use" if ckeys.include?(ckey)
|
13
|
+
|
14
|
+
root.define_singleton_method(name) do
|
15
|
+
derived_types[type] ||= begin
|
16
|
+
type_class = Taro::Types::Coercion.call(type:)
|
17
|
+
Class.new(type_class).tap { |t| t.derive_from(self) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
ckeys << ckey
|
22
|
+
end
|
23
|
+
|
24
|
+
def derived_types
|
25
|
+
@derived_types ||= {}
|
26
|
+
end
|
27
|
+
end
|
@@ -8,6 +8,8 @@ module Taro::Types::Shared::Errors
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def coerce_error_message(msg)
|
11
|
-
|
11
|
+
type_desc = (self.class.name || self.class.superclass.name)
|
12
|
+
.sub(/^Taro::Types::.*?([^:]+)Type$/, '\1')
|
13
|
+
"#{object.class} is not valid as #{type_desc}: #{msg}"
|
12
14
|
end
|
13
15
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
module Taro::Types::Shared::Fields
|
3
3
|
# Field types are set using class name Strings. The respective type classes
|
4
4
|
# are evaluated lazily to allow for circular or recursive type references,
|
5
|
-
# and to avoid unnecessary
|
5
|
+
# and to avoid unnecessary autoloading of all types in dev/test envs.
|
6
6
|
def field(name, **kwargs)
|
7
7
|
defined_at = kwargs[:defined_at] || caller_locations(1..1)[0]
|
8
8
|
validate_name(name, defined_at:)
|
@@ -27,11 +27,12 @@ module Taro::Types::Shared::Fields
|
|
27
27
|
[true, false].include?(kwargs[:null]) ||
|
28
28
|
raise(Taro::ArgumentError, "null has to be specified as true or false for field #{name} at #{defined_at}")
|
29
29
|
|
30
|
-
|
31
|
-
|
30
|
+
c_keys = Taro::Types::Coercion.keys
|
31
|
+
(type_keys = (kwargs.keys & c_keys)).size == 1 ||
|
32
|
+
raise(Taro::ArgumentError, "exactly one of #{c_keys.join(', ')} must be given for field #{name} at #{defined_at}")
|
32
33
|
|
33
34
|
kwargs[type_keys.first].class == String ||
|
34
|
-
raise(Taro::ArgumentError, "#{
|
35
|
+
raise(Taro::ArgumentError, "#{type_keys.first} must be a String for field #{name} at #{defined_at}")
|
35
36
|
end
|
36
37
|
|
37
38
|
def validate_no_override(name, defined_at:)
|
@@ -46,7 +47,7 @@ module Taro::Types::Shared::Fields
|
|
46
47
|
def evaluate_field_defs
|
47
48
|
field_defs.transform_values do |field_def|
|
48
49
|
type = Taro::Types::Coercion.call(field_def)
|
49
|
-
Taro::Types::Field.new(**field_def.except(*Taro::Types::Coercion
|
50
|
+
Taro::Types::Field.new(**field_def.except(*Taro::Types::Coercion.keys), type:)
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
@@ -3,6 +3,8 @@ module Taro::Types::Shared::ObjectCoercion
|
|
3
3
|
def coerce_input
|
4
4
|
self.class.fields.transform_values do |field|
|
5
5
|
field.value_for_input(object)
|
6
|
+
rescue Taro::Error => e
|
7
|
+
raise_enriched_coercion_error(e, field)
|
6
8
|
end
|
7
9
|
end
|
8
10
|
|
@@ -11,6 +13,17 @@ module Taro::Types::Shared::ObjectCoercion
|
|
11
13
|
object_is_hash = object.is_a?(Hash)
|
12
14
|
self.class.fields.transform_values do |field|
|
13
15
|
field.value_for_response(object, context: self, object_is_hash:)
|
16
|
+
rescue Taro::Error => e
|
17
|
+
raise_enriched_coercion_error(e, field)
|
14
18
|
end
|
15
19
|
end
|
20
|
+
|
21
|
+
def raise_enriched_coercion_error(error, field)
|
22
|
+
# The indentation is on purpose. These errors can be recursively rescued
|
23
|
+
# and re-raised by a tree of object types, which should be made apparent.
|
24
|
+
raise error.class, <<~MSG
|
25
|
+
Failed to read #{self.class.name} field `#{field.name}` from #{object.class}:
|
26
|
+
#{error.message.lines.map { |line| " #{line}" }.join}
|
27
|
+
MSG
|
28
|
+
end
|
16
29
|
end
|
@@ -5,13 +5,19 @@ module Taro::Types::Shared::OpenAPIName
|
|
5
5
|
@openapi_name ||= default_openapi_name
|
6
6
|
end
|
7
7
|
|
8
|
+
def openapi_name?
|
9
|
+
!!openapi_name
|
10
|
+
rescue Taro::Error
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
8
14
|
def openapi_name=(arg)
|
9
15
|
arg.nil? || arg.is_a?(String) ||
|
10
16
|
raise(Taro::ArgumentError, 'openapi_name must be a String')
|
11
17
|
@openapi_name = arg
|
12
18
|
end
|
13
19
|
|
14
|
-
def default_openapi_name
|
20
|
+
def default_openapi_name
|
15
21
|
if self < Taro::Types::EnumType ||
|
16
22
|
self < Taro::Types::InputType ||
|
17
23
|
self < Taro::Types::ObjectType
|
@@ -19,12 +25,8 @@ module Taro::Types::Shared::OpenAPIName
|
|
19
25
|
raise(Taro::Error, 'openapi_name must be set for anonymous type classes')
|
20
26
|
elsif self < Taro::Types::ScalarType
|
21
27
|
openapi_type
|
22
|
-
elsif self < Taro::Types::ListType
|
23
|
-
"#{item_type.openapi_name}_List"
|
24
|
-
elsif self < Taro::Types::ObjectTypes::PageType
|
25
|
-
"#{item_type.openapi_name}_Page"
|
26
28
|
else
|
27
|
-
raise
|
29
|
+
raise Taro::Error, 'no default_openapi_name implemented for this type'
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
# The `::render` method is intended for use in controllers.
|
2
|
-
# Special types (e.g. PageType) may accept kwargs for `#coerce_response`.
|
3
1
|
module Taro::Types::Shared::Rendering
|
4
|
-
|
5
|
-
|
2
|
+
# The `::render` method is intended for use in controllers.
|
3
|
+
# Overrides of this method must call super.
|
4
|
+
def render(object)
|
5
|
+
result = new(object).coerce_response
|
6
6
|
self.last_render = [self, result.__id__]
|
7
7
|
result
|
8
8
|
end
|
data/lib/taro/version.rb
CHANGED
data/tasks/benchmark.rake
CHANGED
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: taro
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Janosch Müller
|
8
|
+
- Johannes Opper
|
8
9
|
autorequire:
|
9
10
|
bindir: exe
|
10
11
|
cert_chain: []
|
11
|
-
date: 2024-11-
|
12
|
+
date: 2024-11-18 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: rack
|
@@ -27,7 +28,6 @@ dependencies:
|
|
27
28
|
description: This library provides an object-based type system for RESTful Ruby APIs,
|
28
29
|
with built-in parameter parsing, response rendering, and OpenAPI schema export.
|
29
30
|
email:
|
30
|
-
- janosch84@gmail.com
|
31
31
|
executables: []
|
32
32
|
extensions: []
|
33
33
|
extra_rdoc_files: []
|
@@ -90,7 +90,8 @@ files:
|
|
90
90
|
- lib/taro/types/shared.rb
|
91
91
|
- lib/taro/types/shared/additional_properties.rb
|
92
92
|
- lib/taro/types/shared/custom_field_resolvers.rb
|
93
|
-
- lib/taro/types/shared/
|
93
|
+
- lib/taro/types/shared/deprecation.rb
|
94
|
+
- lib/taro/types/shared/derived_types.rb
|
94
95
|
- lib/taro/types/shared/description.rb
|
95
96
|
- lib/taro/types/shared/errors.rb
|
96
97
|
- lib/taro/types/shared/fields.rb
|
@@ -125,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
126
|
- !ruby/object:Gem::Version
|
126
127
|
version: '0'
|
127
128
|
requirements: []
|
128
|
-
rubygems_version: 3.5.
|
129
|
+
rubygems_version: 3.5.22
|
129
130
|
signing_key:
|
130
131
|
specification_version: 4
|
131
132
|
summary: Typed Api using Ruby Objects.
|