sober_swag 0.1.0 → 0.2.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 +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
|
|