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