diff --git a/Gemfile b/Gemfile index 1f1e3bf8..3cc2a4e2 100644 --- a/Gemfile +++ b/Gemfile @@ -28,8 +28,8 @@ group :tools do end group :benchmarks do - gem "actionpack", "~> 5.0" - gem "activemodel", "~> 5.0" + gem "actionpack" + gem "activemodel" gem "benchmark-ips" gem "hotch", platform: :mri gem "virtus" diff --git a/lib/dry/schema/extensions/struct.rb b/lib/dry/schema/extensions/struct.rb index 943c296c..770dffbe 100644 --- a/lib/dry/schema/extensions/struct.rb +++ b/lib/dry/schema/extensions/struct.rb @@ -5,20 +5,42 @@ module Dry module Schema module Macros + class StructToSchema < ::Dry::Struct::Compiler + def call(struct) + visit(struct.to_ast) + end + + # strip away structs from AST + def visit_struct(node) + _, ast = node + visit(ast) + end + end + + Hash.option :struct_compiler, default: proc { StructToSchema.new(schema_dsl.config.types) } + Hash.prepend(::Module.new { def call(*args) - if args.size >= 1 && args[0].is_a?(::Class) && args[0] <= ::Dry::Struct + if args.size >= 1 && struct?(args[0]) if block_given? raise ArgumentError, "blocks are not supported when using "\ "a struct class (#{name.inspect} => #{args[0]})" end - super(args[0].schema, *args.drop(1)) - type(schema_dsl.types[name].constructor(args[0].schema)) + schema = struct_compiler.(args[0]) + + super(schema, *args.drop(1)) + type(schema_dsl.types[name].constructor(schema)) else super end end + + private + + def struct?(type) + type.is_a?(::Class) && type <= ::Dry::Struct + end }) end diff --git a/spec/extensions/struct_spec.rb b/spec/extensions/struct_spec.rb index 31347a4a..4ef4ef9f 100644 --- a/spec/extensions/struct_spec.rb +++ b/spec/extensions/struct_spec.rb @@ -47,6 +47,38 @@ ) end end + + context "complex struct" do + let(:struct) do + Class.new(Dry::Struct) do + attribute :name, Types::String + attribute :addresses, Types::Array do + attribute :city, Types::String + attribute? :street, Types::String + end + end + end + + context "with valid input" do + let(:input) do + {foo: {name: "Jane", addresses: [{city: "New York"}]}} + end + + it "has no errors" do + expect(schema.(input).errors.to_h).to eq({}) + end + end + + context "with empty nested array" do + let(:input) do + {foo: {name: "Jane", addresses: []}} + end + + it "has no errors" do + expect(schema.(input).errors.to_h).to eq({}) + end + end + end end context "value macro" do