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 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