Skip to content

Commit 0a88f78

Browse files
committed
Pretty-print contracts in error messages.
1 parent 8112ddc commit 0a88f78

File tree

5 files changed

+99
-9
lines changed

5 files changed

+99
-9
lines changed

lib/contracts.rb

+43-8
Original file line numberDiff line numberDiff line change
@@ -116,22 +116,57 @@ def to_s
116116
# This function is used by the default #failure_callback method
117117
# and uses the hash passed into the failure_callback method.
118118
def self.failure_msg(data)
119-
expected = Contracts::Formatters::Expected.new(data[:contract]).contract
120-
position = Contracts::Support.method_position(data[:method])
119+
indent_amount = 8
121120
method_name = Contracts::Support.method_name(data[:method])
122121

122+
# Header
123123
header = if data[:return_value]
124124
"Contract violation for return value:"
125125
else
126126
"Contract violation for argument #{data[:arg_pos]} of #{data[:total_args]}:"
127127
end
128128

129-
%{#{header}
130-
Expected: #{expected},
131-
Actual: #{data[:arg].inspect}
132-
Value guarded in: #{data[:class]}::#{method_name}
133-
With Contract: #{data[:contracts]}
134-
At: #{position} }
129+
# Expected
130+
expected_prefix = "Expected: "
131+
expected_value = Contracts::Support.indent_string(
132+
Contracts::Formatters::Expected.new(data[:contract]).contract.pretty_inspect,
133+
expected_prefix.length
134+
).strip
135+
expected_line = expected_prefix + expected_value + ","
136+
137+
# Actual
138+
actual_prefix = "Actual: "
139+
actual_value = Contracts::Support.indent_string(
140+
data[:arg].pretty_inspect,
141+
actual_prefix.length
142+
).strip
143+
actual_line = actual_prefix + actual_value
144+
145+
# Value guarded in
146+
value_prefix = "Value guarded in: "
147+
value_value = "#{data[:class]}::#{method_name}"
148+
value_line = value_prefix + value_value
149+
150+
# Contract
151+
contract_prefix = "With Contract: "
152+
contract_value = data[:contracts].to_s
153+
contract_line = contract_prefix + contract_value
154+
155+
# Position
156+
position_prefix = "At: "
157+
position_value = Contracts::Support.method_position(data[:method])
158+
position_line = position_prefix + position_value
159+
160+
header +
161+
"\n" +
162+
Contracts::Support.indent_string(
163+
[expected_line,
164+
actual_line,
165+
value_line,
166+
contract_line,
167+
position_line].join("\n"),
168+
indent_amount
169+
)
135170
end
136171

137172
# Callback for when a contract fails. By default it raises

lib/contracts/formatters.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require "pp"
2+
13
module Contracts
24
# A namespace for classes related to formatting.
35
module Formatters
@@ -31,7 +33,7 @@ def hash_contract(hash)
3133
# Formats Array contracts.
3234
def array_contract(array)
3335
@full = true
34-
array.map { |v| InspectWrapper.create(contract(v), @full) }.inspect
36+
array.map { |v| InspectWrapper.create(contract(v), @full) }
3537
end
3638
end
3739

lib/contracts/support.rb

+7
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ def eigenclass?(target)
4242
target <= eigenclass_of(Object)
4343
end
4444

45+
def indent_string(string, amount)
46+
string.gsub(
47+
/^(?!$)/,
48+
(string[/^[ \t]/] || " ") * amount
49+
)
50+
end
51+
4552
private
4653

4754
# Module eigenclass can be detected by its ancestor chain

spec/contracts_spec.rb

+22
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,28 @@ def delim(match)
637637
end.to raise_error(ContractError, not_s(delim "String or Symbol"))
638638
end
639639

640+
it "should wrap and pretty print for long param contracts" do
641+
expect do
642+
@o.long_array_param_contracts(true)
643+
end.to(
644+
raise_error(
645+
ParamContractError,
646+
/\[\(String or Symbol\),\n \(String or Symbol\),/
647+
)
648+
)
649+
end
650+
651+
it "should wrap and pretty print for long return contracts" do
652+
expect do
653+
@o.long_array_return_contracts
654+
end.to(
655+
raise_error(
656+
ReturnContractError,
657+
/\[\(String or Symbol\),\n \(String or Symbol\),/
658+
)
659+
)
660+
end
661+
640662
it "should not contain Contracts:: module prefix" do
641663
expect do
642664
@o.double("bad")

spec/fixtures/fixtures.rb

+24
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,30 @@ def array_complex_contracts(data)
152152
def nested_array_complex_contracts(data)
153153
end
154154

155+
Contract [
156+
C::Or[String, Symbol],
157+
C::Or[String, Symbol],
158+
C::Or[String, Symbol],
159+
C::Or[String, Symbol],
160+
C::Or[String, Symbol],
161+
C::Or[String, Symbol],
162+
C::Or[String, Symbol]
163+
] => nil
164+
def long_array_param_contracts(data)
165+
end
166+
167+
Contract C::None => [
168+
C::Or[String, Symbol],
169+
C::Or[String, Symbol],
170+
C::Or[String, Symbol],
171+
C::Or[String, Symbol],
172+
C::Or[String, Symbol],
173+
C::Or[String, Symbol],
174+
C::Or[String, Symbol]
175+
]
176+
def long_array_return_contracts
177+
end
178+
155179
Contract Proc => C::Any
156180
def do_call(&block)
157181
block.call

0 commit comments

Comments
 (0)