From 56fc4e3bd448afb6ba39dcbfea078d32295747dd Mon Sep 17 00:00:00 2001 From: Nikita Shilnikov Date: Mon, 6 Jan 2025 18:16:42 +0100 Subject: [PATCH] Support for Maybe macro in info extension (close #471) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Santiago Doldán --- Gemfile | 1 + .../schema/extensions/info/schema_compiler.rb | 23 +++++++---- spec/extensions/info/schema_spec.rb | 39 ++++++++++++++----- spec/spec_helper.rb | 4 ++ 4 files changed, 51 insertions(+), 16 deletions(-) diff --git a/Gemfile b/Gemfile index 91379e88..b50fbccf 100644 --- a/Gemfile +++ b/Gemfile @@ -12,6 +12,7 @@ group :test do gem "i18n", require: false gem "json-schema" gem "ostruct" + gem "super_diff" gem "transproc" end diff --git a/lib/dry/schema/extensions/info/schema_compiler.rb b/lib/dry/schema/extensions/info/schema_compiler.rb index c497873c..f079fcb7 100644 --- a/lib/dry/schema/extensions/info/schema_compiler.rb +++ b/lib/dry/schema/extensions/info/schema_compiler.rb @@ -70,20 +70,25 @@ def visit_and(node, opts = EMPTY_HASH) # @api private def visit_implication(node, opts = EMPTY_HASH) - node.each do |el| - visit(el, opts.merge(required: false)) + case node + in [:not, [:predicate, [:nil?, _]]], el + visit(el, {**opts, nullable: true}) + else + node.each do |el| + visit(el, {**opts, required: false}) + end end end # @api private def visit_each(node, opts = EMPTY_HASH) - visit(node, opts.merge(member: true)) + visit(node, {**opts, member: true}) end # @api private def visit_key(node, opts = EMPTY_HASH) name, rest = node - visit(rest, opts.merge(key: name, required: true)) + visit(rest, {**opts, key: name, required: true}) end # @api private @@ -93,19 +98,23 @@ def visit_predicate(node, opts = EMPTY_HASH) key = opts[:key] if name.equal?(:key?) - keys[rest[0][1]] = {required: opts.fetch(:required, true)} + keys[rest[0][1]] = { + required: opts.fetch(:required, true) + } else type = PREDICATE_TO_TYPE[name] - assign_type(key, type) if type + nullable = opts.fetch(:nullable, false) + assign_type(key, type, nullable) if type end end # @api private - def assign_type(key, type) + def assign_type(key, type, nullable) if keys[key][:type] keys[key][:member] = type else keys[key][:type] = type + keys[key][:nullable] = nullable end end end diff --git a/spec/extensions/info/schema_spec.rb b/spec/extensions/info/schema_spec.rb index 840d084e..b48a74b3 100644 --- a/spec/extensions/info/schema_spec.rb +++ b/spec/extensions/info/schema_spec.rb @@ -19,7 +19,7 @@ required(:street).filled(:string) required(:zipcode).filled(:string) required(:city).filled(:string) - optional(:phone).filled(:string) + optional(:phone).maybe(:string) end end end @@ -29,24 +29,29 @@ keys: { email: { required: true, + nullable: false, type: "string" }, age: { required: false, + nullable: false, type: "integer" }, roles: { required: true, type: "array", + nullable: false, member: { keys: { name: { required: true, - type: "string" + type: "string", + nullable: false }, desc: { required: false, - type: "string" + type: "string", + nullable: false } } } @@ -54,22 +59,27 @@ address: { required: false, type: "hash", + nullable: false, keys: { street: { required: true, - type: "string" + type: "string", + nullable: false }, zipcode: { required: true, - type: "string" + type: "string", + nullable: false }, city: { required: true, - type: "string" + type: "string", + nullable: false }, phone: { required: false, - type: "string" + type: "string", + nullable: true } } } @@ -88,6 +98,7 @@ required(:opt2).filled(Types::Array(:string)) required(:opt3).filled(Types::Array(:integer)) required(:opt4).filled(Types::Array(:bool)) + required(:opt5).maybe(Types::Array(:bool)) end end @@ -96,21 +107,31 @@ keys: { opt1: { required: true, - type: "array" + type: "array", + nullable: false }, opt2: { required: true, type: "array", + nullable: false, member: "string" }, opt3: { required: true, type: "array", + nullable: false, member: "integer" }, opt4: { required: true, type: "array", + nullable: false, + member: "bool" + }, + opt5: { + required: true, + type: "array", + nullable: true, member: "bool" } } @@ -136,7 +157,7 @@ }.each do |type_spec, type_name| it "infers '#{type_name}' from '#{type_spec}'" do expect(Dry::Schema.define { required(:key).value(type_spec) }.info).to eql( - keys: {key: {required: true, type: type_name}} + keys: {key: {required: true, nullable: false, type: type_name}} ) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 74ddf414..d192cc77 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -17,6 +17,10 @@ Dir[SPEC_ROOT.join("shared/**/*.rb")].each(&method(:require)) Dir[SPEC_ROOT.join("support/**/*.rb")].each(&method(:require)) +Warning.ignore(%r{rspec/matchers}) + +require "super_diff/rspec" + require "dry/schema" require "dry/types"