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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff24aa407a6b8360569931c050bee2958cb6e3169f34f57f1d455f0d573327af
4
- data.tar.gz: 61bf1feb701ae67e2a2fd85f41ab2d8ccd25d5e512a4cd26e3849f65e78a14e9
3
+ metadata.gz: ad86b8f4701d16ec53f0fc998bb2a881cdc448acfbb509bbfeb7821cc7142d1b
4
+ data.tar.gz: ce70ffa29ca02b8fc70e7f78c0b41bf56e8e3cdffbcf7282d8dad1cf16717b23
5
5
  SHA512:
6
- metadata.gz: 46c72426dcabb170bf8c5416e8583c254a9c627f8a6cf48424145dc688c9abcb2f7c6e61c87c55da0b8f66f9d076254cce3f815616c9f03eaa88aa14b19c72e4
7
- data.tar.gz: a13ac7c92fdaec126d93a3400e13f93287c993bd19c82caa76cc662112e82983a64de5233adfce1cd30a32bfdca6ecc794b5f06a15f7cc2ffda0bbd155b1d0a5
6
+ metadata.gz: '081ef6717877b1c40682fbcbf1436a7e44fd51307a03650edc9a6d4a594a99bc4e9f07adbb73a9073f784aa75dc6a5463f0aa1d77b77fefc976c0531273ced22'
7
+ data.tar.gz: c1fb5da8a19b9d6ee1324badbbff4caac4db49349efd301b9daaa74097372f2241e174617af5b59970b467610145419b6e392b579fc4a3457073439e5ec6824e
@@ -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: ${{ matrix.ruby }}
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: rubocop lib spec example
44
+ run: bundle exec rubocop -P
47
45
  - uses: actions/cache@v2
48
46
  with:
49
47
  path: example/vendor/bundle
data/.gitignore CHANGED
@@ -13,3 +13,5 @@
13
13
  Gemfile.lock
14
14
 
15
15
  *.gem
16
+
17
+ /vendor/
@@ -1,85 +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
+
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
@@ -1,7 +1,13 @@
1
1
  # Changelog
2
2
 
3
- ## V0.15.0: 2020-09-02
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
- 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,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 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).
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).
@@ -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')
@@ -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: :newstyle })
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
- serilaizer.new(my_record)
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, 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 a `OutputObject`.
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" sorta thing that will do type-checking and throw errors, however, the cost of doing so in production is probably not worth it.
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 string.
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 nils.
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, too.
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
- Consdier this case:
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, primtiive(:String)
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: use the `inherits:` kwarg:
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` will include fields `name`, `bio`, and `age`.
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.new(request.path_parameters)
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.new(body_params)
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.new(request.query_parameters)
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 in to.
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 in to
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('ResponseBody', @request_body_class)
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 defiend!' if @response_serializers.key?(status_key)
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
- Class.new(base, &block).tap do |e|
128
- e.transform_keys(&:to_sym) if [SoberSwag::InputObject, Dry::Struct].include?(base)
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
- def primitive(sym)
33
- SoberSwag::Types.const_get(sym)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SoberSwag
4
- VERSION = '0.15.0'
4
+ VERSION = '0.16.0'
5
5
  end
@@ -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.15.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-09-02 00:00:00.000000000 Z
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: '0'
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: '0'
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: '0'
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: '0'
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