sober_swag 0.12.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa0e0e510ec9344738f12f01098f1d34557ac41c83dc966b4368b1407496b868
4
- data.tar.gz: 50f62bed3de241407e3d16caaae632dc8af21781477ac7e212a60af71780c932
3
+ metadata.gz: 7a0e2ee1acc571a7f6a90db87f4c15b6d7d820f24b75abd3f37cb369d431f271
4
+ data.tar.gz: 6384f275b7b1bd21ef35f4972b519be59a19d1a5e24a5c2138e203748a9702bb
5
5
  SHA512:
6
- metadata.gz: 04ad84a03ee992677593aeea1342ddbb1fb693d7f67ae6c9e3b31b8d5050aeda86d71f65390d906ade4d30e458cdb7717d3ef02d93f3e966a92e4fac63240e8e
7
- data.tar.gz: 535711fae0cc27c5122f5873cf64901e571631d9d87ecc04d44420deb99ff173bf83c0b36cdd34ce44aa6a697cec643098a8784be2487c2f9619ea78b52b2754
6
+ metadata.gz: c1d8b82b62dc05656482b0e550bc5edb24cdf76dde9dc9bcd4729381c5439983df7e98704eb9a0d54964f70297c8097b8192a016736db5ffff3126849d5425e9
7
+ data.tar.gz: f55485cc2dd04c4dd414b7c2b5668be91d1c8ada7ad51cf20d04bad62925054d27f940caf83c6c83e1763928c3ea5295d9c592b7a83f85e133cdfbaca697652c
@@ -1,15 +1,47 @@
1
- name: Linters
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
2
7
 
3
- on: [push]
8
+ name: Ruby Lint
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
4
15
 
5
16
  jobs:
6
- build:
17
+ test:
18
+
7
19
  runs-on: ubuntu-latest
20
+ strategy:
21
+ matrix:
22
+ ruby: [ '2.6', '2.7' ]
23
+
8
24
  steps:
9
- - uses: actions/checkout@v1
10
- - name: Rubocop Linter
11
- uses: andrewmcodes/rubocop-linter-action@v3.0.0
25
+ - uses: actions/checkout@v2
26
+ - name: Set up Ruby
27
+ uses: ruby/setup-ruby@v1
28
+ with:
29
+ ruby-version: ${{ matrix.ruby }}
30
+ - uses: actions/cache@v2
31
+ with:
32
+ path: vendor/bundle
33
+ key: ${{ runner.os }}-${{ matrix.ruby }}-gem-deps-${{ hashFiles('**/Gemfile.lock') }}
34
+ restore-keys: |
35
+ ${{ runner.os }}-${{ matrix.ruby }}-gem-deps-
36
+ - name: Install dependencies
37
+ run: |
38
+ bundle config path vendor/bundle
39
+ bundle install
40
+ - name: Run Lints
41
+ run: bundle exec rubocop -P
42
+ - uses: actions/cache@v2
12
43
  with:
13
- action_config_path: '.github/config/rubocop_linter_action.yml' # Note: this is the default location
14
- env:
15
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44
+ path: example/vendor/bundle
45
+ key: ${{ runner.os }}-${{ matrix.ruby }}-example-deps-${{ hashFiles('example/**/Gemfile.lock') }}
46
+ restore-keys: |
47
+ ${{ runner.os }}-${{ matrix.ruby }}-example-deps-
@@ -20,14 +20,10 @@ jobs:
20
20
  strategy:
21
21
  matrix:
22
22
  ruby: [ '2.6', '2.7' ]
23
-
24
23
  steps:
25
24
  - uses: actions/checkout@v2
26
25
  - name: Set up Ruby
27
- # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
28
- # change this to (see https://github.com/ruby/setup-ruby#versioning):
29
- # uses: ruby/setup-ruby@v1
30
- uses: ruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0
26
+ uses: ruby/setup-ruby@v1
31
27
  with:
32
28
  ruby-version: ${{ matrix.ruby }}
33
29
  - uses: actions/cache@v2
data/.gitignore CHANGED
@@ -13,3 +13,5 @@
13
13
  Gemfile.lock
14
14
 
15
15
  *.gem
16
+
17
+ /vendor/
@@ -1,81 +1,126 @@
1
- Style/FrozenStringLiteralComment:
2
- Enabled: false
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
- require: rubocop-rspec
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
+
31
+ RSpec/LeadingSubject:
32
+ Enabled: false
33
+
27
34
  Style/MultilineBlockChain:
28
35
  Enabled: false
36
+
29
37
  Metrics/AbcSize:
30
38
  Enabled: false
39
+
31
40
  Style/Documentation:
32
41
  Exclude:
33
42
  - 'example/db/migrate/**/*'
43
+
34
44
  Metrics/PerceivedComplexity:
35
45
  Enabled: false
46
+
36
47
  Layout/EmptyLinesAroundAttributeAccessor:
37
48
  Enabled: true
49
+
38
50
  Layout/SpaceAroundMethodCallOperator:
39
51
  Enabled: true
52
+
40
53
  Lint/DeprecatedOpenSSLConstant:
41
54
  Enabled: true
55
+
42
56
  Lint/DuplicateElsifCondition:
43
57
  Enabled: true
58
+
44
59
  Lint/MixedRegexpCaptureTypes:
45
60
  Enabled: true
61
+
46
62
  Lint/RaiseException:
47
63
  Enabled: true
64
+
48
65
  Lint/StructNewOverride:
49
66
  Enabled: true
67
+
50
68
  Style/AccessorGrouping:
51
69
  Enabled: true
70
+
52
71
  Style/ArrayCoercion:
53
72
  Enabled: true
73
+
54
74
  Style/BisectedAttrAccessor:
55
75
  Enabled: true
76
+
56
77
  Style/CaseLikeIf:
57
78
  Enabled: true
79
+
58
80
  Style/ExponentialNotation:
59
81
  Enabled: true
82
+
60
83
  Style/HashAsLastArrayItem:
61
84
  Enabled: true
85
+
62
86
  Style/HashEachMethods:
63
87
  Enabled: true
88
+
64
89
  Style/HashLikeCase:
65
90
  Enabled: true
91
+
66
92
  Style/HashTransformKeys:
67
93
  Enabled: true
94
+
68
95
  Style/HashTransformValues:
69
96
  Enabled: true
97
+
70
98
  Style/RedundantAssignment:
71
99
  Enabled: true
100
+
72
101
  Style/RedundantFetchBlock:
73
102
  Enabled: true
103
+
74
104
  Style/RedundantFileExtensionInRequire:
75
105
  Enabled: true
106
+
76
107
  Style/RedundantRegexpCharacterClass:
77
108
  Enabled: true
109
+
78
110
  Style/RedundantRegexpEscape:
79
111
  Enabled: true
112
+
80
113
  Style/SlicingWithRange:
81
114
  Enabled: true
115
+
116
+ RSpec/NestedGroups:
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
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ ## [v0.17.0]: 2020-11-30
4
+
5
+ - Allow tagging endpoints via the new `tags` method.
6
+
7
+ ## [v0.16.0]: 2020-10-23
8
+
9
+ - Allow non-class types to be used as the inputs to controllers
10
+
11
+ ## [v0.15.0]: 2020-09-02
12
+
13
+ ### Added
14
+ - Add a new `#merge` method to output objects, which will merge fields from another output object into the given output object.
15
+ - Add `multi` to Output Objects, as a way to define more than one field of the same type at once.
16
+ - Add an `inherits:` key to output objects, for view inheritance.
17
+ - Add `SoberSwag::Types::CommaArray`, which parses comma-separated strings into arrays.
18
+ This also sets `style` to `form` and `explode` to `false` when generating Swagger docs.
19
+ This class is mostly useful for query parameters where you want a simpler format: `tag=foo,bar` instead of `tag[]=foo,tag[]=bar`.
20
+ - Add support for using `meta` to specify alternative `style` and `explode` keys for query and path params.
21
+ 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.
22
+
23
+ ### Fixed
24
+ - No longer swallow `Dry::Struct` errors, instead let them surface to the user.
25
+
26
+ [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
- We can now use this information to generate swagger documentation, available at the `swagger` action on this controller.
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 null
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,44 @@ QueryInput = SoberSwag.input_object do
151
268
  end
152
269
  ```
153
270
 
271
+ ## Tags
272
+
273
+ If you want to organize your API into sections, you can use `tags`.
274
+ It's quite simple:
275
+
276
+ ```ruby
277
+ define :patch, :update, '/people/{id}' do
278
+ # other cool config
279
+ tags 'people', 'mutations', 'incurs_cost'
280
+ end
281
+ ```
282
+
283
+ This will map to OpenAPI's `tags` field (naturally), and the UI codegen will automatically organize your endpoints by their tags.
284
+
285
+ ## Testing the validity of output objects
286
+
287
+ If you're using RSpec and want to test the validity of output objects, you can do so relatively easily.
288
+
289
+ 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:
290
+
291
+ ```ruby
292
+ RSpec.describe UserOutputObject do
293
+ describe 'serialized result' do
294
+ subject do
295
+ described_class.type.new(described_class.serialize(create(:user)))
296
+ end
297
+
298
+ it 'works with an object' do
299
+ expect { subject }.not_to raise_error
300
+ end
301
+ end
302
+ end
303
+ ```
304
+
154
305
  ## Special Thanks
155
306
 
156
307
  This gem is a mishmash of ideas from various sources.
157
308
  The biggest thanks is owed to the [dry-rb](https://github.com/dry-rb) project, upon which the typing of SoberSwag is based.
158
309
  On an API design level, much is owed to [blueprinter](https://github.com/procore/blueprinter) for the serializers.
159
310
  The idea of a strongly-typed API came from the Haskell framework [servant](https://www.servant.dev/).
160
- Generating the swagger documenation 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).
311
+ 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).