sober_swag 0.15.0 → 0.16.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/.github/workflows/lint.yml +2 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +46 -5
- data/CHANGELOG.md +12 -1
- data/README.md +141 -4
- data/bin/rspec +29 -0
- data/docs/serializers.md +15 -13
- data/lib/sober_swag/controller.rb +3 -3
- data/lib/sober_swag/controller/route.rb +21 -8
- data/lib/sober_swag/input_object.rb +29 -2
- data/lib/sober_swag/version.rb +1 -1
- data/sober_swag.gemspec +2 -2
- metadata +11 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad86b8f4701d16ec53f0fc998bb2a881cdc448acfbb509bbfeb7821cc7142d1b
|
4
|
+
data.tar.gz: ce70ffa29ca02b8fc70e7f78c0b41bf56e8e3cdffbcf7282d8dad1cf16717b23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '081ef6717877b1c40682fbcbf1436a7e44fd51307a03650edc9a6d4a594a99bc4e9f07adbb73a9073f784aa75dc6a5463f0aa1d77b77fefc976c0531273ced22'
|
7
|
+
data.tar.gz: c1fb5da8a19b9d6ee1324badbbff4caac4db49349efd301b9daaa74097372f2241e174617af5b59970b467610145419b6e392b579fc4a3457073439e5ec6824e
|
data/.github/workflows/lint.yml
CHANGED
@@ -29,7 +29,7 @@ jobs:
|
|
29
29
|
# uses: ruby/setup-ruby@v1
|
30
30
|
uses: ruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0
|
31
31
|
with:
|
32
|
-
ruby-version:
|
32
|
+
ruby-version: ${{ matrix.ruby }}
|
33
33
|
- uses: actions/cache@v2
|
34
34
|
with:
|
35
35
|
path: vendor/bundle
|
@@ -40,10 +40,8 @@ jobs:
|
|
40
40
|
run: |
|
41
41
|
bundle config path vendor/bundle
|
42
42
|
bundle install
|
43
|
-
gem install rubocop
|
44
|
-
gem install rubocop-rspec
|
45
43
|
- name: Run Lints
|
46
|
-
run:
|
44
|
+
run: bundle exec rubocop -P
|
47
45
|
- uses: actions/cache@v2
|
48
46
|
with:
|
49
47
|
path: example/vendor/bundle
|
data/.rubocop.yml
CHANGED
@@ -1,85 +1,126 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
Style/BlockDelimiters:
|
4
|
-
EnforcedStyle: braces_for_chaining
|
1
|
+
require: rubocop-rspec
|
2
|
+
|
5
3
|
AllCops:
|
6
4
|
TargetRubyVersion: 2.6.0
|
7
5
|
Exclude:
|
8
6
|
- 'bin/bundle'
|
9
7
|
- 'example/bin/bundle'
|
8
|
+
- 'vendor/bundle/**/*'
|
10
9
|
|
11
10
|
Layout/LineLength:
|
12
11
|
Max: 160
|
13
|
-
|
12
|
+
|
14
13
|
RSpec/NamedSubject:
|
15
14
|
Enabled: false
|
15
|
+
|
16
16
|
RSpec/DescribeClass:
|
17
17
|
Enabled: false
|
18
|
+
|
18
19
|
Metrics/BlockLength:
|
19
20
|
Exclude:
|
20
21
|
- 'spec/**/*.rb'
|
21
22
|
- 'sober_swag.gemspec'
|
22
23
|
- 'example/spec/**/*.rb'
|
24
|
+
|
23
25
|
RSpec/ImplicitBlockExpectation:
|
24
26
|
Enabled: false
|
27
|
+
|
25
28
|
RSpec/ImplicitExpect:
|
26
29
|
EnforcedStyle: should
|
30
|
+
|
27
31
|
RSpec/LeadingSubject:
|
28
32
|
Enabled: false
|
33
|
+
|
29
34
|
Style/MultilineBlockChain:
|
30
35
|
Enabled: false
|
36
|
+
|
31
37
|
Metrics/AbcSize:
|
32
38
|
Enabled: false
|
39
|
+
|
33
40
|
Style/Documentation:
|
34
41
|
Exclude:
|
35
42
|
- 'example/db/migrate/**/*'
|
43
|
+
|
36
44
|
Metrics/PerceivedComplexity:
|
37
45
|
Enabled: false
|
46
|
+
|
38
47
|
Layout/EmptyLinesAroundAttributeAccessor:
|
39
48
|
Enabled: true
|
49
|
+
|
40
50
|
Layout/SpaceAroundMethodCallOperator:
|
41
51
|
Enabled: true
|
52
|
+
|
42
53
|
Lint/DeprecatedOpenSSLConstant:
|
43
54
|
Enabled: true
|
55
|
+
|
44
56
|
Lint/DuplicateElsifCondition:
|
45
57
|
Enabled: true
|
58
|
+
|
46
59
|
Lint/MixedRegexpCaptureTypes:
|
47
60
|
Enabled: true
|
61
|
+
|
48
62
|
Lint/RaiseException:
|
49
63
|
Enabled: true
|
64
|
+
|
50
65
|
Lint/StructNewOverride:
|
51
66
|
Enabled: true
|
67
|
+
|
52
68
|
Style/AccessorGrouping:
|
53
69
|
Enabled: true
|
70
|
+
|
54
71
|
Style/ArrayCoercion:
|
55
72
|
Enabled: true
|
73
|
+
|
56
74
|
Style/BisectedAttrAccessor:
|
57
75
|
Enabled: true
|
76
|
+
|
58
77
|
Style/CaseLikeIf:
|
59
78
|
Enabled: true
|
79
|
+
|
60
80
|
Style/ExponentialNotation:
|
61
81
|
Enabled: true
|
82
|
+
|
62
83
|
Style/HashAsLastArrayItem:
|
63
84
|
Enabled: true
|
85
|
+
|
64
86
|
Style/HashEachMethods:
|
65
87
|
Enabled: true
|
88
|
+
|
66
89
|
Style/HashLikeCase:
|
67
90
|
Enabled: true
|
91
|
+
|
68
92
|
Style/HashTransformKeys:
|
69
93
|
Enabled: true
|
94
|
+
|
70
95
|
Style/HashTransformValues:
|
71
96
|
Enabled: true
|
97
|
+
|
72
98
|
Style/RedundantAssignment:
|
73
99
|
Enabled: true
|
100
|
+
|
74
101
|
Style/RedundantFetchBlock:
|
75
102
|
Enabled: true
|
103
|
+
|
76
104
|
Style/RedundantFileExtensionInRequire:
|
77
105
|
Enabled: true
|
106
|
+
|
78
107
|
Style/RedundantRegexpCharacterClass:
|
79
108
|
Enabled: true
|
109
|
+
|
80
110
|
Style/RedundantRegexpEscape:
|
81
111
|
Enabled: true
|
112
|
+
|
82
113
|
Style/SlicingWithRange:
|
83
114
|
Enabled: true
|
115
|
+
|
84
116
|
RSpec/NestedGroups:
|
85
117
|
Max: 5
|
118
|
+
|
119
|
+
RSpec/ExampleLength:
|
120
|
+
Max: 10
|
121
|
+
|
122
|
+
Style/FrozenStringLiteralComment:
|
123
|
+
Enabled: false
|
124
|
+
|
125
|
+
Style/BlockDelimiters:
|
126
|
+
EnforcedStyle: braces_for_chaining
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
##
|
3
|
+
## [v0.16.0]: 2020-10-23
|
4
4
|
|
5
|
+
- Allow non-class types to be used as the inputs to controllers
|
6
|
+
|
7
|
+
## [v0.15.0]: 2020-09-02
|
8
|
+
|
9
|
+
### Added
|
10
|
+
- Add a new `#merge` method to output objects, which will merge fields from another output object into the given output object.
|
5
11
|
- Add `multi` to Output Objects, as a way to define more than one field of the same type at once.
|
6
12
|
- Add an `inherits:` key to output objects, for view inheritance.
|
7
13
|
- Add `SoberSwag::Types::CommaArray`, which parses comma-separated strings into arrays.
|
@@ -9,3 +15,8 @@
|
|
9
15
|
This class is mostly useful for query parameters where you want a simpler format: `tag=foo,bar` instead of `tag[]=foo,tag[]=bar`.
|
10
16
|
- Add support for using `meta` to specify alternative `style` and `explode` keys for query and path params.
|
11
17
|
Note that this support *does not* extend to parsing: If you modify the `style` or `explode` keywords, you will need to make those input formats work with the actual type yourself.
|
18
|
+
|
19
|
+
### Fixed
|
20
|
+
- No longer swallow `Dry::Struct` errors, instead let them surface to the user.
|
21
|
+
|
22
|
+
[v0.15.0]: https://github.com/SonderMindOrg/sober_swag/releases/tag/v0.15.0
|
data/README.md
CHANGED
@@ -9,6 +9,10 @@ This generates documentation from *types*, which (conveniently) also lets you ge
|
|
9
9
|
|
10
10
|
An introductory presentation is available [here](https://www.icloud.com/keynote/0bxP3Dn8ETNO0lpsSQSVfEL6Q#SoberSwagPresentation).
|
11
11
|
|
12
|
+
Further documentation on using the gem is available in the `docs/` directory:
|
13
|
+
|
14
|
+
- [Serializers](docs/serializers.md)
|
15
|
+
|
12
16
|
## Types for a fully-automated API
|
13
17
|
|
14
18
|
SoberSwag lets you type your API using describe blocks.
|
@@ -18,7 +22,14 @@ This lets you type your API endpoint:
|
|
18
22
|
```ruby
|
19
23
|
class PeopleController < ApplicationController
|
20
24
|
include SoberSwag::Controller
|
25
|
+
|
21
26
|
define :patch, :update, '/people/{id}' do
|
27
|
+
summary 'Update a Person record.'
|
28
|
+
description <<~MARKDOWN
|
29
|
+
You can use this endpoint to update a Person record. Note that age cannot
|
30
|
+
be a negative integer.
|
31
|
+
MARKDOWN
|
32
|
+
|
22
33
|
query_params do
|
23
34
|
attribute? :include_extra_info, Types::Params::Bool
|
24
35
|
end
|
@@ -28,11 +39,13 @@ class PeopleController < ApplicationController
|
|
28
39
|
end
|
29
40
|
path_params { attribute :id, Types::Params::Integer }
|
30
41
|
end
|
42
|
+
def update
|
43
|
+
# update action here
|
44
|
+
end
|
31
45
|
end
|
32
46
|
```
|
33
47
|
|
34
|
-
|
35
|
-
More than that, we can use this information *inside* our controller methods:
|
48
|
+
Then we can use the information from our SoberSwag definition *inside* the controller method:
|
36
49
|
|
37
50
|
```ruby
|
38
51
|
def update
|
@@ -44,6 +57,51 @@ end
|
|
44
57
|
No need for `params.require` or anything like that.
|
45
58
|
You define the type of parameters you accept, and we reject anything that doesn't fit.
|
46
59
|
|
60
|
+
### Rendering Swagger documentation from SoberSwag
|
61
|
+
|
62
|
+
We can also use the information from SoberSwag objects to generate Swagger
|
63
|
+
documentation, available at the `swagger` action on this controller.
|
64
|
+
|
65
|
+
You can create the `swagger` action for a controller as follows:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
# config/routes.rb
|
69
|
+
Rails.application.routes.draw do
|
70
|
+
# Add a `swagger` GET endpoint to render the Swagger documentation created
|
71
|
+
# by SoberSwag.
|
72
|
+
resources :people do
|
73
|
+
get :swagger, on: :collection
|
74
|
+
end
|
75
|
+
|
76
|
+
# Or use a concern to make it easier to enable swagger endpoints for a number
|
77
|
+
# of controllers at once.
|
78
|
+
concern :swaggerable do
|
79
|
+
get :swagger, on: :collection
|
80
|
+
end
|
81
|
+
|
82
|
+
resources :people, concerns: :swaggerable do
|
83
|
+
get :search, on: :collection
|
84
|
+
end
|
85
|
+
|
86
|
+
resources :places, only: [:index], concerns: :swaggerable
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
If you don't want the API documentation to show up in certain cases, you can
|
91
|
+
use an environment variable or a check on the current Rails environment.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
# config/routes.rb
|
95
|
+
Rails.application.routes.draw do
|
96
|
+
resources :people do
|
97
|
+
# Enable based on environment variable.
|
98
|
+
get :swagger, on: :collection if ENV['ENABLE_SWAGGER']
|
99
|
+
# Or just disable in production.
|
100
|
+
get :swagger, on: :collection unless Rails.env.production?
|
101
|
+
end
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
47
105
|
### Typed Responses
|
48
106
|
|
49
107
|
Want to go further and type your responses too?
|
@@ -53,6 +111,8 @@ Use SoberSwag output objects, a serializer library heavily inspired by [Blueprin
|
|
53
111
|
PersonOutputObject = SoberSwag::OutputObject.define do
|
54
112
|
field :id, primitive(:Integer)
|
55
113
|
field :name, primitive(:String).optional
|
114
|
+
# For fields that don't map to a simple attribute on your model, you can
|
115
|
+
# use a block.
|
56
116
|
field :is_registered, primitive(:Bool) do |person|
|
57
117
|
person.registered?
|
58
118
|
end
|
@@ -95,7 +155,7 @@ User = SoberSwag.input_object do
|
|
95
155
|
attribute :name, SoberSwag::Types::String
|
96
156
|
# use ? if attributes are not required
|
97
157
|
attribute? :favorite_movie, SoberSwag::Types::String
|
98
|
-
# use .optional if attributes may be
|
158
|
+
# use .optional if attributes may be nil
|
99
159
|
attribute :age, SoberSwag::Types::Params::Integer.optional
|
100
160
|
end
|
101
161
|
```
|
@@ -120,6 +180,63 @@ end
|
|
120
180
|
Under the hood, this literally just generates a subclass of `Dry::Struct`.
|
121
181
|
We use the DSL-like method just to make working with Rails' reloading less annoying.
|
122
182
|
|
183
|
+
#### Nested object attributes
|
184
|
+
|
185
|
+
You can nest attributes using a block. They'll return as nested JSON objects.
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
User = SoberSwag.input_object do
|
189
|
+
attribute :user_notes do
|
190
|
+
attribute :note, SoberSwag::Types::String
|
191
|
+
end
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
If you want to use a specific type of object within an input object, you can
|
196
|
+
nest them by setting the other input object as the type of an attribute. For
|
197
|
+
example, if you had a UserGroup object with various Users, you could write
|
198
|
+
them like this:
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
User = SoberSwag.input_object do
|
202
|
+
attribute :name, SoberSwag::Types::String
|
203
|
+
attribute :age, SoberSwag::Types::Params::Integer.optional
|
204
|
+
end
|
205
|
+
|
206
|
+
UserGroup = SoberSwag.input_object do
|
207
|
+
attribute :name, SoberSwag::Types::String
|
208
|
+
attribute :users, SoberSwag::Types::Array.of(User)
|
209
|
+
end
|
210
|
+
```
|
211
|
+
|
212
|
+
#### Input and Output Object Identifiers
|
213
|
+
|
214
|
+
Both input objects and output objects accept an identifier, which is used in
|
215
|
+
the Swagger Documentation to disambiguate between SoberSwag types.
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
User = SoberSwag.input_object do
|
219
|
+
identifier 'User'
|
220
|
+
|
221
|
+
attribute? :name, SoberSwag::Types::String
|
222
|
+
end
|
223
|
+
```
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
PersonOutputObject = SoberSwag::OutputObject.define do
|
227
|
+
identifier 'PersonOutput'
|
228
|
+
|
229
|
+
field :id, primitive(:Integer)
|
230
|
+
field :name, primitive(:String).optional
|
231
|
+
end
|
232
|
+
```
|
233
|
+
|
234
|
+
You can use these to make your Swagger documentation a bit easier to follow,
|
235
|
+
and it can also be useful for 'namespacing' objects if you're developing in
|
236
|
+
a large application, e.g. if you had a pet store and for some reason users
|
237
|
+
with cats and users with dogs were different, you could namespace it with
|
238
|
+
`identifier 'Dogs.User'`.
|
239
|
+
|
123
240
|
#### Adding additional documentation
|
124
241
|
|
125
242
|
You can use the `.meta` attribute on a type to add additional documentation.
|
@@ -151,10 +268,30 @@ QueryInput = SoberSwag.input_object do
|
|
151
268
|
end
|
152
269
|
```
|
153
270
|
|
271
|
+
## Testing the validity of output objects
|
272
|
+
|
273
|
+
If you're using RSpec and want to test the validity of output objects, you can do so relatively easily.
|
274
|
+
|
275
|
+
For example, assuming that you have a `UserOutputObject` class for representing a User record, and you have a `:user` factory via FactoryBot, you can validate that the serialization works without error like so:
|
276
|
+
|
277
|
+
```ruby
|
278
|
+
RSpec.describe UserOutputObject do
|
279
|
+
describe 'serialized result' do
|
280
|
+
subject do
|
281
|
+
described_class.type.new(described_class.serialize(create(:user)))
|
282
|
+
end
|
283
|
+
|
284
|
+
it 'works with an object' do
|
285
|
+
expect { subject }.not_to raise_error
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
```
|
290
|
+
|
154
291
|
## Special Thanks
|
155
292
|
|
156
293
|
This gem is a mishmash of ideas from various sources.
|
157
294
|
The biggest thanks is owed to the [dry-rb](https://github.com/dry-rb) project, upon which the typing of SoberSwag is based.
|
158
295
|
On an API design level, much is owed to [blueprinter](https://github.com/procore/blueprinter) for the serializers.
|
159
296
|
The idea of a strongly-typed API came from the Haskell framework [servant](https://www.servant.dev/).
|
160
|
-
Generating the swagger
|
297
|
+
Generating the swagger documentation happens via the use of a catamorphism, which I believe I first really understood thanks to [this medium article by Jared Tobin](https://medium.com/@jaredtobin/practical-recursion-schemes-c10648ec1c29).
|
data/bin/rspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'pathname'
|
12
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path('bundle', __dir__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'rubygems'
|
27
|
+
require 'bundler/setup'
|
28
|
+
|
29
|
+
load Gem.bin_path('rspec-core', 'rspec')
|
data/docs/serializers.md
CHANGED
@@ -25,21 +25,21 @@ For example, you might have a serializer that can return a date in two formats,
|
|
25
25
|
In this case, it might be used as:
|
26
26
|
|
27
27
|
```ruby
|
28
|
-
serializer.new(my_record, { format: :
|
28
|
+
serializer.new(my_record, { format: :new_style })
|
29
29
|
```
|
30
30
|
|
31
31
|
However, since it is *always* optional, you can also do:
|
32
32
|
|
33
33
|
```ruby
|
34
|
-
|
34
|
+
serializer.new(my_record)
|
35
35
|
```
|
36
36
|
|
37
37
|
And it *should* pick some default format.
|
38
38
|
|
39
39
|
### Primitives
|
40
40
|
|
41
|
-
Primitive serializers
|
42
|
-
They are implemented as [`SoberSwag::Serializer::Primitive`](../lib/sober_swag/serializer/primitive.rb), or as the `#primitive` method on
|
41
|
+
Primitive serializers — or "identity serializers" — are serializers that do nothing.
|
42
|
+
They are implemented as [`SoberSwag::Serializer::Primitive`](../lib/sober_swag/serializer/primitive.rb), or as the `#primitive` method on an `OutputObject`.
|
43
43
|
Since they don't do anything, they can be considered the most "basic" serializer.
|
44
44
|
|
45
45
|
These serializers *do not* check types.
|
@@ -51,12 +51,12 @@ serializer.serialize(10) # => 10
|
|
51
51
|
```
|
52
52
|
|
53
53
|
Thus, care should be used when working with these serializers.
|
54
|
-
In the future, we might add some "debug mode"
|
54
|
+
In the future, we might add some "debug mode" thing that will do type-checking and throw errors, however, the cost of doing so in production is probably not worth it.
|
55
55
|
|
56
56
|
### Mapped
|
57
57
|
|
58
58
|
Sometimes, you can create a serializer via a *proc*.
|
59
|
-
For example, let's say that I want a serializer that takes a `Date` and returns a
|
59
|
+
For example, let's say that I want a serializer that takes a `Date` and returns a String.
|
60
60
|
I can do this:
|
61
61
|
|
62
62
|
```ruby
|
@@ -74,7 +74,7 @@ In the future, we might add a debug mode.
|
|
74
74
|
Oftentimes, we want to give a serializer the ability to serialize `nil` values.
|
75
75
|
This is often useful in serializing fields.
|
76
76
|
|
77
|
-
It turns out that it's pretty easy to make a serializer that can serialize `nil` values: just propogate
|
77
|
+
It turns out that it's pretty easy to make a serializer that can serialize `nil` values: just propogate `nil`s.
|
78
78
|
For example, let's say I have the following code:
|
79
79
|
|
80
80
|
```ruby
|
@@ -99,7 +99,7 @@ Continuing our example from earlier:
|
|
99
99
|
my_serializer.array.serialize([Foo.new(10, 11)]) #=> [{ bar: 10, baz: 11 }]
|
100
100
|
```
|
101
101
|
|
102
|
-
This changes the type properly
|
102
|
+
This changes the type properly too.
|
103
103
|
|
104
104
|
## OutputObjects
|
105
105
|
|
@@ -132,7 +132,7 @@ end
|
|
132
132
|
We can see a few things here:
|
133
133
|
|
134
134
|
1. You define field names with a `field` definition, which is a way to define the serializer for a single field.
|
135
|
-
2. You must provide types with field names
|
135
|
+
2. You must provide types with field names.
|
136
136
|
3. You can use blocks to do data formatting, which lets you pick different fields and such.
|
137
137
|
|
138
138
|
|
@@ -223,12 +223,12 @@ For clarity (and to prevent infinitely-looping serializers on accident, we recom
|
|
223
223
|
Output objects don't support inheritance.
|
224
224
|
You can't have one output object based on another.
|
225
225
|
You *can*, however, merge one into another!
|
226
|
-
|
226
|
+
Consider this case:
|
227
227
|
|
228
228
|
```ruby
|
229
229
|
GenericBioOutput = SoberSwag::OutputObject.define do
|
230
230
|
field :name, primitive(:String)
|
231
|
-
field :brief_history,
|
231
|
+
field :brief_history, primitive(:String)
|
232
232
|
end
|
233
233
|
|
234
234
|
ExecutiveBioOutput = SoberSwag::OutputObject.define do
|
@@ -248,14 +248,16 @@ Identifiers and views will not be copied over.
|
|
248
248
|
|
249
249
|
While defining a new Output Object, you *do not* have access to the definition of that output object.
|
250
250
|
So, how do I say that one view should be an extension of another?
|
251
|
-
Simple
|
251
|
+
Simple, use the `inherits:` kwarg:
|
252
252
|
|
253
253
|
```ruby
|
254
254
|
BioOutput = SoberSwag::OutputObject.define do
|
255
255
|
field :name, primitive(:String)
|
256
|
+
|
256
257
|
view :detail do
|
257
258
|
field :bio, primitive(:String)
|
258
259
|
end
|
260
|
+
|
259
261
|
view :super_detail, inherits: :detail do
|
260
262
|
field :age, primitive(:Integer)
|
261
263
|
end
|
@@ -263,4 +265,4 @@ end
|
|
263
265
|
```
|
264
266
|
|
265
267
|
`inherits` will automatically merge in all the fields of the referenced view.
|
266
|
-
This means that the view `super_detail`
|
268
|
+
This means that the view `super_detail` will include fields `name`, `bio`, and `age`.
|
@@ -90,7 +90,7 @@ module SoberSwag
|
|
90
90
|
r = current_action_def
|
91
91
|
raise UndefinedPathError unless r&.path_params_class
|
92
92
|
|
93
|
-
r.path_params_class.
|
93
|
+
r.path_params_class.call(request.path_parameters)
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
@@ -104,7 +104,7 @@ module SoberSwag
|
|
104
104
|
r = current_action_def
|
105
105
|
raise UndefinedBodyError unless r&.request_body_class
|
106
106
|
|
107
|
-
r.request_body_class.
|
107
|
+
r.request_body_class.call(body_params)
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
@@ -118,7 +118,7 @@ module SoberSwag
|
|
118
118
|
r = current_action_def
|
119
119
|
raise UndefinedQueryError unless r&.query_params_class
|
120
120
|
|
121
|
-
r.query_params_class.
|
121
|
+
r.query_params_class.call(request.query_parameters)
|
122
122
|
end
|
123
123
|
end
|
124
124
|
|
@@ -14,14 +14,13 @@ module SoberSwag
|
|
14
14
|
attr_reader :response_serializers, :response_descriptions, :controller, :method, :path, :action_name
|
15
15
|
|
16
16
|
##
|
17
|
-
# What to parse the request body
|
17
|
+
# What to parse the request body into.
|
18
18
|
attr_reader :request_body_class
|
19
19
|
##
|
20
|
-
# What to parse the request query_params
|
20
|
+
# What to parse the request query_params into.
|
21
21
|
attr_reader :query_params_class
|
22
|
-
|
23
22
|
##
|
24
|
-
# What to parse the path params into
|
23
|
+
# What to parse the path params into.
|
25
24
|
attr_reader :path_params_class
|
26
25
|
|
27
26
|
##
|
@@ -30,7 +29,7 @@ module SoberSwag
|
|
30
29
|
# If you want, you can also define utility methods in here
|
31
30
|
def request_body(base = SoberSwag::InputObject, &block)
|
32
31
|
@request_body_class = make_input_object!(base, &block)
|
33
|
-
action_module.const_set('
|
32
|
+
action_module.const_set('RequestBody', @request_body_class)
|
34
33
|
end
|
35
34
|
|
36
35
|
##
|
@@ -103,7 +102,7 @@ module SoberSwag
|
|
103
102
|
def response(status_code, description, serializer = nil, &block)
|
104
103
|
status_key = Rack::Utils.status_code(status_code)
|
105
104
|
|
106
|
-
raise ArgumentError, 'Response
|
105
|
+
raise ArgumentError, 'Response defined!' if @response_serializers.key?(status_key)
|
107
106
|
|
108
107
|
serializer ||= SoberSwag::OutputObject.define(&block)
|
109
108
|
response_module.const_set(status_code.to_s.classify, serializer)
|
@@ -124,8 +123,22 @@ module SoberSwag
|
|
124
123
|
end
|
125
124
|
|
126
125
|
def make_input_object!(base, &block)
|
127
|
-
|
128
|
-
|
126
|
+
if base.is_a?(Class)
|
127
|
+
make_input_class(base, block)
|
128
|
+
elsif block
|
129
|
+
raise ArgumentError, 'passed a non-class base and a block to an input'
|
130
|
+
else
|
131
|
+
base
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def make_input_class(base, block)
|
136
|
+
if block
|
137
|
+
Class.new(base, &block).tap do |e|
|
138
|
+
e.transform_keys(&:to_sym) if [SoberSwag::InputObject, Dry::Struct].include?(base)
|
139
|
+
end
|
140
|
+
else
|
141
|
+
base
|
129
142
|
end
|
130
143
|
end
|
131
144
|
end
|
@@ -18,6 +18,18 @@ module SoberSwag
|
|
18
18
|
@identifier || name.to_s.gsub('::', '.')
|
19
19
|
end
|
20
20
|
|
21
|
+
def attribute(key, parent = SoberSwag::InputObject, &block)
|
22
|
+
raise ArgumentError, "parent class #{parent} is not an input object type!" unless valid_field_def?(parent, block)
|
23
|
+
|
24
|
+
super(key, parent, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def attribute?(key, parent = SoberSwag::InputObject, &block)
|
28
|
+
raise ArgumentError, "parent class #{parent} is not an input object type!" unless valid_field_def?(parent, block)
|
29
|
+
|
30
|
+
super(key, parent, &block)
|
31
|
+
end
|
32
|
+
|
21
33
|
def meta(*args)
|
22
34
|
original = self
|
23
35
|
|
@@ -29,13 +41,28 @@ module SoberSwag
|
|
29
41
|
end
|
30
42
|
end
|
31
43
|
|
32
|
-
|
33
|
-
|
44
|
+
##
|
45
|
+
# .primitive is already defined on Dry::Struct, so forward to the superclass if
|
46
|
+
# not called as a way to get a primitive type
|
47
|
+
def primitive(*args)
|
48
|
+
if args.length == 1
|
49
|
+
SoberSwag::Types.const_get(args.first)
|
50
|
+
else
|
51
|
+
super
|
52
|
+
end
|
34
53
|
end
|
35
54
|
|
36
55
|
def param(sym)
|
37
56
|
SoberSwag::Types::Params.const_get(sym)
|
38
57
|
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def valid_field_def?(parent, block)
|
62
|
+
return true if block.nil?
|
63
|
+
|
64
|
+
parent.is_a?(Class) && parent <= SoberSwag::InputObject
|
65
|
+
end
|
39
66
|
end
|
40
67
|
end
|
41
68
|
end
|
data/lib/sober_swag/version.rb
CHANGED
data/sober_swag.gemspec
CHANGED
@@ -39,7 +39,7 @@ Gem::Specification.new do |spec|
|
|
39
39
|
spec.add_development_dependency 'pry-byebug'
|
40
40
|
spec.add_development_dependency 'rake', '~> 13.0'
|
41
41
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
42
|
-
spec.add_development_dependency 'rubocop'
|
43
|
-
spec.add_development_dependency 'rubocop-rspec'
|
42
|
+
spec.add_development_dependency 'rubocop', '~> 0.93.1'
|
43
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.44.1'
|
44
44
|
spec.add_development_dependency 'simplecov'
|
45
45
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sober_swag
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.16.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anthony Super
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -126,30 +126,30 @@ dependencies:
|
|
126
126
|
name: rubocop
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- - "
|
129
|
+
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version:
|
131
|
+
version: 0.93.1
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
-
- - "
|
136
|
+
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version:
|
138
|
+
version: 0.93.1
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: rubocop-rspec
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
|
-
- - "
|
143
|
+
- - "~>"
|
144
144
|
- !ruby/object:Gem::Version
|
145
|
-
version:
|
145
|
+
version: 1.44.1
|
146
146
|
type: :development
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
|
-
- - "
|
150
|
+
- - "~>"
|
151
151
|
- !ruby/object:Gem::Version
|
152
|
-
version:
|
152
|
+
version: 1.44.1
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
154
|
name: simplecov
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -185,6 +185,7 @@ files:
|
|
185
185
|
- README.md
|
186
186
|
- Rakefile
|
187
187
|
- bin/console
|
188
|
+
- bin/rspec
|
188
189
|
- bin/setup
|
189
190
|
- docs/serializers.md
|
190
191
|
- example/.gitignore
|