sober_swag 0.15.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|