sober_swag 0.12.0 → 0.17.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 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).