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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.github/config/rubocop_linter_action.yml +4 -0
  3. data/.github/workflows/lint.yml +15 -0
  4. data/.github/workflows/ruby.yml +33 -2
  5. data/.gitignore +4 -0
  6. data/.rubocop.yml +75 -1
  7. data/.ruby-version +1 -1
  8. data/README.md +154 -1
  9. data/bin/console +16 -15
  10. data/docs/serializers.md +203 -0
  11. data/example/.rspec +1 -0
  12. data/example/.ruby-version +1 -1
  13. data/example/Gemfile +9 -7
  14. data/example/Gemfile.lock +96 -79
  15. data/example/app/controllers/people_controller.rb +41 -23
  16. data/example/app/controllers/posts_controller.rb +110 -0
  17. data/example/app/models/application_record.rb +3 -0
  18. data/example/app/models/person.rb +6 -0
  19. data/example/app/models/post.rb +9 -0
  20. data/example/app/output_objects/person_errors_output_object.rb +5 -0
  21. data/example/app/output_objects/person_output_object.rb +15 -0
  22. data/example/app/output_objects/post_output_object.rb +10 -0
  23. data/example/bin/bundle +24 -20
  24. data/example/bin/rails +1 -1
  25. data/example/bin/rake +1 -1
  26. data/example/config/application.rb +11 -7
  27. data/example/config/environments/development.rb +0 -1
  28. data/example/config/environments/production.rb +3 -3
  29. data/example/config/puma.rb +5 -5
  30. data/example/config/routes.rb +3 -0
  31. data/example/config/spring.rb +4 -4
  32. data/example/db/migrate/20200311152021_create_people.rb +0 -1
  33. data/example/db/migrate/20200603172347_create_posts.rb +11 -0
  34. data/example/db/schema.rb +16 -7
  35. data/example/spec/rails_helper.rb +64 -0
  36. data/example/spec/requests/people/create_spec.rb +52 -0
  37. data/example/spec/requests/people/get_spec.rb +35 -0
  38. data/example/spec/requests/people/index_spec.rb +69 -0
  39. data/example/spec/spec_helper.rb +94 -0
  40. data/lib/sober_swag.rb +6 -3
  41. data/lib/sober_swag/compiler/error.rb +2 -0
  42. data/lib/sober_swag/compiler/path.rb +2 -5
  43. data/lib/sober_swag/compiler/paths.rb +0 -1
  44. data/lib/sober_swag/compiler/type.rb +86 -56
  45. data/lib/sober_swag/controller.rb +16 -11
  46. data/lib/sober_swag/controller/route.rb +18 -21
  47. data/lib/sober_swag/controller/undefined_body_error.rb +3 -0
  48. data/lib/sober_swag/controller/undefined_path_error.rb +3 -0
  49. data/lib/sober_swag/controller/undefined_query_error.rb +3 -0
  50. data/lib/sober_swag/input_object.rb +28 -0
  51. data/lib/sober_swag/nodes/array.rb +1 -1
  52. data/lib/sober_swag/nodes/base.rb +5 -3
  53. data/lib/sober_swag/nodes/binary.rb +2 -1
  54. data/lib/sober_swag/nodes/enum.rb +4 -2
  55. data/lib/sober_swag/nodes/list.rb +0 -1
  56. data/lib/sober_swag/nodes/primitive.rb +6 -5
  57. data/lib/sober_swag/output_object.rb +102 -0
  58. data/lib/sober_swag/output_object/definition.rb +30 -0
  59. data/lib/sober_swag/{blueprint → output_object}/field.rb +14 -4
  60. data/lib/sober_swag/{blueprint → output_object}/field_syntax.rb +2 -2
  61. data/lib/sober_swag/{blueprint → output_object}/view.rb +15 -6
  62. data/lib/sober_swag/parser.rb +9 -4
  63. data/lib/sober_swag/serializer.rb +5 -2
  64. data/lib/sober_swag/serializer/array.rb +12 -0
  65. data/lib/sober_swag/serializer/base.rb +50 -1
  66. data/lib/sober_swag/serializer/conditional.rb +19 -2
  67. data/lib/sober_swag/serializer/field_list.rb +29 -6
  68. data/lib/sober_swag/serializer/mapped.rb +15 -3
  69. data/lib/sober_swag/serializer/meta.rb +35 -0
  70. data/lib/sober_swag/serializer/optional.rb +17 -2
  71. data/lib/sober_swag/serializer/primitive.rb +4 -1
  72. data/lib/sober_swag/server.rb +83 -0
  73. data/lib/sober_swag/types.rb +3 -0
  74. data/lib/sober_swag/version.rb +1 -1
  75. data/sober_swag.gemspec +8 -4
  76. metadata +79 -47
  77. data/Gemfile.lock +0 -92
  78. data/example/person.json +0 -4
  79. data/example/test/controllers/.keep +0 -0
  80. data/example/test/fixtures/.keep +0 -0
  81. data/example/test/fixtures/files/.keep +0 -0
  82. data/example/test/fixtures/people.yml +0 -11
  83. data/example/test/integration/.keep +0 -0
  84. data/example/test/models/.keep +0 -0
  85. data/example/test/models/person_test.rb +0 -7
  86. data/example/test/test_helper.rb +0 -13
  87. data/lib/sober_swag/blueprint.rb +0 -113
  88. data/lib/sober_swag/path.rb +0 -8
  89. data/lib/sober_swag/path/integer.rb +0 -21
  90. data/lib/sober_swag/path/lit.rb +0 -41
  91. data/lib/sober_swag/path/literal.rb +0 -29
  92. 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
@@ -15,15 +15,18 @@ module SoberSwag
15
15
 
16
16
  autoload :Parser, 'sober_swag/parser'
17
17
  autoload :Serializer, 'sober_swag/serializer'
18
- autoload :Blueprint, 'sober_swag/blueprint'
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
- def self.struct(parent = Dry::Struct, &block)
27
- Class.new(parent, &block)
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
@@ -1,5 +1,7 @@
1
1
  module SoberSwag
2
2
  class Compiler
3
+ ##
4
+ # Base class of compilation errors.
3
5
  class Error < ::SoberSwag::Error; end
4
6
  end
5
7
  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
@@ -48,7 +48,6 @@ module SoberSwag
48
48
  def compile_route(route, compiler)
49
49
  SoberSwag::Compiler::Path.new(route, compiler).schema
50
50
  end
51
-
52
51
  end
53
52
  end
54
53
  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.to_s.gsub('::', '.')
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 { |e| e.merge(in: :path) }
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, _) = parsed_result
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 ArgumentError, "Cannot generate a schema stub for #{type} (#{type.class})"
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
- in Nodes::Sum[Nodes::OneOf[*lhs], Nodes::OneOf[*rhs]]
144
- Nodes::OneOf.new(lhs + rhs)
145
- in Nodes::Sum[Nodes::OneOf[*args], rhs]
146
- Nodes::OneOf.new(args + [rhs])
147
- in Nodes::Sum[lhs, Nodes::OneOf[*args]]
148
- Nodes::OneOf.new([lhs] + args)
149
- in Nodes::Sum[lhs, rhs]
150
- Nodes::OneOf.new([lhs, rhs])
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
- in Nodes::OneOf[*args]
159
- Nodes::OneOf.new(args.uniq)
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
- in Nodes::List[element]
183
+ when Nodes::List
168
184
  {
169
185
  type: :array,
170
- items: element
186
+ items: object.deconstruct.first
171
187
  }
172
- in Nodes::Enum[values]
188
+ when Nodes::Enum
173
189
  {
174
190
  type: :string,
175
- enum: values
191
+ enum: object.deconstruct.first
176
192
  }
177
- in Nodes::OneOf[{ type: 'null' }, b]
178
- b.merge(nullable: true)
179
- in Nodes::OneOf[a, { type: 'null' }]
180
- a.merge(nullable: true)
181
- in Nodes::OneOf[*attrs] if attrs.include?(type: 'null')
182
- { oneOf: attrs.reject { |e| e[:type] == 'null' }, nullable: true }
183
- in Nodes::OneOf[*cases]
184
- { oneOf: cases }
185
- in Nodes::Object[*attrs]
186
- # openAPI requires that you give a list of required attributes
187
- # (which IMO is the *totally* wrong thing to do but whatever)
188
- # so we must do this garbage
189
- required = attrs.filter {|(_, b)| b[:required] }.map(&:first)
190
- {
191
- type: :object,
192
- properties: attrs.map { |(a,b)|
193
- [a, b.select { |k, _| k != :required }]
194
- }.to_h,
195
- required: required
196
- }
197
- in Nodes::Attribute[name, true, value]
198
- [name, value.merge(required: true)]
199
- in Nodes::Attribute[name, false, value]
200
- [name, value]
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
- in Nodes::Primitive[value:] if self.class.primitive?(value)
205
- self.class.primitive_def(value)
206
- in Nodes::Primitive[value:]
207
- { '$ref': self.class.get_ref(value) }
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 { |k, _| %i[required nullable].include?(k) },
220
- allowEmptyValue: !object_schema[:required].include?(k) || !!v[:nullable], # if it's required, no empties, but if *nullabe*, empties are okay
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