Skip to content

Commit

Permalink
Merge pull request #474 from dcr8898/permit-nullary-constraints
Browse files Browse the repository at this point in the history
Provide a consistent interface for specifying nullary constraints
  • Loading branch information
flash-gordon authored Jul 31, 2024
2 parents fdc3f26 + f00d430 commit dcf3302
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 12 deletions.
4 changes: 2 additions & 2 deletions lib/dry/types/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ def optional
# @return [Constrained]
#
# @api public
def constrained(options)
constrained_type.new(self, rule: Types.Rule(options))
def constrained(...)
constrained_type.new(self, rule: Types.Rule(...))
end

# Turn a type into a type with a default value
Expand Down
26 changes: 22 additions & 4 deletions lib/dry/types/constrained.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,24 @@ def try(input, &block)
end
end

# @param [Hash] options
# The options hash provided to {Types.Rule} and combined
# @param *nullary_rules [Array<Symbol>] a list of rules that do not require an additional
# argument (e.g., :odd)
# @param **unary_rules [Hash] a list of rules that require an additional argument
# (e.g., gt: 0)
# The parameters are merger to create a rules hash provided to {Types.Rule} and combined
# using {&} with previous {#rule}
#
# @return [Constrained]
#
# @see Dry::Logic::Operators#and
#
# @api public
def constrained(options)
with(rule: rule & Types.Rule(options))
def constrained(*nullary_rules, **unary_rules)
nullary_rules_hash = parse_arguments(nullary_rules)

rules = nullary_rules_hash.merge(unary_rules)

with(rule: rule & Types.Rule(rules))
end

# @return [true]
Expand Down Expand Up @@ -133,6 +140,17 @@ def constructor_type
def decorate?(response)
super || response.is_a?(Constructor)
end

# @param [Array] positional_args
#
# @return [Hash]
#
# @api private
def parse_arguments(positional_arguments)
return positional_arguments.first if positional_arguments.first.is_a?(::Hash)

positional_arguments.flatten.zip([]).to_h
end
end
end
end
4 changes: 2 additions & 2 deletions lib/dry/types/sum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ def meta(data = Undefined)
# @see Builder#constrained
#
# @api public
def constrained(options)
def constrained(...)
if optional?
right.constrained(options).optional
right.constrained(...).optional
else
super
end
Expand Down
24 changes: 20 additions & 4 deletions spec/dry/types/predicate_inferrer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,29 @@ def type(*args)
expect(inferrer[type(:integer).constrained(gteq: 18)]).to eql([:int?, gteq?: 18])
end

it "works with rules without additional parameters" do
expect(inferrer[type(:integer).constrained([:odd])]).to eql([:int?, :odd?])
it "works with nullary rules" do
expect(inferrer[type(:integer).constrained(:odd, :even)]).to eql([:int?, :odd?, :even?])
end

it "can extract many rules" do
it "works with array of nullary rules" do
expect(inferrer[type(:integer).constrained([:odd, :even])]).to eql([:int?, :odd?, :even?])
end

it "works with combination of nullary and unary rules" do
expect(
inferrer[type(:integer).constrained(:odd, gteq: 18, lt: 100)]
).to eql([:int?, :odd?, gteq?: 18, lt?: 100])
end

it "works with array of nullary rules with unary rules" do
expect(
inferrer[type(:integer).constrained([:odd, :even], gteq: 18, lt: 100)]
).to eql([:int?, :odd?, :even?, gteq?: 18, lt?: 100])
end

it "works with hash of unary rules" do
expect(
inferrer[type(:integer).constrained(gteq: 18, lt: 100)]
inferrer[type(:integer).constrained({gteq: 18, lt: 100})]
).to eql([:int?, gteq?: 18, lt?: 100])
end

Expand Down

0 comments on commit dcf3302

Please sign in to comment.