sober_swag 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/config/rubocop_linter_action.yml +5 -0
- data/.github/workflows/lint.yml +15 -0
- data/.github/workflows/ruby.yml +23 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +73 -1
- data/.ruby-version +1 -1
- data/Gemfile.lock +29 -5
- data/README.md +109 -0
- data/bin/console +15 -14
- data/docs/serializers.md +203 -0
- data/example/.rspec +1 -0
- data/example/.ruby-version +1 -1
- data/example/Gemfile +10 -6
- data/example/Gemfile.lock +96 -76
- data/example/app/controllers/people_controller.rb +37 -21
- data/example/app/controllers/posts_controller.rb +102 -0
- data/example/app/models/application_record.rb +3 -0
- data/example/app/models/person.rb +6 -0
- data/example/app/models/post.rb +9 -0
- data/example/app/output_objects/person_errors_output_object.rb +5 -0
- data/example/app/output_objects/person_output_object.rb +15 -0
- data/example/app/output_objects/post_output_object.rb +10 -0
- data/example/bin/bundle +24 -20
- data/example/bin/rails +1 -1
- data/example/bin/rake +1 -1
- data/example/config/application.rb +11 -7
- data/example/config/environments/development.rb +0 -1
- data/example/config/environments/production.rb +3 -3
- data/example/config/puma.rb +5 -5
- data/example/config/routes.rb +3 -0
- data/example/config/spring.rb +4 -4
- data/example/db/migrate/20200311152021_create_people.rb +0 -1
- data/example/db/migrate/20200603172347_create_posts.rb +11 -0
- data/example/db/schema.rb +16 -7
- data/example/spec/rails_helper.rb +64 -0
- data/example/spec/requests/people/create_spec.rb +52 -0
- data/example/spec/requests/people/get_spec.rb +35 -0
- data/example/spec/requests/people/index_spec.rb +69 -0
- data/example/spec/spec_helper.rb +94 -0
- data/lib/sober_swag.rb +6 -3
- data/lib/sober_swag/compiler/error.rb +2 -0
- data/lib/sober_swag/compiler/path.rb +2 -5
- data/lib/sober_swag/compiler/paths.rb +0 -1
- data/lib/sober_swag/compiler/type.rb +28 -15
- data/lib/sober_swag/controller.rb +16 -11
- data/lib/sober_swag/controller/route.rb +18 -21
- data/lib/sober_swag/controller/undefined_body_error.rb +3 -0
- data/lib/sober_swag/controller/undefined_path_error.rb +3 -0
- data/lib/sober_swag/controller/undefined_query_error.rb +3 -0
- data/lib/sober_swag/input_object.rb +28 -0
- data/lib/sober_swag/nodes/array.rb +1 -1
- data/lib/sober_swag/nodes/base.rb +2 -4
- data/lib/sober_swag/nodes/binary.rb +2 -1
- data/lib/sober_swag/nodes/enum.rb +4 -2
- data/lib/sober_swag/nodes/list.rb +0 -1
- data/lib/sober_swag/nodes/primitive.rb +6 -5
- data/lib/sober_swag/output_object.rb +102 -0
- data/lib/sober_swag/output_object/definition.rb +30 -0
- data/lib/sober_swag/{blueprint → output_object}/field.rb +14 -4
- data/lib/sober_swag/{blueprint → output_object}/field_syntax.rb +1 -1
- data/lib/sober_swag/{blueprint → output_object}/view.rb +15 -6
- data/lib/sober_swag/parser.rb +5 -3
- data/lib/sober_swag/serializer.rb +5 -2
- data/lib/sober_swag/serializer/array.rb +12 -0
- data/lib/sober_swag/serializer/base.rb +50 -1
- data/lib/sober_swag/serializer/conditional.rb +15 -2
- data/lib/sober_swag/serializer/field_list.rb +29 -6
- data/lib/sober_swag/serializer/mapped.rb +12 -2
- data/lib/sober_swag/serializer/meta.rb +35 -0
- data/lib/sober_swag/serializer/optional.rb +17 -2
- data/lib/sober_swag/serializer/primitive.rb +4 -1
- data/lib/sober_swag/server.rb +83 -0
- data/lib/sober_swag/types.rb +3 -0
- data/lib/sober_swag/version.rb +1 -1
- data/sober_swag.gemspec +6 -4
- metadata +77 -44
- data/example/person.json +0 -4
- data/example/test/controllers/.keep +0 -0
- data/example/test/fixtures/.keep +0 -0
- data/example/test/fixtures/files/.keep +0 -0
- data/example/test/fixtures/people.yml +0 -11
- data/example/test/integration/.keep +0 -0
- data/example/test/models/.keep +0 -0
- data/example/test/models/person_test.rb +0 -7
- data/example/test/test_helper.rb +0 -13
- data/lib/sober_swag/blueprint.rb +0 -113
- data/lib/sober_swag/path.rb +0 -8
- data/lib/sober_swag/path/integer.rb +0 -21
- data/lib/sober_swag/path/lit.rb +0 -41
- data/lib/sober_swag/path/literal.rb +0 -29
- data/lib/sober_swag/path/param.rb +0 -33
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'Getting a person' do
|
4
|
+
context 'with a good id' do
|
5
|
+
let!(:person) { Person.create(first_name: 'Anthony', last_name: 'Guy') }
|
6
|
+
let(:request) { get "/people/#{person.id}" }
|
7
|
+
|
8
|
+
describe 'the response' do
|
9
|
+
subject { request && response }
|
10
|
+
|
11
|
+
it { should be_successful }
|
12
|
+
it { should_not be_server_error }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'the response body' do
|
16
|
+
subject { request && response && JSON.parse(response.body) }
|
17
|
+
|
18
|
+
it { should include('first_name' => 'Anthony') }
|
19
|
+
it { should include('last_name' => 'Guy') }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'with a bad id' do
|
24
|
+
let(:request) { get '/people/my-awesome-guy' }
|
25
|
+
|
26
|
+
describe 'the response' do
|
27
|
+
subject { request && response }
|
28
|
+
|
29
|
+
it { should_not be_successful }
|
30
|
+
it { should_not be_server_error }
|
31
|
+
it { should have_http_status(:bad_request) }
|
32
|
+
it { should be_bad_request }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'Index action for people' do
|
4
|
+
let(:parsed_body) { request && response && JSON.parse(response.body) }
|
5
|
+
|
6
|
+
context 'with no people' do
|
7
|
+
let(:request) { get '/people' }
|
8
|
+
|
9
|
+
it 'is successful' do
|
10
|
+
request
|
11
|
+
expect(response).to be_successful
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'is has a blank body' do
|
15
|
+
expect(parsed_body).to be_blank
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'with a person' do
|
20
|
+
let!(:person) { Person.create(first_name: 'Anthony', last_name: 'Guy') }
|
21
|
+
|
22
|
+
shared_examples 'a request with the person' do
|
23
|
+
it 'is successful' do
|
24
|
+
request
|
25
|
+
expect(response).to be_successful
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'has people' do
|
29
|
+
expect(parsed_body).not_to be_blank
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'has the right person' do
|
33
|
+
expect(parsed_body).to include(include('id' => person.id))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with a good first-name search' do
|
38
|
+
let(:request) { get '/people', params: { first_name: 'A' } }
|
39
|
+
|
40
|
+
it_behaves_like 'a request with the person'
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'with a good last-name search' do
|
44
|
+
let(:request) { get '/people', params: { last_name: 'G' } }
|
45
|
+
|
46
|
+
it_behaves_like 'a request with the person'
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'with a valid view' do
|
50
|
+
let(:request) { get '/people', params: { view: 'detail' } }
|
51
|
+
|
52
|
+
it_behaves_like 'a request with the person'
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'with an invalid view' do
|
56
|
+
let(:request) { get '/people', params: { view: 'not-a-thing-lol' } }
|
57
|
+
|
58
|
+
it 'is not successful' do
|
59
|
+
request
|
60
|
+
expect(response).not_to be_successful
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'is a bad request' do
|
64
|
+
request
|
65
|
+
expect(response).to be_bad_request
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# This file was generated by the `rails generate rspec:install` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
4
|
+
# this file to always be loaded, without a need to explicitly require it in any
|
5
|
+
# files.
|
6
|
+
#
|
7
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
8
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
9
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
10
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
11
|
+
# a separate helper file that requires the additional dependencies and performs
|
12
|
+
# the additional setup, and require it from the spec files that actually need
|
13
|
+
# it.
|
14
|
+
#
|
15
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
16
|
+
RSpec.configure do |config|
|
17
|
+
# rspec-expectations config goes here. You can use an alternate
|
18
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
19
|
+
# assertions if you prefer.
|
20
|
+
config.expect_with :rspec do |expectations|
|
21
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
22
|
+
# and `failure_message` of custom matchers include text for helper methods
|
23
|
+
# defined using `chain`, e.g.:
|
24
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
25
|
+
# # => "be bigger than 2 and smaller than 4"
|
26
|
+
# ...rather than:
|
27
|
+
# # => "be bigger than 2"
|
28
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
29
|
+
end
|
30
|
+
|
31
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
32
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
33
|
+
config.mock_with :rspec do |mocks|
|
34
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
35
|
+
# a real object. This is generally recommended, and will default to
|
36
|
+
# `true` in RSpec 4.
|
37
|
+
mocks.verify_partial_doubles = true
|
38
|
+
end
|
39
|
+
|
40
|
+
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
41
|
+
# have no way to turn it off -- the option exists only for backwards
|
42
|
+
# compatibility in RSpec 3). It causes shared context metadata to be
|
43
|
+
# inherited by the metadata hash of host groups and examples, rather than
|
44
|
+
# triggering implicit auto-inclusion in groups with matching metadata.
|
45
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
46
|
+
|
47
|
+
# The settings below are suggested to provide a good initial experience
|
48
|
+
# with RSpec, but feel free to customize to your heart's content.
|
49
|
+
# # This allows you to limit a spec run to individual examples or groups
|
50
|
+
# # you care about by tagging them with `:focus` metadata. When nothing
|
51
|
+
# # is tagged with `:focus`, all examples get run. RSpec also provides
|
52
|
+
# # aliases for `it`, `describe`, and `context` that include `:focus`
|
53
|
+
# # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
54
|
+
# config.filter_run_when_matching :focus
|
55
|
+
#
|
56
|
+
# # Allows RSpec to persist some state between runs in order to support
|
57
|
+
# # the `--only-failures` and `--next-failure` CLI options. We recommend
|
58
|
+
# # you configure your source control system to ignore this file.
|
59
|
+
# config.example_status_persistence_file_path = "spec/examples.txt"
|
60
|
+
#
|
61
|
+
# # Limits the available syntax to the non-monkey patched syntax that is
|
62
|
+
# # recommended. For more details, see:
|
63
|
+
# # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
64
|
+
# # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
65
|
+
# # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
66
|
+
# config.disable_monkey_patching!
|
67
|
+
#
|
68
|
+
# # Many RSpec users commonly either run the entire suite or an individual
|
69
|
+
# # file, and it's useful to allow more verbose output when running an
|
70
|
+
# # individual spec file.
|
71
|
+
# if config.files_to_run.one?
|
72
|
+
# # Use the documentation formatter for detailed output,
|
73
|
+
# # unless a formatter has already been configured
|
74
|
+
# # (e.g. via a command-line flag).
|
75
|
+
# config.default_formatter = "doc"
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# # Print the 10 slowest examples and example groups at the
|
79
|
+
# # end of the spec run, to help surface which specs are running
|
80
|
+
# # particularly slow.
|
81
|
+
# config.profile_examples = 10
|
82
|
+
#
|
83
|
+
# # Run specs in random order to surface order dependencies. If you find an
|
84
|
+
# # order dependency and want to debug it, you can fix the order by providing
|
85
|
+
# # the seed, which is printed after each run.
|
86
|
+
# # --seed 1234
|
87
|
+
# config.order = :random
|
88
|
+
#
|
89
|
+
# # Seed global randomization in this process using the `--seed` CLI option.
|
90
|
+
# # Setting this allows you to use `--seed` to deterministically reproduce
|
91
|
+
# # test failures related to randomization by passing the same `--seed` value
|
92
|
+
# # as the one that triggered the failure.
|
93
|
+
# Kernel.srand config.seed
|
94
|
+
end
|
data/lib/sober_swag.rb
CHANGED
@@ -15,15 +15,18 @@ module SoberSwag
|
|
15
15
|
|
16
16
|
autoload :Parser, 'sober_swag/parser'
|
17
17
|
autoload :Serializer, 'sober_swag/serializer'
|
18
|
-
autoload :
|
18
|
+
autoload :OutputObject, 'sober_swag/output_object'
|
19
19
|
autoload :Nodes, 'sober_swag/nodes'
|
20
20
|
autoload :Compiler, 'sober_swag/compiler'
|
21
21
|
autoload :Controller, 'sober_swag/controller'
|
22
|
+
autoload :InputObject, 'sober_swag/input_object'
|
23
|
+
autoload :Server, 'sober_swag/server'
|
22
24
|
|
23
25
|
##
|
24
26
|
# Define a struct of something.
|
25
27
|
# Useful to prevent weirdness from autoloading.
|
26
|
-
|
27
|
-
|
28
|
+
# @param parent [Class] the base class for the struct (default of {SoberSwag::Struct})
|
29
|
+
def self.input_object(parent = nil, &block)
|
30
|
+
Class.new(parent || SoberSwag::InputObject, &block)
|
28
31
|
end
|
29
32
|
end
|
@@ -24,7 +24,7 @@ module SoberSwag
|
|
24
24
|
base
|
25
25
|
end
|
26
26
|
|
27
|
-
def responses
|
27
|
+
def responses # rubocop:disable Metrics/MethodLength
|
28
28
|
route.response_serializers.map { |status, serializer|
|
29
29
|
[
|
30
30
|
status.to_s,
|
@@ -32,9 +32,7 @@ module SoberSwag
|
|
32
32
|
description: route.response_descriptions[status],
|
33
33
|
content: {
|
34
34
|
'application/json': {
|
35
|
-
schema: compiler.response_for(
|
36
|
-
serializer.respond_to?(:new) ? serializer.new.type : serializer.type
|
37
|
-
)
|
35
|
+
schema: compiler.response_for(serializer.type)
|
38
36
|
}
|
39
37
|
}
|
40
38
|
}
|
@@ -74,7 +72,6 @@ module SoberSwag
|
|
74
72
|
}
|
75
73
|
}
|
76
74
|
end
|
77
|
-
|
78
75
|
end
|
79
76
|
end
|
80
77
|
end
|
@@ -3,14 +3,18 @@ module SoberSwag
|
|
3
3
|
##
|
4
4
|
# A compiler for DRY-Struct data types, essentially.
|
5
5
|
# It only consumes one type at a time.
|
6
|
-
class Type
|
6
|
+
class Type # rubocop:disable Metrics/ClassLength
|
7
7
|
class << self
|
8
8
|
def get_ref(klass)
|
9
9
|
"#/components/schemas/#{safe_name(klass)}"
|
10
10
|
end
|
11
11
|
|
12
12
|
def safe_name(klass)
|
13
|
-
klass.
|
13
|
+
if klass.respond_to?(:identifier)
|
14
|
+
klass.identifier
|
15
|
+
else
|
16
|
+
klass.to_s.gsub('::', '.')
|
17
|
+
end
|
14
18
|
end
|
15
19
|
|
16
20
|
def primitive?(value)
|
@@ -42,6 +46,8 @@ module SoberSwag
|
|
42
46
|
class TooComplicatedForPathError < TooComplicatedError; end
|
43
47
|
class TooComplicatedForQueryError < TooComplicatedError; end
|
44
48
|
|
49
|
+
METADATA_KEYS = %i[description deprecated].freeze
|
50
|
+
|
45
51
|
def initialize(type)
|
46
52
|
@type = type
|
47
53
|
end
|
@@ -91,7 +97,7 @@ module SoberSwag
|
|
91
97
|
def parsed_type
|
92
98
|
@parsed_type ||=
|
93
99
|
begin
|
94
|
-
(parsed,
|
100
|
+
(parsed,) = parsed_result
|
95
101
|
parsed
|
96
102
|
end
|
97
103
|
end
|
@@ -110,7 +116,7 @@ module SoberSwag
|
|
110
116
|
|
111
117
|
private
|
112
118
|
|
113
|
-
def generate_schema_stub
|
119
|
+
def generate_schema_stub # rubocop:disable Metrics/MethodLength
|
114
120
|
return self.class.primitive_def(type) if self.class.primitive?(type)
|
115
121
|
|
116
122
|
case type
|
@@ -120,6 +126,8 @@ module SoberSwag
|
|
120
126
|
self.class.new(type.type).schema_stub
|
121
127
|
when Dry::Types::Array::Member
|
122
128
|
{ type: :array, items: self.class.new(type.member).schema_stub }
|
129
|
+
when Dry::Types::Sum
|
130
|
+
{ oneOf: normalize(parsed_type).elements.map { |t| self.class.new(t.value).schema_stub } }
|
123
131
|
else
|
124
132
|
raise ArgumentError, "Cannot generate a schema stub for #{type} (#{type.class})"
|
125
133
|
end
|
@@ -138,7 +146,7 @@ module SoberSwag
|
|
138
146
|
object.cata { |e| rewrite_sums(e) }.cata { |e| flatten_one_ofs(e) }
|
139
147
|
end
|
140
148
|
|
141
|
-
def rewrite_sums(object)
|
149
|
+
def rewrite_sums(object) # rubocop:disable Metrics/MethodLength
|
142
150
|
case object
|
143
151
|
in Nodes::Sum[Nodes::OneOf[*lhs], Nodes::OneOf[*rhs]]
|
144
152
|
Nodes::OneOf.new(lhs + rhs)
|
@@ -157,12 +165,12 @@ module SoberSwag
|
|
157
165
|
case object
|
158
166
|
in Nodes::OneOf[*args]
|
159
167
|
Nodes::OneOf.new(args.uniq)
|
160
|
-
|
168
|
+
else
|
161
169
|
object
|
162
170
|
end
|
163
171
|
end
|
164
172
|
|
165
|
-
def to_object_schema(object)
|
173
|
+
def to_object_schema(object) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
166
174
|
case object
|
167
175
|
in Nodes::List[element]
|
168
176
|
{
|
@@ -186,11 +194,11 @@ module SoberSwag
|
|
186
194
|
# openAPI requires that you give a list of required attributes
|
187
195
|
# (which IMO is the *totally* wrong thing to do but whatever)
|
188
196
|
# so we must do this garbage
|
189
|
-
required = attrs.filter {|(_, b)| b[:required] }.map(&:first)
|
197
|
+
required = attrs.filter { |(_, b)| b[:required] }.map(&:first)
|
190
198
|
{
|
191
199
|
type: :object,
|
192
|
-
properties: attrs.map { |(a,b)|
|
193
|
-
[a, b.
|
200
|
+
properties: attrs.map { |(a, b)|
|
201
|
+
[a, b.reject { |k, _| k == :required }]
|
194
202
|
}.to_h,
|
195
203
|
required: required
|
196
204
|
}
|
@@ -201,8 +209,11 @@ module SoberSwag
|
|
201
209
|
# can't match on value directly as ruby uses `===` to match,
|
202
210
|
# and classes use `===` to mean `is an instance of`, as
|
203
211
|
# opposed to direct equality lmao
|
204
|
-
in Nodes::Primitive[value:] if self.class.primitive?(value)
|
205
|
-
self.class.primitive_def(value)
|
212
|
+
in Nodes::Primitive[value:, metadata:] if self.class.primitive?(value)
|
213
|
+
md = self.class.primitive_def(value)
|
214
|
+
METADATA_KEYS.select(&metadata.method(:key?)).reduce(md) do |definition, key|
|
215
|
+
definition.merge(key => metadata[key])
|
216
|
+
end
|
206
217
|
in Nodes::Primitive[value:]
|
207
218
|
{ '$ref': self.class.get_ref(value) }
|
208
219
|
else
|
@@ -216,20 +227,22 @@ module SoberSwag
|
|
216
227
|
ensure_uncomplicated(k, v)
|
217
228
|
{
|
218
229
|
name: k,
|
219
|
-
schema: v.reject { |
|
230
|
+
schema: v.reject { |key, _| %i[required nullable].include?(key) },
|
231
|
+
# rubocop:disable Style/DoubleNegation
|
220
232
|
allowEmptyValue: !object_schema[:required].include?(k) || !!v[:nullable], # if it's required, no empties, but if *nullabe*, empties are okay
|
221
|
-
|
233
|
+
# rubocop:enable Style/DoubleNegation
|
234
|
+
required: object_schema[:required].include?(k) || false
|
222
235
|
}
|
223
236
|
end
|
224
237
|
end
|
225
238
|
|
226
239
|
def ensure_uncomplicated(key, value)
|
227
240
|
return if value[:type]
|
241
|
+
|
228
242
|
raise TooComplicatedError, <<~ERROR
|
229
243
|
Property #{key} has object-schema #{value}, but this type of param should be simple (IE a primitive of some kind)
|
230
244
|
ERROR
|
231
245
|
end
|
232
|
-
|
233
246
|
end
|
234
247
|
end
|
235
248
|
end
|
@@ -10,10 +10,19 @@ module SoberSwag
|
|
10
10
|
autoload :UndefinedPathError, 'sober_swag/controller/undefined_path_error'
|
11
11
|
autoload :UndefinedQueryError, 'sober_swag/controller/undefined_query_error'
|
12
12
|
|
13
|
+
##
|
14
|
+
# Types module, so you can more easily access Types::Whatever
|
15
|
+
# without having to type SoberSwag::Types::Whatever.
|
13
16
|
module Types
|
14
17
|
include ::Dry::Types()
|
15
18
|
end
|
16
19
|
|
20
|
+
included do
|
21
|
+
rescue_from Dry::Struct::Error do
|
22
|
+
head :bad_request
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
17
26
|
class_methods do
|
18
27
|
##
|
19
28
|
# Define a new action with the given HTTP method, action name, and path.
|
@@ -65,10 +74,7 @@ module SoberSwag
|
|
65
74
|
res = defined_routes.reduce(SoberSwag::Compiler.new) { |c, r| c.add_route(r) }
|
66
75
|
{
|
67
76
|
openapi: '3.0.0',
|
68
|
-
info: {
|
69
|
-
version: '1',
|
70
|
-
title: self.name
|
71
|
-
}
|
77
|
+
info: { version: '1', title: name }
|
72
78
|
}.merge(res.to_swagger)
|
73
79
|
end
|
74
80
|
end
|
@@ -89,6 +95,7 @@ module SoberSwag
|
|
89
95
|
begin
|
90
96
|
r = current_action_def
|
91
97
|
raise UndefinedPathError unless r&.path_params_class
|
98
|
+
|
92
99
|
r.path_params_class.new(request.path_parameters)
|
93
100
|
end
|
94
101
|
end
|
@@ -102,6 +109,7 @@ module SoberSwag
|
|
102
109
|
begin
|
103
110
|
r = current_action_def
|
104
111
|
raise UndefinedBodyError unless r&.request_body_class
|
112
|
+
|
105
113
|
r.request_body_class.new(body_params)
|
106
114
|
end
|
107
115
|
end
|
@@ -115,6 +123,7 @@ module SoberSwag
|
|
115
123
|
begin
|
116
124
|
r = current_action_def
|
117
125
|
raise UndefinedQueryError unless r&.query_params_class
|
126
|
+
|
118
127
|
r.query_params_class.new(request.query_parameters)
|
119
128
|
end
|
120
129
|
end
|
@@ -124,11 +133,11 @@ module SoberSwag
|
|
124
133
|
# @todo figure out how to specify views and other options for the serializer here
|
125
134
|
# @param status [Symbol] the HTTP status symbol to use for the status code
|
126
135
|
# @param entity the thing to serialize
|
127
|
-
def respond!(status, entity)
|
136
|
+
def respond!(status, entity, serializer_opts: {}, rails_opts: {})
|
128
137
|
r = current_action_def
|
129
138
|
serializer = r.response_serializers[Rack::Utils.status_code(status)]
|
130
139
|
serializer ||= serializer.new if serializer.respond_to?(:new)
|
131
|
-
render json: serializer.serialize(entity)
|
140
|
+
render json: serializer.serialize(entity, serializer_opts), status: status, **rails_opts
|
132
141
|
end
|
133
142
|
|
134
143
|
##
|
@@ -138,10 +147,7 @@ module SoberSwag
|
|
138
147
|
# but it keeps the docs honest: parameters sent in the body *must* be
|
139
148
|
# in the body.
|
140
149
|
def body_params
|
141
|
-
|
142
|
-
request.query_parameters.key?(k) || request.path_parameters.key?(k)
|
143
|
-
end
|
144
|
-
bparams.permit(bparams.keys)
|
150
|
+
request.request_parameters
|
145
151
|
end
|
146
152
|
|
147
153
|
##
|
@@ -150,7 +156,6 @@ module SoberSwag
|
|
150
156
|
def current_action_def
|
151
157
|
self.class.find_route(params[:action])
|
152
158
|
end
|
153
|
-
|
154
159
|
end
|
155
160
|
end
|
156
161
|
|