sober_swag 0.1.0 → 0.6.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 +4 -0
- data/.github/workflows/lint.yml +15 -0
- data/.github/workflows/ruby.yml +33 -2
- data/.gitignore +4 -0
- data/.rubocop.yml +75 -1
- data/.ruby-version +1 -1
- data/README.md +154 -1
- data/bin/console +16 -15
- data/docs/serializers.md +203 -0
- data/example/.rspec +1 -0
- data/example/.ruby-version +1 -1
- data/example/Gemfile +9 -7
- data/example/Gemfile.lock +96 -79
- data/example/app/controllers/people_controller.rb +41 -23
- data/example/app/controllers/posts_controller.rb +110 -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 +86 -56
- 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 +5 -3
- 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 +2 -2
- data/lib/sober_swag/{blueprint → output_object}/view.rb +15 -6
- data/lib/sober_swag/parser.rb +9 -4
- 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 +19 -2
- data/lib/sober_swag/serializer/field_list.rb +29 -6
- data/lib/sober_swag/serializer/mapped.rb +15 -3
- 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 +8 -4
- metadata +79 -47
- data/Gemfile.lock +0 -92
- 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: { filters: { 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: { filters: { 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)
|
@@ -18,6 +22,8 @@ module SoberSwag
|
|
18
22
|
end
|
19
23
|
|
20
24
|
def primitive_def(value)
|
25
|
+
value = value.primitive if value.is_a?(Dry::Types::Nominal)
|
26
|
+
|
21
27
|
return nil unless value.is_a?(Class)
|
22
28
|
|
23
29
|
if (name = primitive_name(value))
|
@@ -42,6 +48,8 @@ module SoberSwag
|
|
42
48
|
class TooComplicatedForPathError < TooComplicatedError; end
|
43
49
|
class TooComplicatedForQueryError < TooComplicatedError; end
|
44
50
|
|
51
|
+
METADATA_KEYS = %i[description deprecated].freeze
|
52
|
+
|
45
53
|
def initialize(type)
|
46
54
|
@type = type
|
47
55
|
end
|
@@ -65,13 +73,16 @@ module SoberSwag
|
|
65
73
|
end
|
66
74
|
|
67
75
|
def path_schema
|
68
|
-
path_schema_stub.map
|
76
|
+
path_schema_stub.map do |e|
|
77
|
+
ensure_uncomplicated(e[:name], e[:schema])
|
78
|
+
e.merge(in: :path)
|
79
|
+
end
|
69
80
|
rescue TooComplicatedError => e
|
70
81
|
raise TooComplicatedForPathError, e.message
|
71
82
|
end
|
72
83
|
|
73
84
|
def query_schema
|
74
|
-
path_schema_stub.map { |e| e.merge(in: :query) }
|
85
|
+
path_schema_stub.map { |e| e.merge(in: :query, style: :deepObject, explode: true) }
|
75
86
|
rescue TooComplicatedError => e
|
76
87
|
raise TooComplicatedForQueryError, e.message
|
77
88
|
end
|
@@ -91,7 +102,7 @@ module SoberSwag
|
|
91
102
|
def parsed_type
|
92
103
|
@parsed_type ||=
|
93
104
|
begin
|
94
|
-
(parsed,
|
105
|
+
(parsed,) = parsed_result
|
95
106
|
parsed
|
96
107
|
end
|
97
108
|
end
|
@@ -110,7 +121,7 @@ module SoberSwag
|
|
110
121
|
|
111
122
|
private
|
112
123
|
|
113
|
-
def generate_schema_stub
|
124
|
+
def generate_schema_stub # rubocop:disable Metrics/MethodLength
|
114
125
|
return self.class.primitive_def(type) if self.class.primitive?(type)
|
115
126
|
|
116
127
|
case type
|
@@ -120,8 +131,10 @@ module SoberSwag
|
|
120
131
|
self.class.new(type.type).schema_stub
|
121
132
|
when Dry::Types::Array::Member
|
122
133
|
{ type: :array, items: self.class.new(type.member).schema_stub }
|
134
|
+
when Dry::Types::Sum
|
135
|
+
{ oneOf: normalize(parsed_type).elements.map { |t| self.class.new(t.value).schema_stub } }
|
123
136
|
else
|
124
|
-
raise
|
137
|
+
raise SoberSwag::Compiler::Error, "Cannot generate a schema stub for #{type} (#{type.class})"
|
125
138
|
end
|
126
139
|
end
|
127
140
|
|
@@ -138,16 +151,19 @@ module SoberSwag
|
|
138
151
|
object.cata { |e| rewrite_sums(e) }.cata { |e| flatten_one_ofs(e) }
|
139
152
|
end
|
140
153
|
|
141
|
-
def rewrite_sums(object)
|
154
|
+
def rewrite_sums(object) # rubocop:disable Metrics/MethodLength
|
142
155
|
case object
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
156
|
+
when Nodes::Sum
|
157
|
+
lhs, rhs = object.deconstruct
|
158
|
+
if lhs.is_a?(Nodes::OneOf) && rhs.is_a?(Nodes::OneOf)
|
159
|
+
Nodes::OneOf.new(lhs.deconstruct + rhs.deconstruct)
|
160
|
+
elsif lhs.is_a?(Nodes::OneOf)
|
161
|
+
Nodes::OneOf.new([*lhs.deconstruct, rhs])
|
162
|
+
elsif rhs.is_a?(Nodes::OneOf)
|
163
|
+
Nodes::OneOf.new([lhs, *rhs.deconstruct])
|
164
|
+
else
|
165
|
+
Nodes::OneOf.new([lhs, rhs])
|
166
|
+
end
|
151
167
|
else
|
152
168
|
object
|
153
169
|
end
|
@@ -155,56 +171,69 @@ module SoberSwag
|
|
155
171
|
|
156
172
|
def flatten_one_ofs(object)
|
157
173
|
case object
|
158
|
-
|
159
|
-
Nodes::OneOf.new(
|
174
|
+
when Nodes::OneOf
|
175
|
+
Nodes::OneOf.new(object.deconstruct.uniq)
|
160
176
|
else
|
161
177
|
object
|
162
178
|
end
|
163
179
|
end
|
164
180
|
|
165
|
-
def to_object_schema(object)
|
181
|
+
def to_object_schema(object) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
166
182
|
case object
|
167
|
-
|
183
|
+
when Nodes::List
|
168
184
|
{
|
169
185
|
type: :array,
|
170
|
-
items:
|
186
|
+
items: object.deconstruct.first
|
171
187
|
}
|
172
|
-
|
188
|
+
when Nodes::Enum
|
173
189
|
{
|
174
190
|
type: :string,
|
175
|
-
enum:
|
191
|
+
enum: object.deconstruct.first
|
176
192
|
}
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
193
|
+
when Nodes::OneOf
|
194
|
+
if object.deconstruct.include?({ type: 'null' })
|
195
|
+
rejected = object.deconstruct.reject { |e| e[:type] == 'null' }
|
196
|
+
if rejected.length == 1
|
197
|
+
rejected.first.merge(nullable: true)
|
198
|
+
else
|
199
|
+
{ oneOf: rejected, nullable: true }
|
200
|
+
end
|
201
|
+
else
|
202
|
+
{ oneOf: object.deconstruct }
|
203
|
+
end
|
204
|
+
when Nodes::Object
|
205
|
+
# openAPI requires that you give a list of required attributes
|
206
|
+
# (which IMO is the *totally* wrong thing to do but whatever)
|
207
|
+
# so we must do this garbage
|
208
|
+
required = object.deconstruct.filter { |(_, b)| b[:required] }.map(&:first)
|
209
|
+
{
|
210
|
+
type: :object,
|
211
|
+
properties: object.deconstruct.map { |(a, b)|
|
212
|
+
[a, b.reject { |k, _| k == :required }]
|
213
|
+
}.to_h,
|
214
|
+
required: required
|
215
|
+
}
|
216
|
+
when Nodes::Attribute
|
217
|
+
name, req, value = object.deconstruct
|
218
|
+
if req
|
219
|
+
[name, value.merge(required: true)]
|
220
|
+
else
|
221
|
+
[name, value]
|
222
|
+
end
|
201
223
|
# can't match on value directly as ruby uses `===` to match,
|
202
224
|
# and classes use `===` to mean `is an instance of`, as
|
203
225
|
# opposed to direct equality lmao
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
226
|
+
when Nodes::Primitive
|
227
|
+
value = object.value
|
228
|
+
metadata = object.metadata
|
229
|
+
if self.class.primitive?(value)
|
230
|
+
md = self.class.primitive_def(value)
|
231
|
+
METADATA_KEYS.select(&metadata.method(:key?)).reduce(md) do |definition, key|
|
232
|
+
definition.merge(key => metadata[key])
|
233
|
+
end
|
234
|
+
else
|
235
|
+
{ '$ref': self.class.get_ref(value) }
|
236
|
+
end
|
208
237
|
else
|
209
238
|
raise ArgumentError, "Got confusing node #{object} (#{object.class})"
|
210
239
|
end
|
@@ -213,23 +242,24 @@ module SoberSwag
|
|
213
242
|
def path_schema_stub
|
214
243
|
@path_schema_stub ||=
|
215
244
|
object_schema[:properties].map do |k, v|
|
216
|
-
ensure_uncomplicated(k, v)
|
245
|
+
# ensure_uncomplicated(k, v)
|
217
246
|
{
|
218
247
|
name: k,
|
219
|
-
schema: v.reject { |
|
220
|
-
|
221
|
-
required: object_schema[:required].include?(k) || false,
|
248
|
+
schema: v.reject { |key, _| %i[required nullable].include?(key) },
|
249
|
+
required: object_schema[:required].include?(k) || false
|
222
250
|
}
|
223
251
|
end
|
224
252
|
end
|
225
253
|
|
226
254
|
def ensure_uncomplicated(key, value)
|
227
255
|
return if value[:type]
|
256
|
+
|
257
|
+
return value[:oneOf].each { |member| ensure_uncomplicated(key, member) } if value[:oneOf]
|
258
|
+
|
228
259
|
raise TooComplicatedError, <<~ERROR
|
229
260
|
Property #{key} has object-schema #{value}, but this type of param should be simple (IE a primitive of some kind)
|
230
261
|
ERROR
|
231
262
|
end
|
232
|
-
|
233
263
|
end
|
234
264
|
end
|
235
265
|
end
|