diff --git a/lib/json/schema_builder/entity.rb b/lib/json/schema_builder/entity.rb index b8ac043..939db4b 100644 --- a/lib/json/schema_builder/entity.rb +++ b/lib/json/schema_builder/entity.rb @@ -43,6 +43,7 @@ def initialize(name, opts = { }, &block) initialize_parent_with opts initialize_with opts eval_block &block + any_of(null) if @nullable extract_types @initialized = true end @@ -138,13 +139,22 @@ def _reset_fragments end def extract_types - any_of(null) if @nullable - if any_of.present? - everything_else = schema.data.reject { |k, v| k == "anyOf" } - return unless everything_else.present? - schema.data.select! { |k, v| k == "anyOf" } - schema.data["anyOf"].unshift everything_else - end + build_any_of if any_of.present? + end + + def build_any_of + initial_object = any_of_options.find { |opt| opt.as_json['type'] == 'object' } + everything_else = schema.data.except("anyOf") + return unless everything_else.present? + + schema.data.keep_if { |k| k == "anyOf" } + return any_of_options.unshift(everything_else) unless initial_object + initial_object.deep_merge! everything_else + initial_object['properties'] = children.select { |c| c.name.presence }.map { |c| [c.name, c] }.to_h + end + + def any_of_options + schema.data["anyOf"] end def initialize_parent_with(opts) diff --git a/lib/json/schema_builder/object.rb b/lib/json/schema_builder/object.rb index b9801f4..50607ae 100644 --- a/lib/json/schema_builder/object.rb +++ b/lib/json/schema_builder/object.rb @@ -23,6 +23,7 @@ def initialize_children self.properties[child.name] = child.as_json end end + build_any_of if @nullable end def extract_types diff --git a/spec/integration/builder_initialization_spec.rb b/spec/integration/builder_initialization_spec.rb index 774576b..dfb6ba6 100644 --- a/spec/integration/builder_initialization_spec.rb +++ b/spec/integration/builder_initialization_spec.rb @@ -17,6 +17,21 @@ } } }, + target: { + anyOf: [ + { + type: :object, + properties: { + id: { + type: :number + } + } + }, + { + type: :null + } + ] + }, preferences: { anyOf: [ { diff --git a/spec/integration/builder_reopening_spec.rb b/spec/integration/builder_reopening_spec.rb index f19653d..17c3d31 100644 --- a/spec/integration/builder_reopening_spec.rb +++ b/spec/integration/builder_reopening_spec.rb @@ -20,6 +20,21 @@ } } }, + target: { + anyOf: [ + { + type: :object, + properties: { + id: { + type: :number + } + } + }, + { + type: :null + } + ] + }, preferences: { anyOf: [ { diff --git a/spec/integration/expand_null_spec.rb b/spec/integration/expand_null_spec.rb new file mode 100644 index 0000000..c58383d --- /dev/null +++ b/spec/integration/expand_null_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +RSpec.describe Examples::ExpandNullTrue, type: :integration do + it_behaves_like 'a builder' do + let(:expected_json) do + { + "type": "object", + "required": [ + "foo" + ], + "properties": { + "foo": { + "type": "object", + "required": [ + "bar" + ], + "properties": { + "bar": { + "anyOf": [ + { + "type": "object", + "properties": { + "baz": { + "type": "string" + } + } + }, + { + "type": "null" + } + ] + } + } + } + } + } + end + end +end diff --git a/spec/integration/nested_nullable_spec.rb b/spec/integration/nested_nullable_spec.rb new file mode 100644 index 0000000..39daf15 --- /dev/null +++ b/spec/integration/nested_nullable_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +RSpec.describe Examples::NestedNullable, type: :integration do + it_behaves_like 'a builder' do + let(:expected_json) do + { + type: 'object', + properties: { + nullable_object: { + anyOf: [ + { + type: 'object', + properties: { + nullable_string: { + anyOf: [ + { + type: 'string' + }, + { + type: 'null' + } + ] + } + } + }, + { + type: 'null' + } + ] + } + } + } + end + end +end diff --git a/spec/support/examples/builder_initialization.rb b/spec/support/examples/builder_initialization.rb index 5173a8e..9c84d4a 100644 --- a/spec/support/examples/builder_initialization.rb +++ b/spec/support/examples/builder_initialization.rb @@ -6,11 +6,18 @@ def example obj = object obj.string :name settings_for(obj) + target_for(obj) preferences_for(obj) add_ids_to(obj) obj end + def target_for(obj) + target = obj.object :target, null: true + target.number :id + target + end + def settings_for(obj) settings = obj.object :settings settings.string :email diff --git a/spec/support/examples/expand_null_true.rb b/spec/support/examples/expand_null_true.rb new file mode 100644 index 0000000..21f62c5 --- /dev/null +++ b/spec/support/examples/expand_null_true.rb @@ -0,0 +1,13 @@ +module Examples + class ExpandNullTrue + include JSON::SchemaBuilder + + def example + object.tap do |base_obj| + foo_obj = base_obj.object(:foo, required: true) + bar_obj = foo_obj.object(:bar, null: true, required: true) + bar_obj.string :baz + end + end + end +end diff --git a/spec/support/examples/nested_nullable.rb b/spec/support/examples/nested_nullable.rb new file mode 100644 index 0000000..9871f50 --- /dev/null +++ b/spec/support/examples/nested_nullable.rb @@ -0,0 +1,12 @@ +module Examples + class NestedNullable + include JSON::SchemaBuilder + + def example + object.tap do |base_obj| + obj = base_obj.object :nullable_object, null: true + obj.string :nullable_string, null: true + end + end + end +end