contracts-0.16.0/0000755000004100000410000000000013120354472013627 5ustar www-datawww-datacontracts-0.16.0/TODO.markdown0000644000004100000410000000072713120354472016146 0ustar www-datawww-data- maybe make some screencasts - you can now do something like Haskell's quickcheck. Every contract has a method 'test_data' or something. You can use that data to automatically check methods with contracts to make sure they are correct. - http://www.cse.chalmers.se/~rjmh/QuickCheck/manual.html - for stuff like the Not contract, should I make a standard set of classes to check those functions with? Would that be useful at all? - also write specs for this stuff contracts-0.16.0/Rakefile0000644000004100000410000000046013120354472015274 0ustar www-datawww-dataif RUBY_VERSION >= "2" task :default => [:spec, :rubocop] require "rubocop/rake_task" RuboCop::RakeTask.new else task :default => [:spec] end task :add_tag do `git tag -a v#{Contracts::VERSION} -m 'v#{Contracts::VERSION}'` end require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) contracts-0.16.0/Gemfile0000644000004100000410000000041113120354472015116 0ustar www-datawww-datasource "http://rubygems.org" gemspec group :test do gem "rspec" gem "aruba" gem "cucumber", "~> 1.3.20" gem "rubocop", "~> 0.29.1" if RUBY_VERSION >= "2" end group :development do gem "relish" gem "method_profiler" gem "ruby-prof" gem "rake" end contracts-0.16.0/script/0000755000004100000410000000000013120354472015133 5ustar www-datawww-datacontracts-0.16.0/script/docs-release0000755000004100000410000000007213120354472017426 0ustar www-datawww-data#!/usr/bin/env bash relish push contracts/contracts-ruby contracts-0.16.0/script/rubocop.rb0000644000004100000410000000020313120354472017124 0ustar www-datawww-dataif RUBY_VERSION.to_f == 2.1 puts "running rubocop..." puts `bundle exec rubocop #{ARGV.join(" ")} -D` exit $?.exitstatus end contracts-0.16.0/script/docs-staging0000755000004100000410000000010213120354472017434 0ustar www-datawww-data#!/usr/bin/env bash relish push contracts-staging/contracts-ruby contracts-0.16.0/script/cucumber0000755000004100000410000000015213120354472016664 0ustar www-datawww-data#!/usr/bin/env bash if ruby -e 'exit(1) unless RUBY_VERSION.to_f >= 2.0'; then bundle exec cucumber fi contracts-0.16.0/features/0000755000004100000410000000000013120354472015445 5ustar www-datawww-datacontracts-0.16.0/features/basics/0000755000004100000410000000000013120354472016711 5ustar www-datawww-datacontracts-0.16.0/features/basics/functype.feature0000644000004100000410000000304613120354472022126 0ustar www-datawww-dataFeature: Fetching contracted function type You can use `functype(name)` method for that: ```ruby functype(:add) # => "add :: Num, Num => Num" ``` Background: Given a file named "example.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core Contract C::Num, C::Num => C::Num def add(a, b) a + b end Contract String => String def self.greeting(name) "Hello, #{name}" end class << self Contract C::Num => C::Num def increment(number) number + 1 end end end """ Scenario: functype on instance method Given a file named "instance_method_functype.rb" with: """ruby require "./example" puts Example.new.functype(:add) """ When I run `ruby instance_method_functype.rb` Then the output should contain: """ add :: Num, Num => Num """ Scenario: functype on class method Given a file named "class_method_functype.rb" with: """ruby require "./example" puts Example.functype(:greeting) """ When I run `ruby class_method_functype.rb` Then the output should contain: """ greeting :: String => String """ Scenario: functype on singleton method Given a file named "singleton_method_functype.rb" with: """ruby require "./example" puts Example.functype(:increment) """ When I run `ruby singleton_method_functype.rb` Then the output should contain: """ increment :: Num => Num """ contracts-0.16.0/features/basics/simple_example.feature0000644000004100000410000001314213120354472023273 0ustar www-datawww-dataFeature: Simple examples and Contract violations Contracts.ruby allows specification of contracts on per-method basis, where method arguments and return value will be validated upon method call. Example: ```ruby Contract C::Num, C::Num => C::Num def add(a, b) a + b end ``` Here `Contract arg_contracts... => return_contract` defines list of argument contracts `args_contracts...` as `C::Num, C::Num` (i.e.: both arguments should be numbers) and return value contract `return_contract` as `C::Num` (i.e.: return value should be a number too). `Contract arg_contracts... => return_contract` affects next defined instance, class or singleton method, meaning that all of these work: - [Instance method](#instance-method), - [Class method](#class-method), - [Singleton method](#singleton-method). Whenever invalid argument is passed to a contracted method, corresponding `ContractError` will be raised. That happens right after bad value got into system protected by contracts and prevents error propagation: first non-contracts library frame in exception's backtrace is a culprit for passing an invalid argument - you do not need to verify 20-30 frames to find a culprit! Example of such error: [instance method contract violation](#instance-method-contract-violation). Whenever invalid return value is returned from a contracted method, corresponding `ContractError` will be raised. That happens right after method returned this value and prevents error propagation: `At: your_filename.rb:17` part of error message points directly to a culprit method. Example of such error: [return value contract violation](#singleton-method-return-value-contract-violation). Contract violation error consists of such parts: - Violation type: - `Contract violation for argument X of Y: (ParamContractError)`, - `Contract violation for return value (ReturnContractError)`. - Expected contract, example: `Expected: Num`. - Actual value, example: `Actual: "foo"`. - Location of violated contract, example: `Value guarded in: Example::add`. - Full contract, example: `With Contract: Num, Num => Num`. - Source code location of contracted method, example: `At: lib/your_library/some_class.rb:17`. Scenario: Instance method Given a file named "instance_method.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core Contract C::Num, C::Num => C::Num def add(a, b) a + b end end puts Example.new.add(2, 2) """ When I run `ruby instance_method.rb` Then the output should contain: """ 4 """ Scenario: Instance method contract violation Given a file named "instance_method_violation.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core Contract C::Num, C::Num => C::Num def add(a, b) a + b end end puts Example.new.add(2, "foo") """ When I run `ruby instance_method_violation.rb` Then the output should contain: """ : Contract violation for argument 2 of 2: (ParamContractError) Expected: Num, Actual: "foo" Value guarded in: Example::add With Contract: Num, Num => Num At: instance_method_violation.rb:8 """ Scenario: Class method Given a file named "class_method.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core Contract C::Num, C::Num => C::Num def self.add(a, b) a + b end end puts Example.add(2, 2) """ When I run `ruby class_method.rb` Then the output should contain: """ 4 """ Scenario: Class method contract violation Given a file named "class_method_violation.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core Contract C::Num, C::Num => C::Num def self.add(a, b) a + b end end puts Example.add(:foo, 2) """ When I run `ruby class_method_violation.rb` Then the output should contain: """ : Contract violation for argument 1 of 2: (ParamContractError) Expected: Num, Actual: :foo Value guarded in: Example::add With Contract: Num, Num => Num At: class_method_violation.rb:8 """ Scenario: Singleton method Given a file named "singleton_method.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core class << self Contract C::Num, C::Num => C::Num def add(a, b) a + b end end end puts Example.add(2, 2) """ When I run `ruby singleton_method.rb` Then the output should contain: """ 4 """ Scenario: Singleton method return value contract violation Given a file named "singleton_method_violation.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core class << self Contract C::Num, C::Num => C::Num def add(a, b) # notice here non-number is returned nil end end end puts Example.add(2, 2) """ When I run `ruby singleton_method_violation.rb` Then the output should contain: """ : Contract violation for return value: (ReturnContractError) Expected: Num, Actual: nil Value guarded in: Example::add With Contract: Num, Num => Num At: singleton_method_violation.rb:9 """ contracts-0.16.0/features/builtin_contracts/0000755000004100000410000000000013120354472021173 5ustar www-datawww-datacontracts-0.16.0/features/builtin_contracts/and.feature0000644000004100000410000000535313120354472023320 0ustar www-datawww-dataFeature: And Takes a variable number of contracts. The contract passes if all of the contracts pass. ```ruby Contract C::And[Float, C::Neg] => String ``` This example will validate first argument of a method and accept only negative `Float`. Background: Given a file named "and_usage.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core Contract C::And[Float, C::Neg] => String def fneg_string(number) number.to_i.to_s end end """ Scenario: Accepts negative float Given a file named "accepts_negative_float.rb" with: """ruby require "./and_usage" puts Example.new.fneg_string(-3.7) """ When I run `ruby accepts_negative_float.rb` Then output should contain: """ -3 """ Scenario: Rejects positive float Given a file named "rejects_positive_float.rb" with: """ruby require "./and_usage" puts Example.new.fneg_string(7.5) """ When I run `ruby rejects_positive_float.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: (Float and Neg), Actual: 7.5 Value guarded in: Example::fneg_string With Contract: And => String """ Scenario: Rejects negative integer Given a file named "rejects_negative_integer.rb" with: """ruby require "./and_usage" puts Example.new.fneg_string(-5) """ When I run `ruby rejects_negative_integer.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: (Float and Neg), Actual: -5 Value guarded in: Example::fneg_string With Contract: And => String """ Scenario: Rejects positive integer Given a file named "rejects_positive_integer.rb" with: """ruby require "./and_usage" puts Example.new.fneg_string(5) """ When I run `ruby rejects_positive_integer.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: (Float and Neg), Actual: 5 Value guarded in: Example::fneg_string With Contract: And => String """ Scenario: Rejects others Given a file named "rejects_others.rb" with: """ruby require "./and_usage" puts Example.new.fneg_string(:foo) """ When I run `ruby rejects_others.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: (Float and Neg), Actual: :foo Value guarded in: Example::fneg_string With Contract: And => String """ contracts-0.16.0/features/builtin_contracts/maybe.feature0000644000004100000410000000002613120354472023643 0ustar www-datawww-dataFeature: Maybe (TODO) contracts-0.16.0/features/builtin_contracts/args.feature0000644000004100000410000000371713120354472023514 0ustar www-datawww-dataFeature: Args Used for `*args` (variadic functions). Takes contract and uses it to validate every element passed in through `*args`. ```ruby Contract C::Args[C::Num] => C::Bool def example(*args) ``` This example contract will validate all arguments passed through `*args` to accept only numbers. Background: Given a file named "args_usage.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core Contract C::Args[C::Num] => C::Bool def only_nums(*args) args.inspect end end """ Scenario: Accepts no arguments Given a file named "accepts_no_arguments.rb" with: """ruby require "./args_usage" puts Example.new.only_nums """ When I run `ruby accepts_no_arguments.rb` Then the output should contain: """ [] """ Scenario: Accepts one valid argument Given a file named "accepts_one_argument.rb" with: """ruby require "./args_usage" puts Example.new.only_nums(42) """ When I run `ruby accepts_one_argument.rb` Then the output should contain: """ [42] """ Scenario: Accepts many valid arguments Given a file named "accepts_many_arguments.rb" with: """ruby require "./args_usage" puts Example.new.only_nums(42, 45, 17, 24) """ When I run `ruby accepts_many_arguments.rb` Then the output should contain: """ [42, 45, 17, 24] """ Scenario: Rejects invalid argument Given a file named "rejects_invalid_argument.rb" with: """ruby require "./args_usage" puts Example.new.only_nums(42, "foo", 17, 24) """ When I run `ruby rejects_invalid_argument.rb` Then the output should contain: """ : Contract violation for argument 1 of 4: (ParamContractError) Expected: (Args[Contracts::Builtin::Num]), Actual: "foo" Value guarded in: Example::only_nums With Contract: Args => Bool """ contracts-0.16.0/features/builtin_contracts/array_of.feature0000644000004100000410000000003013120354472024343 0ustar www-datawww-dataFeature: ArrayOf (TODO) contracts-0.16.0/features/builtin_contracts/range_of.feature0000644000004100000410000000003013120354472024321 0ustar www-datawww-dataFeature: RangeOf (TODO) contracts-0.16.0/features/builtin_contracts/int.feature0000644000004100000410000000414113120354472023342 0ustar www-datawww-dataFeature: Int Checks that an argument is an integer. ```ruby Contract C::Int => C::Int ``` Background: Given a file named "int_usage.rb" with: """ruby require "contracts" C = Contracts class Integr include Contracts::Core Contract C::Int => C::Int def prev(number) number - 1 end end """ Scenario: Accepts positive integers Given a file named "accepts_positive_integers.rb" with: """ruby require "./int_usage" puts Integr.new.prev(7) """ When I run `ruby accepts_positive_integers.rb` Then output should contain: """ 6 """ Scenario: Accepts zero Given a file named "accepts_zero.rb" with: """ruby require "./int_usage" puts Integr.new.prev(1) """ When I run `ruby accepts_zero.rb` Then output should contain: """ 0 """ Scenario: Accepts negative integers Given a file named "accepts_negative_integers.rb" with: """ruby require "./int_usage" puts Integr.new.prev(-1) """ When I run `ruby accepts_negative_integers.rb` Then output should contain: """ -2 """ Scenario: Rejects floats Given a file named "rejects_floats.rb" with: """ruby require "./int_usage" puts Integr.new.prev(3.43) """ When I run `ruby rejects_floats.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: Int, Actual: 3.43 Value guarded in: Integr::prev With Contract: Int => Int """ And output should contain "int_usage.rb:8" Scenario: Rejects other values Given a file named "rejects_others.rb" with: """ruby require "./int_usage" puts Integr.new.prev("foo") """ When I run `ruby rejects_others.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: Int, Actual: "foo" Value guarded in: Integr::prev With Contract: Int => Int """ And output should contain "int_usage.rb:8" contracts-0.16.0/features/builtin_contracts/keyword_args.feature0000644000004100000410000000003413120354472025245 0ustar www-datawww-dataFeature: KeywordArgs (TODO) contracts-0.16.0/features/builtin_contracts/send.feature0000644000004100000410000000616313120354472023507 0ustar www-datawww-dataFeature: Send Takes a variable number of method names as symbols. Given an argument, all of those methods are called on the argument one by one. If they all return true, the contract passes. ```ruby Contract C::Send[:valid?, :has_items?] => C::ArrayOf[Item] ``` This contract will pass only if: `arg.valid? == true && arg.has_items? == true`, where `arg` is the first argument. Background: Given a file named "item.rb" with: """ruby Item = Struct.new(:name, :cost) Item::DEFAULT = Item["default", 0] """ Given a file named "send_usage.rb" with: """ruby require "contracts" C = Contracts require "./item" class FetchItemCommand include Contracts::Core Contract C::Send[:valid?, :has_items?] => C::ArrayOf[Item] def call(subject) ([Item::DEFAULT] + subject.items).uniq end end """ Scenario: All methods return `true` Given a file named "box.rb" with: """ruby class Box def valid? true end def has_items? true end def items [Item["cat", 599.99]] end end require "./send_usage" p FetchItemCommand.new.call(Box.new) """ When I run `ruby box.rb` Then output should contain: """ [#, #] """ Scenario: When second method returns `false` Given a file named "cat.rb" with: """ruby class Cat def valid? true end def has_items? false end end require "./send_usage" p FetchItemCommand.new.call(Cat.new) """ When I run `ruby cat.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: (a value that returns true for all of [:valid?, :has_items?]), """ And output should contain: """ Actual: # C::Nat ``` Background: Given a file named "nat_usage.rb" with: """ruby require "contracts" C = Contracts class Natural include Contracts::Core Contract C::Nat => C::Nat def prev(number) number - 1 end end """ Scenario: Accepts positive integers Given a file named "accepts_positive_integers.rb" with: """ruby require "./nat_usage" puts Natural.new.prev(7) """ When I run `ruby accepts_positive_integers.rb` Then output should contain: """ 6 """ Scenario: Accepts zero Given a file named "accepts_zero.rb" with: """ruby require "./nat_usage" puts Natural.new.prev(1) """ When I run `ruby accepts_zero.rb` Then output should contain: """ 0 """ Scenario: Rejects negative integers Given a file named "rejects_negative_integers.rb" with: """ruby require "./nat_usage" puts Natural.new.prev(-1) """ When I run `ruby rejects_negative_integers.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: Nat, Actual: -1 Value guarded in: Natural::prev With Contract: Nat => Nat """ And output should contain "nat_usage.rb:8" Scenario: Rejects negative integers as a return value Given a file named "rejects_negative_integers.rb" with: """ruby require "./nat_usage" puts Natural.new.prev(0) """ When I run `ruby rejects_negative_integers.rb` Then output should contain: """ : Contract violation for return value: (ReturnContractError) Expected: Nat, Actual: -1 Value guarded in: Natural::prev With Contract: Nat => Nat """ And output should contain "nat_usage.rb:8" Scenario: Rejects floats Given a file named "rejects_floats.rb" with: """ruby require "./nat_usage" puts Natural.new.prev(3.43) """ When I run `ruby rejects_floats.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: Nat, Actual: 3.43 Value guarded in: Natural::prev With Contract: Nat => Nat """ And output should contain "nat_usage.rb:8" Scenario: Rejects other values Given a file named "rejects_others.rb" with: """ruby require "./nat_usage" puts Natural.new.prev("foo") """ When I run `ruby rejects_others.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: Nat, Actual: "foo" Value guarded in: Natural::prev With Contract: Nat => Nat """ And output should contain "nat_usage.rb:8" contracts-0.16.0/features/builtin_contracts/bool.feature0000644000004100000410000000253613120354472023511 0ustar www-datawww-dataFeature: Bool Checks that the argument is a `true` or `false`. ```ruby Contract String => C::Bool ``` Background: Given a file named "bool_usage.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core Contract String => C::Bool def self.strong?(password) return if password == "" password.length > 22 end end """ Scenario: Accepts `true` Given a file named "true.rb" with: """ruby require "./bool_usage" puts Example.strong?("verystrongandLon774gPassword!ForYouHere") """ When I run `ruby true.rb` Then output should contain: """ true """ Scenario: Accepts `false` Given a file named "false.rb" with: """ruby require "./bool_usage" puts Example.strong?("welcome") """ When I run `ruby false.rb` Then output should contain: """ false """ Scenario: Rejects everything else Given a file named "nil.rb" with: """ruby require "./bool_usage" puts Example.strong?("") """ When I run `ruby nil.rb` Then output should contain: """ : Contract violation for return value: (ReturnContractError) Expected: Bool, Actual: nil Value guarded in: Example::strong? With Contract: String => Bool """ contracts-0.16.0/features/builtin_contracts/or.feature0000644000004100000410000000375113120354472023176 0ustar www-datawww-dataFeature: Or Takes a variable number of contracts. The contract passes if any of the contracts pass. ```ruby Contract C::Or[Float, C::Nat] => String ``` This example will validate first argument of a method and accept either `Float` or natural integer. Background: Given a file named "or_usage.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core Contract C::Or[Float, C::Nat] => String def nat_string(number) number.to_i.to_s end end """ Scenario: Accepts float Given a file named "accepts_float.rb" with: """ruby require "./or_usage" puts Example.new.nat_string(3.7) """ When I run `ruby accepts_float.rb` Then output should contain: """ 3 """ Scenario: Accepts natural Given a file named "accepts_natural.rb" with: """ruby require "./or_usage" puts Example.new.nat_string(7) """ When I run `ruby accepts_natural.rb` Then output should contain: """ 7 """ Scenario: Rejects negative integer Given a file named "rejects_negative_integer.rb" with: """ruby require "./or_usage" puts Example.new.nat_string(-3) """ When I run `ruby rejects_negative_integer.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: (Float or Nat), Actual: -3 Value guarded in: Example::nat_string With Contract: Or => String """ Scenario: Rejects other values Given a file named "rejects_other.rb" with: """ruby require "./or_usage" puts Example.new.nat_string(nil) """ When I run `ruby rejects_other.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: (Float or Nat), Actual: nil Value guarded in: Example::nat_string With Contract: Or => String """ contracts-0.16.0/features/builtin_contracts/xor.feature0000644000004100000410000000510013120354472023354 0ustar www-datawww-dataFeature: Xor Takes a variable number of contracts. The contract passes if one and only one of the contracts pass. ```ruby Contract C::Xor[Float, C::Neg] => String ``` This example will validate first argument of a method and accept either `Float` or natural integer, but not both. Background: Given a file named "xor_usage.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core Contract C::Xor[Float, C::Neg] => String def strange_number(number) number.to_i.to_s end end """ Scenario: Accepts float Given a file named "accepts_float.rb" with: """ruby require "./xor_usage" puts Example.new.strange_number(3.7) """ When I run `ruby accepts_float.rb` Then output should contain: """ 3 """ Scenario: Accepts negative integer Given a file named "accepts_negative_integer.rb" with: """ruby require "./xor_usage" puts Example.new.strange_number(-7) """ When I run `ruby accepts_negative_integer.rb` Then output should contain: """ -7 """ Scenario: Rejects negative float Given a file named "rejects_negative_float.rb" with: """ruby require "./xor_usage" puts Example.new.strange_number(-3.5) """ When I run `ruby rejects_negative_float.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: (Float xor Neg), Actual: -3.5 Value guarded in: Example::strange_number With Contract: Xor => String """ Scenario: Rejects positive integer Given a file named "rejects_positive_integer.rb" with: """ruby require "./xor_usage" puts Example.new.strange_number(9) """ When I run `ruby rejects_positive_integer.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: (Float xor Neg), Actual: 9 Value guarded in: Example::strange_number With Contract: Xor => String """ Scenario: Rejects other values Given a file named "rejects_other.rb" with: """ruby require "./xor_usage" puts Example.new.strange_number(:foo) """ When I run `ruby rejects_other.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: (Float xor Neg), Actual: :foo Value guarded in: Example::strange_number With Contract: Xor => String """ contracts-0.16.0/features/builtin_contracts/enum.feature0000644000004100000410000000002513120354472023511 0ustar www-datawww-dataFeature: Enum (TODO) contracts-0.16.0/features/builtin_contracts/hash_of.feature0000644000004100000410000000002713120354472024156 0ustar www-datawww-dataFeature: HashOf (TODO) contracts-0.16.0/features/builtin_contracts/eq.feature0000644000004100000410000000002313120354472023150 0ustar www-datawww-dataFeature: Eq (TODO) contracts-0.16.0/features/builtin_contracts/respond_to.feature0000644000004100000410000000414113120354472024724 0ustar www-datawww-dataFeature: RespondTo Takes a variable number of method names as symbols. The contract passes if the argument responds to all of those methods. ```ruby Contract C::RespondTo[:email, :password, :confirmation] => C::Bool ``` This contract will pass only for objects that `respond_to?` `:email`, `:password` and `:confirmation`. Background: Given a file named "signup_validator.rb" with: """ruby require "contracts" C = Contracts class SignupValidator include Contracts::Core Contract C::RespondTo[:email, :password, :confirmation] => C::Bool def valid?(signup) !!signup.email.match("@") && signup.password.length > 6 && signup.password == signup.confirmation end end """ Given a file named "signup.rb" with: """ruby Signup = Struct.new(:email, :password, :confirmation) """ Given a file named "signin.rb" with: """ruby Signin = Struct.new(:email, :password) """ Given a file named "helper.rb" with: """ruby require "./signup_validator" require "./signup" require "./signin" """ Scenario: Accepts correct object Given a file named "correct.rb" with: """ruby require "./helper" puts SignupValidator.new.valid?(Signup["john@example.org", "welcome", "welcome"]) puts SignupValidator.new.valid?(Signup["john@example.org", "welcome", "welcomr"]) """ When I run `ruby correct.rb` Then output should contain: """ true false """ Scenario: Rejects incorrect object Given a file named "incorrect.rb" with: """ruby require "./helper" puts SignupValidator.new.valid?(Signin["john@example.org", "welcome"]) """ When I run `ruby incorrect.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: (a value that responds to [:email, :password, :confirmation]), Actual: # Value guarded in: SignupValidator::valid? With Contract: RespondTo => Bool """ contracts-0.16.0/features/builtin_contracts/none.feature0000644000004100000410000000672413120354472023520 0ustar www-datawww-dataFeature: None Fails for any argument. Often used to explicitly specify that method does not accept any arguments: ```ruby Contract C::None => Symbol def a_symbol :a_symbol end ``` The same behavior can be achieved when argument list omitted: ```ruby Contract Symbol def a_symbol :a_symbol end ``` Background: Given a file named "helper.rb" with: """ruby def autorescue yield rescue => e puts e.inspect end """ Given a file named "none_usage.rb" with: """ruby require "contracts" require "./helper" C = Contracts class Example include Contracts::Core Contract C::None => Symbol def self.a_symbol(*args) :a_symbol end end """ Scenario: Accepts nothing Given a file named "nothing.rb" with: """ruby require "./none_usage" puts Example.a_symbol """ When I run `ruby nothing.rb` Then output should contain: """ a_symbol """ Scenario: Rejects any argument Given a file named "anything.rb" with: """ruby require "./none_usage" autorescue { Example.a_symbol(nil) } autorescue { Example.a_symbol(12) } autorescue { Example.a_symbol(37.5) } autorescue { Example.a_symbol("foo") } autorescue { Example.a_symbol(:foo) } autorescue { Example.a_symbol({}) } autorescue { Example.a_symbol([]) } autorescue { Example.a_symbol(Object) } """ When I run `ruby anything.rb` Then output should contain: """ ParamContractError: Contract violation for argument 1 of 1: Expected: None, Actual: nil Value guarded in: Example::a_symbol With Contract: None => Symbol """ Then output should contain: """ ParamContractError: Contract violation for argument 1 of 1: Expected: None, Actual: 12 Value guarded in: Example::a_symbol With Contract: None => Symbol """ Then output should contain: """ ParamContractError: Contract violation for argument 1 of 1: Expected: None, Actual: 37.5 Value guarded in: Example::a_symbol With Contract: None => Symbol """ Then output should contain: """ ParamContractError: Contract violation for argument 1 of 1: Expected: None, Actual: "foo" Value guarded in: Example::a_symbol With Contract: None => Symbol """ Then output should contain: """ ParamContractError: Contract violation for argument 1 of 1: Expected: None, Actual: :foo Value guarded in: Example::a_symbol With Contract: None => Symbol """ Then output should contain: """ ParamContractError: Contract violation for argument 1 of 1: Expected: None, Actual: {} Value guarded in: Example::a_symbol With Contract: None => Symbol """ Then output should contain: """ ParamContractError: Contract violation for argument 1 of 1: Expected: None, Actual: [] Value guarded in: Example::a_symbol With Contract: None => Symbol """ Then output should contain: """ ParamContractError: Contract violation for argument 1 of 1: Expected: None, Actual: Object Value guarded in: Example::a_symbol With Contract: None => Symbol """ contracts-0.16.0/features/builtin_contracts/num.feature0000644000004100000410000000254713120354472023357 0ustar www-datawww-dataFeature: Num Checks that an argument is `Numeric`. ```ruby Contract C::Num => C::Num ``` Background: Given a file named "num_usage.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core Contract C::Num => C::Num def increase(number) number + 1 end end """ Scenario: Accepts integers Given a file named "accepts_integers.rb" with: """ruby require "./num_usage" puts Example.new.increase(7) """ When I run `ruby accepts_integers.rb` Then output should contain: """ 8 """ Scenario: Accepts floats Given a file named "accepts_floats.rb" with: """ruby require "./num_usage" puts Example.new.increase(7.5) """ When I run `ruby accepts_floats.rb` Then output should contain: """ 8.5 """ Scenario: Rejects other values Given a file named "rejects_others.rb" with: """ruby require "./num_usage" puts Example.new.increase("foo") """ When I run `ruby rejects_others.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: Num, Actual: "foo" Value guarded in: Example::increase With Contract: Num => Num """ And output should contain "num_usage.rb:8" contracts-0.16.0/features/builtin_contracts/not.feature0000644000004100000410000000002413120354472023344 0ustar www-datawww-dataFeature: Not (TODO) contracts-0.16.0/features/builtin_contracts/func.feature0000644000004100000410000000002513120354472023500 0ustar www-datawww-dataFeature: Func (TODO) contracts-0.16.0/features/builtin_contracts/neg.feature0000644000004100000410000000577113120354472023333 0ustar www-datawww-dataFeature: Neg Checks that an argument is negative `Numeric`. ```ruby Contract C::Neg => C::Neg ``` Background: Given a file named "pos_usage.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core Contract C::Neg => C::Neg def double_expense(amount) amount * 2 end end """ Scenario: Accepts negative integers Given a file named "accepts_negative_integers.rb" with: """ruby require "./pos_usage" puts Example.new.double_expense(-50) """ When I run `ruby accepts_negative_integers.rb` Then output should contain: """ -100 """ Scenario: Accepts negative floats Given a file named "accepts_negative_floats.rb" with: """ruby require "./pos_usage" puts Example.new.double_expense(-37.99) """ When I run `ruby accepts_negative_floats.rb` Then output should contain: """ -75.98 """ Scenario: Rejects positive integers Given a file named "rejects_positive_integers.rb" with: """ruby require "./pos_usage" puts Example.new.double_expense(50) """ When I run `ruby rejects_positive_integers.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: Neg, Actual: 50 Value guarded in: Example::double_expense With Contract: Neg => Neg """ And output should contain "pos_usage.rb:8" Scenario: Rejects positive floats Given a file named "rejects_positive_floats.rb" with: """ruby require "./pos_usage" puts Example.new.double_expense(42.50) """ When I run `ruby rejects_positive_floats.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: Neg, Actual: 42.5 Value guarded in: Example::double_expense With Contract: Neg => Neg """ And output should contain "pos_usage.rb:8" Scenario: Rejects zero Given a file named "rejects_zero.rb" with: """ruby require "./pos_usage" puts Example.new.double_expense(0) """ When I run `ruby rejects_zero.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: Neg, Actual: 0 Value guarded in: Example::double_expense With Contract: Neg => Neg """ And output should contain "pos_usage.rb:8" Scenario: Rejects other values Given a file named "rejects_others.rb" with: """ruby require "./pos_usage" puts Example.new.double_expense("foo") """ When I run `ruby rejects_others.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: Neg, Actual: "foo" Value guarded in: Example::double_expense With Contract: Neg => Neg """ And output should contain "pos_usage.rb:8" contracts-0.16.0/features/builtin_contracts/pos.feature0000644000004100000410000000601113120354472023347 0ustar www-datawww-dataFeature: Pos Checks that an argument is positive `Numeric`. ```ruby Contract C::Pos => C::Pos ``` Background: Given a file named "pos_usage.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core Contract C::Pos, C::Pos => C::Pos def power(number, power) return number if power <= 1 number * self.power(number, power - 1) end end """ Scenario: Accepts positive integers Given a file named "accepts_positive_integers.rb" with: """ruby require "./pos_usage" puts Example.new.power(3, 4) """ When I run `ruby accepts_positive_integers.rb` Then output should contain: """ 81 """ Scenario: Accepts positive floats Given a file named "accepts_positive_floats.rb" with: """ruby require "./pos_usage" puts Example.new.power(3.7, 4.5) """ When I run `ruby accepts_positive_floats.rb` Then output should contain: """ 693.4395 """ Scenario: Rejects negative integers Given a file named "rejects_negative_integers.rb" with: """ruby require "./pos_usage" puts Example.new.power(3, -4) """ When I run `ruby rejects_negative_integers.rb` Then output should contain: """ : Contract violation for argument 2 of 2: (ParamContractError) Expected: Pos, Actual: -4 Value guarded in: Example::power With Contract: Pos, Pos => Pos """ And output should contain "pos_usage.rb:8" Scenario: Rejects negative floats Given a file named "rejects_negative_floats.rb" with: """ruby require "./pos_usage" puts Example.new.power(3.7, -4.4) """ When I run `ruby rejects_negative_floats.rb` Then output should contain: """ : Contract violation for argument 2 of 2: (ParamContractError) Expected: Pos, Actual: -4.4 Value guarded in: Example::power With Contract: Pos, Pos => Pos """ And output should contain "pos_usage.rb:8" Scenario: Rejects zero Given a file named "rejects_zero.rb" with: """ruby require "./pos_usage" puts Example.new.power(3, 0) """ When I run `ruby rejects_zero.rb` Then output should contain: """ : Contract violation for argument 2 of 2: (ParamContractError) Expected: Pos, Actual: 0 Value guarded in: Example::power With Contract: Pos, Pos => Pos """ And output should contain "pos_usage.rb:8" Scenario: Rejects other values Given a file named "rejects_others.rb" with: """ruby require "./pos_usage" puts Example.new.power("foo", 2) """ When I run `ruby rejects_others.rb` Then output should contain: """ : Contract violation for argument 1 of 2: (ParamContractError) Expected: Pos, Actual: "foo" Value guarded in: Example::power With Contract: Pos, Pos => Pos """ And output should contain "pos_usage.rb:8" contracts-0.16.0/features/builtin_contracts/any.feature0000644000004100000410000000142413120354472023340 0ustar www-datawww-dataFeature: Any Passes for any argument. ```ruby Contract C::Any => String ``` Scenario: Accepts any argument Given a file named "any_usage.rb" with: """ruby require "contracts" C = Contracts class Example include Contracts::Core Contract C::Any => String def self.stringify(x) x.inspect end end puts Example.stringify(25) puts Example.stringify(37.59) puts Example.stringify("foo") puts Example.stringify(:foo) puts Example.stringify(nil) puts Example.stringify(Object) """ When I run `ruby any_usage.rb` Then output should contain: """ 25 37.59 "foo" :foo nil Object """ And output should not contain: """ Contract violation for """ contracts-0.16.0/features/builtin_contracts/nat_pos.feature0000644000004100000410000000640513120354472024220 0ustar www-datawww-dataFeature: NatPos Checks that an argument is a positive natural number. ```ruby Contract C::NatPos => C::NatPos ``` Background: Given a file named "nat_pos_usage.rb" with: """ruby require "contracts" C = Contracts class NaturalPositive include Contracts::Core Contract C::NatPos => C::NatPos def prev(number) number - 1 end end """ Scenario: Accepts positive integers Given a file named "accepts_positive_integers.rb" with: """ruby require "./nat_pos_usage" puts NaturalPositive.new.prev(7) """ When I run `ruby accepts_positive_integers.rb` Then output should contain: """ 6 """ Scenario: Rejects zero Given a file named "rejects_zero.rb" with: """ruby require "./nat_pos_usage" puts NaturalPositive.new.prev(0) """ When I run `ruby rejects_zero.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: NatPos, Actual: 0 Value guarded in: NaturalPositive::prev With Contract: NatPos => NatPos """ Scenario: Rejects negative integers Given a file named "rejects_negative_integers.rb" with: """ruby require "./nat_pos_usage" puts NaturalPositive.new.prev(-1) """ When I run `ruby rejects_negative_integers.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: NatPos, Actual: -1 Value guarded in: NaturalPositive::prev With Contract: NatPos => NatPos """ And output should contain "nat_pos_usage.rb:8" Scenario: Rejects negative integers as a return value Given a file named "rejects_negative_integers.rb" with: """ruby require "./nat_pos_usage" puts NaturalPositive.new.prev(1) """ When I run `ruby rejects_negative_integers.rb` Then output should contain: """ : Contract violation for return value: (ReturnContractError) Expected: NatPos, Actual: 0 Value guarded in: NaturalPositive::prev With Contract: NatPos => NatPos """ And output should contain "nat_pos_usage.rb:8" Scenario: Rejects floats Given a file named "rejects_floats.rb" with: """ruby require "./nat_pos_usage" puts NaturalPositive.new.prev(3.43) """ When I run `ruby rejects_floats.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: NatPos, Actual: 3.43 Value guarded in: NaturalPositive::prev With Contract: NatPos => NatPos """ And output should contain "nat_pos_usage.rb:8" Scenario: Rejects other values Given a file named "rejects_others.rb" with: """ruby require "./nat_pos_usage" puts NaturalPositive.new.prev("foo") """ When I run `ruby rejects_others.rb` Then output should contain: """ : Contract violation for argument 1 of 1: (ParamContractError) Expected: NatPos, Actual: "foo" Value guarded in: NaturalPositive::prev With Contract: NatPos => NatPos """ And output should contain "nat_pos_usage.rb:8" contracts-0.16.0/features/builtin_contracts/set_of.feature0000644000004100000410000000002613120354472024025 0ustar www-datawww-dataFeature: SetOf (TODO) contracts-0.16.0/features/builtin_contracts/README.md0000644000004100000410000000114413120354472022452 0ustar www-datawww-dataTo use builtin contracts you can refer them with `Contracts::*`: ```ruby Contract Contracts::Num => Contracts::Maybe(Contracts::Num) ``` It is recommended to use a short alias for `Contracts`, for example `C`: ```ruby C = Contracts Contract C::Num => C::Maybe(C::Num) ``` It is possible to `include Contracts` and refer them without namespace, but this is deprecated and not recommended. *NOTE: in the future it will be possible to do `include Contracts::Builtin` instead.* *NOTE: all contracts marked as (TODO) have their documentaion `.feature` file as stub. Contributions to those are warmly welcome!* contracts-0.16.0/features/builtin_contracts/exactly.feature0000644000004100000410000000003013120354472024212 0ustar www-datawww-dataFeature: Exactly (TODO) contracts-0.16.0/features/support/0000755000004100000410000000000013120354472017161 5ustar www-datawww-datacontracts-0.16.0/features/support/env.rb0000644000004100000410000000022513120354472020275 0ustar www-datawww-datarequire "aruba/cucumber" require "aruba/jruby" if RUBY_PLATFORM == "java" Before do @aruba_timeout_seconds = RUBY_PLATFORM == "java" ? 30 : 5 end contracts-0.16.0/features/README.md0000644000004100000410000000100613120354472016721 0ustar www-datawww-datacontracts.ruby brings code contracts to the Ruby language. Example: ```ruby class Example include Contracts::Core C = Contracts Contract C::Num, C::Num => C::Num def add(a, b) a + b end end ``` This documentation is [open source](https://github.com/egonSchiele/contracts.ruby/tree/master/features). If you find it incomplete or confusing, please [submit an issue](https://github.com/egonSchiele/contracts.ruby/issues), or, better yet, [a pull request](https://github.com/egonSchiele/contracts.ruby). contracts-0.16.0/.rspec0000644000004100000410000000003613120354472014743 0ustar www-datawww-data--color --require spec_helper contracts-0.16.0/spec/0000755000004100000410000000000013120354472014561 5ustar www-datawww-datacontracts-0.16.0/spec/validators_spec.rb0000644000004100000410000000243313120354472020272 0ustar www-datawww-datarequire "spec_helper" RSpec.describe "Contract validators" do subject(:o) { GenericExample.new } describe "Range" do it "passes when value is in range" do expect do o.method_with_range_contract(5) end.not_to raise_error end it "fails when value is not in range" do expect do o.method_with_range_contract(300) end.to raise_error(ContractError, /Expected: 1\.\.10/) end it "fails when value is incorrect" do expect do o.method_with_range_contract("hello world") end.to raise_error(ContractError, /Expected: 1\.\.10.*Actual: "hello world"/m) end end describe "Regexp" do it "should pass for a matching string" do expect { o.should_contain_foo("containing foo") }.to_not raise_error end it "should fail for a non-matching string" do expect { o.should_contain_foo("that's not F00") }.to raise_error(ContractError) end describe "within a hash" do it "should pass for a matching string" do expect { o.hash_containing_foo(:host => "foo.example.org") }.to_not raise_error end end describe "within an array" do it "should pass for a matching string" do expect { o.array_containing_foo(["foo"]) }.to_not raise_error end end end end contracts-0.16.0/spec/support_spec.rb0000644000004100000410000000126113120354472017634 0ustar www-datawww-datamodule Contracts RSpec.describe Support do describe "eigenclass?" do it "is falsey for non-singleton classes" do expect(Contracts::Support.eigenclass? String).to be_falsey end it "is truthy for singleton classes" do singleton_class = String.instance_exec { class << self; self; end } expect(Contracts::Support.eigenclass? singleton_class).to be_truthy end end describe "eigenclass_of" do it "returns the eigenclass of a given object" do singleton_class = String.instance_exec { class << self; self; end } expect(Contracts::Support.eigenclass_of String).to eq singleton_class end end end end contracts-0.16.0/spec/module_spec.rb0000644000004100000410000000057313120354472017412 0ustar www-datawww-datamodule Mod include Contracts::Core Contract C::Num => C::Num def self.a_module_method a a + 1 end end RSpec.describe "module methods" do it "should pass for correct input" do expect { Mod.a_module_method(2) }.to_not raise_error end it "should fail for incorrect input" do expect { Mod.a_module_method("bad") }.to raise_error(ContractError) end end contracts-0.16.0/spec/support.rb0000644000004100000410000000032213120354472016617 0ustar www-datawww-datadef with_enabled_no_contracts no_contracts = ENV["NO_CONTRACTS"] ENV["NO_CONTRACTS"] = "true" yield ENV["NO_CONTRACTS"] = no_contracts end def ruby_version RUBY_VERSION.match(/\d+\.\d+/)[0].to_f end contracts-0.16.0/spec/spec_helper.rb0000644000004100000410000001070313120354472017400 0ustar www-datawww-datarequire "contracts" require File.expand_path(File.join(__FILE__, "../support")) require File.expand_path(File.join(__FILE__, "../fixtures/fixtures")) # This file was generated by the `rspec --init` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause this # file to always be loaded, without a need to explicitly require it in any files. # # Given that it is always loaded, you are encouraged to keep this file as # light-weight as possible. Requiring heavyweight dependencies from this file # will add to the boot time of your test suite on EVERY test run, even for an # individual file that may not need all of that loaded. Instead, consider making # a separate helper file that requires the additional dependencies and performs # the additional setup, and require it from the spec files that actually need it. # # The `.rspec` file also contains a few flags that are not defaults but that # users commonly want. # # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| config.pattern = "*.rb" # Only load tests who's syntax is valid in the current Ruby [1.9, 2.0, 2.1].each do |ver| config.pattern << ",ruby_version_specific/*#{ver}.rb" if ruby_version >= ver end # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest # assertions if you prefer. config.expect_with :rspec do |expectations| # This option will default to `true` in RSpec 4. It makes the `description` # and `failure_message` of custom matchers include text for helper methods # defined using `chain`, e.g.: # be_bigger_than(2).and_smaller_than(4).description # # => "be bigger than 2 and smaller than 4" # ...rather than: # # => "be bigger than 2" expectations.include_chain_clauses_in_custom_matcher_descriptions = true end # rspec-mocks config goes here. You can use an alternate test double # library (such as bogus or mocha) by changing the `mock_with` option here. config.mock_with :rspec do |mocks| # Prevents you from mocking or stubbing a method that does not exist on # a real object. This is generally recommended, and will default to # `true` in RSpec 4. mocks.verify_partial_doubles = true end # These two settings work together to allow you to limit a spec run # to individual examples or groups you care about by tagging them with # `:focus` metadata. When nothing is tagged with `:focus`, all examples # get run. config.filter_run :focus config.run_all_when_everything_filtered = true # Limits the available syntax to the non-monkey patched syntax that is recommended. # For more details, see: # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching config.disable_monkey_patching! # This setting enables warnings. It's recommended, but in some cases may # be too noisy due to issues in dependencies. # config.warnings = true # Many RSpec users commonly either run the entire suite or an individual # file, and it's useful to allow more verbose output when running an # individual spec file. if config.files_to_run.one? # Use the documentation formatter for detailed output, # unless a formatter has already been configured # (e.g. via a command-line flag). config.default_formatter = "doc" end # Print the 10 slowest examples and example groups at the # end of the spec run, to help surface which specs are running # particularly slow. config.profile_examples = 10 # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 # Unable to use it now config.order = :random # Seed global randomization in this process using the `--seed` CLI option. # Setting this allows you to use `--seed` to deterministically reproduce # test failures related to randomization by passing the same `--seed` value # as the one that triggered the failure. Kernel.srand config.seed # Callbacks config.after :each do ::Contract.restore_failure_callback end end contracts-0.16.0/spec/fixtures/0000755000004100000410000000000013120354472016432 5ustar www-datawww-datacontracts-0.16.0/spec/fixtures/fixtures.rb0000644000004100000410000002706113120354472020636 0ustar www-datawww-datarequire "date" C = Contracts class A include Contracts::Core Contract C::Num => C::Num def self.a_class_method x x + 1 end def good true end Contract C::Num => C::Num def triple x x * 3 end Contract C::Num => C::Num def instance_and_class_method x x * 2 end Contract String => String def self.instance_and_class_method x x * 2 end end class B include Contracts::Core def bad false end Contract String => String def triple x x * 3 end end class F include Contracts::Core def good false end def bad true end end class EmptyCont def self.to_s "" end end class GenericExample include Contracts::Core Contract C::Num => C::Num def self.a_class_method x x + 1 end Contract C::Num => nil def bad_double(x) x * 2 end Contract C::Num => C::Num def double(x) x * 2 end Contract 123, nil => nil def constanty(num, nul) 0 end Contract String => nil def hello(name) end Contract lambda { |x| x.is_a? Numeric } => C::Num def square(x) x ** 2 end Contract [C::Num, C::Num, C::Num] => C::Num def sum_three(vals) vals.inject(0) do |acc, x| acc + x end end Contract ({ :name => String, :age => Fixnum }) => nil def person(data) end Contract C::StrictHash[{ :name => String, :age => Fixnum }] => nil def strict_person(data) end Contract ({ :rigged => C::Or[TrueClass, FalseClass] }) => nil def hash_complex_contracts(data) end Contract ({ :rigged => C::Bool, :contents => { :kind => C::Or[String, Symbol], :total => C::Num } }) => nil def nested_hash_complex_contracts(data) end Contract C::KeywordArgs[:name => String, :age => Fixnum] => nil def person_keywordargs(data) end # Testing overloaded method Contract String, Fixnum => nil def person_keywordargs(name, age) end Contract C::KeywordArgs[:hash => C::HashOf[Symbol, C::Num]] => nil def hash_keywordargs(data) end Contract (/foo/) => nil def should_contain_foo(s) end Contract ({ :host => /foo/ }) => nil def hash_containing_foo(s) end Contract C::ArrayOf[/foo/] => nil def array_containing_foo(s) end Contract [C::Or[TrueClass, FalseClass]] => nil def array_complex_contracts(data) end Contract [C::Bool, [C::Or[String, Symbol]]] => nil def nested_array_complex_contracts(data) end Contract Proc => C::Any def do_call(&block) block.call end Contract C::Args[C::Num], C::Maybe[Proc] => C::Any def maybe_call(*vals, &block) block.call if block vals end Contract C::Args[C::Num] => C::Num def sum(*vals) vals.inject(0) do |acc, val| acc + val end end Contract C::Args[C::Num], Proc => C::Num def with_partial_sums(*vals, &blk) sum = vals.inject(0) do |acc, val| blk[acc] acc + val end blk[sum] end Contract C::Args[C::Num], C::Func[C::Num => C::Num] => C::Num def with_partial_sums_contracted(*vals, &blk) sum = vals.inject(0) do |acc, val| blk[acc] acc + val end blk[sum] end # Important to use different arg types or it falsely passes Contract C::Num, C::Args[String] => C::ArrayOf[String] def arg_then_splat(n, *vals) vals.map { |v| v * n } end Contract C::Num, Proc => nil def double_with_proc(x, &blk) blk.call(x * 2) nil end Contract C::Pos => nil def pos_test(x) end Contract C::Neg => nil def neg_test(x) end Contract C::Nat => nil def nat_test(x) end Contract C::Any => nil def show(x) end Contract C::None => nil def fail_all(x) end Contract C::Or[C::Num, String] => nil def num_or_string(x) end Contract C::Xor[C::RespondTo[:good], C::RespondTo[:bad]] => nil def xor_test(x) end Contract C::And[A, C::RespondTo[:good]] => nil def and_test(x) end Contract C::Enum[:a, :b, :c] => nil def enum_test(x) end Contract C::RespondTo[:good] => nil def responds_test(x) end Contract C::Send[:good] => nil def send_test(x) end Contract C::Not[nil] => nil def not_nil(x) end Contract C::ArrayOf[C::Num] => C::Num def product(vals) vals.inject(1) do |acc, x| acc * x end end Contract C::SetOf[C::Num] => C::Num def product_from_set(vals) vals.inject(1) do |acc, x| acc * x end end Contract C::RangeOf[C::Num] => C::Num def first_in_range_num(r) r.first end Contract C::RangeOf[Date] => Date def first_in_range_date(r) r.first end Contract C::DescendantOf[Enumerable] => nil def enumerable_descendant_test(enum) end Contract C::Bool => nil def bool_test(x) end Contract C::Num def no_args 1 end # This function has a contract which says it has no args, # but the function does have args. Contract nil => C::Num def old_style_no_args 2 end Contract C::ArrayOf[C::Num], C::Func[C::Num => C::Num] => C::ArrayOf[C::Num] def map(arr, func) ret = [] arr.each do |x| ret << func[x] end ret end Contract C::ArrayOf[C::Any], Proc => C::ArrayOf[C::Any] def tutorial_map(arr, func) ret = [] arr.each do |x| ret << func[x] end ret end # Need to test Func with weak contracts for other args # and changing type from input to output otherwise it falsely passes! Contract Array, C::Func[String => C::Num] => Array def map_plain(arr, func) arr.map do |x| func[x] end end Contract C::None => C::Func[String => C::Num] def lambda_with_wrong_return lambda { |x| x } end Contract C::None => C::Func[String => C::Num] def lambda_with_correct_return lambda { |x| x.length } end Contract C::Num => C::Num def default_args(x = 1) 2 end Contract C::Maybe[C::Num] => C::Maybe[C::Num] def maybe_double x if x.nil? nil else x * 2 end end Contract C::HashOf[Symbol, C::Num] => C::Num def gives_max_value(hash) hash.values.max end Contract C::HashOf[Symbol => C::Num] => C::Num def pretty_gives_max_value(hash) hash.values.max end Contract EmptyCont => C::Any def using_empty_contract(a) a end Contract (1..10) => nil def method_with_range_contract(x) end Contract String def a_private_method "works" end private :a_private_method Contract String def a_protected_method "works" end protected :a_protected_method private Contract String def a_really_private_method "works for sure" end protected Contract String def a_really_protected_method "works for sure" end end # for testing inheritance class Parent include Contracts::Core Contract C::Num => C::Num def double x x * 2 end end class Child < Parent end class GenericExample Contract Parent => Parent def id_ a a end Contract C::Exactly[Parent] => nil def exactly_test(x) end end # for testing equality class Foo end module Bar end Baz = 1 class GenericExample Contract C::Eq[Foo] => C::Any def eq_class_test(x) end Contract C::Eq[Bar] => C::Any def eq_module_test(x) end Contract C::Eq[Baz] => C::Any def eq_value_test(x) end end # pattern matching example with possible deep contract violation class PatternMatchingExample include Contracts::Core class Success attr_accessor :request def initialize request @request = request end def ==(other) request == other.request end end class Failure end Response = C::Or[Success, Failure] class StringWithHello def self.valid?(string) string.is_a?(String) && !!string.match(/hello/i) end end Contract Success => Response def process_request(status) Success.new(decorated_request(status.request)) end Contract Failure => Response def process_request(status) Failure.new end Contract StringWithHello => String def decorated_request(request) request + "!" end Contract C::Num, String => String def do_stuff(number, string) "foo" end Contract C::Num, String, C::Num => String def do_stuff(number, string, other_number) "bar" end Contract C::Num => C::Num def double x "bad" end Contract String => String def double x x * 2 end end # invariant example (silliest implementation ever) class MyBirthday include Contracts::Core include Contracts::Invariants invariant(:day) { 1 <= day && day <= 31 } invariant(:month) { 1 <= month && month <= 12 } attr_accessor :day, :month def initialize(day, month) @day = day @month = month end Contract C::None => Fixnum def silly_next_day! self.day += 1 end Contract C::None => Fixnum def silly_next_month! self.month += 1 end Contract C::None => Fixnum def clever_next_day! return clever_next_month! if day == 31 self.day += 1 end Contract C::None => Fixnum def clever_next_month! return next_year! if month == 12 self.month += 1 self.day = 1 end Contract C::None => Fixnum def next_year! self.month = 1 self.day = 1 end end class SingletonClassExample # This turned out to be required line here to make singleton classes # work properly under all platforms. Not sure if it worth trying to # do something with it. include Contracts::Core class << self Contract String => String def hoge(str) "super#{str}" end Contract C::Num, C::Num => C::Num def add(a, b) a + b end end end with_enabled_no_contracts do class NoContractsSimpleExample include Contracts::Core Contract String => nil def some_method(x) nil end end class NoContractsInvariantsExample include Contracts::Core include Contracts::Invariants attr_accessor :day invariant(:day_rule) { 1 <= day && day <= 7 } Contract C::None => nil def next_day self.day += 1 end end class NoContractsPatternMatchingExample include Contracts::Core Contract 200, String => String def on_response(status, body) body + "!" end Contract Fixnum, String => String def on_response(status, body) "error #{status}: #{body}" end end end module ModuleExample include Contracts::Core Contract C::Num, C::Num => C::Num def plus(a, b) a + b end Contract String => String def self.hoge(str) "super#{str}" end class << self Contract String => nil def eat(food) # yummy nil end end end class KlassWithModuleExample include ModuleExample end class SingletonInheritanceExample include Contracts::Core Contract C::Any => C::Any def self.a_contracted_self self end end class SingletonInheritanceExampleSubclass < SingletonInheritanceExample class << self Contract Integer => Integer def num(int) int end end end class BareOptionalContractUsed include Contracts::Core Contract C::Num, C::Optional[C::Num] => nil def something(a, b) nil end end module ModuleContractExample include Contracts::Core module AModule end module AnotherModule end module InheritedModule include AModule end class AClassWithModule include AModule end class AClassWithoutModule end class AClassWithAnotherModule include AnotherModule end class AClassWithInheritedModule include InheritedModule end class AClassWithBothModules include AModule include AnotherModule end Contract AModule => Symbol def self.hello(thing) :world end end module ModuleWithContracts def self.included(base) base.extend ClassMethods end module ClassMethods include Contracts::Core Contract C::None => String def foo "bar" end end end contracts-0.16.0/spec/override_validators_spec.rb0000644000004100000410000000752513120354472022200 0ustar www-datawww-dataRSpec.describe Contract do describe ".override_validator" do around do |example| Contract.reset_validators example.run Contract.reset_validators end it "allows to override simple validators" do Contract.override_validator(Hash) do |contract| lambda do |arg| return false unless arg.is_a?(Hash) # Any hash in my system should have :it_is_a_hash key! return false unless arg.key?(:it_is_a_hash) contract.keys.all? do |k| Contract.valid?(arg[k], contract[k]) end end end klass = Class.new do include Contracts::Core Contract ({ :a => Contracts::Num, :b => String }) => nil def something(opts) nil end end obj = klass.new expect do obj.something(:a => 35, :b => "hello") end.to raise_error(ContractError) expect do obj.something( :a => 35, :b => "hello", :it_is_a_hash => true ) end.not_to raise_error end it "allows to override valid contract" do Contract.override_validator(:valid) do |contract| if contract.respond_to?(:in_valid_state?) lambda do |arg| contract.in_valid_state? && contract.valid?(arg) end else lambda { |arg| contract.valid?(arg) } end end stateful_contract = Class.new(Contracts::CallableClass) do def initialize(contract) @contract = contract @state = 0 end def in_valid_state? @state < 3 end def valid?(arg) @state += 1 Contract.valid?(arg, @contract) end end klass = Class.new do include Contracts::Core Contract stateful_contract[Contracts::Num] => Contracts::Num def only_three_times(x) x * x end end obj = klass.new expect(obj.only_three_times(3)).to eq(9) expect(obj.only_three_times(3)).to eq(9) expect(obj.only_three_times(3)).to eq(9) expect do obj.only_three_times(3) end.to raise_error(ContractError) expect do obj.only_three_times(3) end.to raise_error(ContractError) end it "allows to override class validator" do # Make contracts accept all rspec doubles Contract.override_validator(:class) do |contract| lambda do |arg| arg.is_a?(RSpec::Mocks::Double) || arg.is_a?(contract) end end klass = Class.new do include Contracts::Core Contract String => String def greet(name) "hello, #{name}" end end obj = klass.new expect(obj.greet("world")).to eq("hello, world") expect do obj.greet(4) end.to raise_error(ContractError) expect(obj.greet(double("name"))).to match( /hello, #\[.*Double.*"name"\]/ ) end it "allows to override default validator" do spy = double("spy") Contract.override_validator(:default) do |contract| lambda do |arg| spy.log("#{arg} == #{contract}") arg == contract end end klass = Class.new do include Contracts::Core Contract 1, Contracts::Num => Contracts::Num def gcd(_, b) b end Contract Contracts::Num, Contracts::Num => Contracts::Num def gcd(a, b) gcd(b % a, a) end end obj = klass.new expect(spy).to receive(:log).with("8 == 1").ordered.once expect(spy).to receive(:log).with("5 == 1").ordered.once expect(spy).to receive(:log).with("3 == 1").ordered.once expect(spy).to receive(:log).with("2 == 1").ordered.once expect(spy).to receive(:log).with("1 == 1").ordered.once obj.gcd(8, 5) end end end contracts-0.16.0/spec/ruby_version_specific/0000755000004100000410000000000013120354472021154 5ustar www-datawww-datacontracts-0.16.0/spec/ruby_version_specific/contracts_spec_2.1.rb0000644000004100000410000000406013120354472025073 0ustar www-datawww-dataclass GenericExample Contract String, C::Bool, C::Args[Symbol], Float, C::KeywordArgs[e: Range, f: C::Optional[C::Num], g: Symbol], Proc => [Proc, Hash, C::Maybe[C::Num], Range, Float, C::ArrayOf[Symbol], C::Bool, String] def complicated(a, b = true, *c, d, e:, f:2, **g, &h) h.call [h, g, f, e, d, c, b, a] end end RSpec.describe "Contracts:" do before :all do @o = GenericExample.new end describe "Required named arguments" do describe "really complicated method signature" do it "should work with default named args used" do expect do @o.complicated("a", false, :b, 2.0, e: (1..5), g: :d) { |x| x } end.to_not raise_error end it "should work with all args filled manually, with extra splat and hash" do expect do @o.complicated("a", true, :b, :c, 2.0, e: (1..5), f: 8.3, g: :d) do |x| x end end.to_not raise_error end it "should fail when the return is invalid" do expect do @o.complicated("a", true, :b, 2.0, e: (1..5)) { |_x| "bad" } end.to raise_error(ContractError) end it "should fail when args are invalid" do expect do @o.complicated("a", "bad", :b, 2.0, e: (1..5)) { |x| x } end.to raise_error(ContractError) end it "should fail when splat is invalid" do expect do @o.complicated("a", true, "bad", 2.0, e: (1..5)) { |x| x } end.to raise_error(ContractError) end it "should fail when named argument is invalid" do expect do @o.complicated("a", true, :b, 2.0, e: "bad") { |x| x } end.to raise_error(ContractError) end it "should fail when passed nil to an optional argument which contract shouldnt accept nil" do expect do @o.complicated("a", true, :b, :c, 2.0, e: (1..5), f: nil, g: :d) do |x| x end end.to raise_error(ContractError, /Expected: \(KeywordArgs\[{:e=>Range, :f=>Optional\[Num\], :g=>Symbol}\]\)/) end end end end contracts-0.16.0/spec/ruby_version_specific/contracts_spec_2.0.rb0000644000004100000410000000331413120354472025073 0ustar www-datawww-dataclass GenericExample Contract C::Args[String], { repeat: C::Maybe[C::Num] } => C::ArrayOf[String] def splat_then_optional_named(*vals, repeat: 2) vals.map { |v| v * repeat } end Contract ({foo: C::Nat}) => nil def nat_test_with_kwarg(foo: 10) end Contract C::KeywordArgs[name: C::Optional[String]], C::Func[String => String] => String def keyword_args_hello(name: "Adit", &block) "Hey, #{yield name}!" end end RSpec.describe "Contracts:" do before :all do @o = GenericExample.new end describe "Optional named arguments" do it "should work with optional named argument unfilled after splat" do expect { @o.splat_then_optional_named("hello", "world") }.to_not raise_error end it "should work with optional named argument filled after splat" do expect { @o.splat_then_optional_named("hello", "world", repeat: 3) }.to_not raise_error end end describe "Nat:" do it "should pass for keyword args with correct arg given" do expect { @o.nat_test_with_kwarg(foo: 10) }.to_not raise_error end it "should fail with a ContractError for wrong keyword args input" do expect { @o.nat_test_with_kwarg(foo: -10) }.to raise_error(ContractError) end it "should fail with a ContractError for no input" do expect { @o.nat_test_with_kwarg }.to raise_error(ContractError) end end describe "keyword args with defaults, with a block" do it "should work when both keyword args and a block is given" do expect(@o.keyword_args_hello(name: "maggie", &:upcase)).to eq("Hey, MAGGIE!") end it "should work even when keyword args aren't given" do expect(@o.keyword_args_hello(&:upcase)).to eq("Hey, ADIT!") end end end contracts-0.16.0/spec/ruby_version_specific/contracts_spec_1.9.rb0000644000004100000410000000103413120354472025100 0ustar www-datawww-dataclass GenericExample Contract C::Args[String], C::Num => C::ArrayOf[String] def splat_then_arg(*vals, n) vals.map { |v| v * n } end if ruby_version <= 1.9 Contract ({:foo => C::Nat}) => nil def nat_test_with_kwarg(a_hash) end end end RSpec.describe "Contracts:" do before :all do @o = GenericExample.new end describe "Splat not last (or penultimate to block)" do it "should work with arg after splat" do expect { @o.splat_then_arg("hello", "world", 3) }.to_not raise_error end end end contracts-0.16.0/spec/contracts_spec.rb0000644000004100000410000005301113120354472020120 0ustar www-datawww-dataRSpec.describe "Contracts:" do before :all do @o = GenericExample.new end describe "basic" do it "should fail for insufficient arguments" do expect do @o.hello end.to raise_error end it "should fail for insufficient contracts" do expect { @o.bad_double(2) }.to raise_error(ContractError) end end describe "contracts for functions with no arguments" do it "should work for functions with no args" do expect { @o.no_args }.to_not raise_error end it "should still work for old-style contracts for functions with no args" do expect { @o.old_style_no_args }.to_not raise_error end it "should not work for a function with a bad contract" do expect do Class.new(GenericExample) do Contract Num, Num def no_args_bad_contract 1 end end end.to raise_error end end describe "pattern matching" do let(:string_with_hello) { "Hello, world" } let(:string_without_hello) { "Hi, world" } let(:expected_decorated_string) { "Hello, world!" } subject { PatternMatchingExample.new } it "should work as expected when there is no contract violation" do expect( subject.process_request(PatternMatchingExample::Success.new(string_with_hello)) ).to eq(PatternMatchingExample::Success.new(expected_decorated_string)) expect( subject.process_request(PatternMatchingExample::Failure.new) ).to be_a(PatternMatchingExample::Failure) end it "should not fall through to next pattern when there is a deep contract violation" do expect(PatternMatchingExample::Failure).not_to receive(:is_a?) expect do subject.process_request(PatternMatchingExample::Success.new(string_without_hello)) end.to raise_error(ContractError) end it "should fail when the pattern-matched method's contract fails" do expect do subject.process_request("bad input") end.to raise_error(ContractError) end it "should work for differing arities" do expect( subject.do_stuff(1, "abc", 2) ).to eq("bar") expect( subject.do_stuff(3, "def") ).to eq("foo") end it "if the return contract for a pattern match fails, it should fail instead of trying the next pattern match" do expect do subject.double(1) end.to raise_error(ContractError) end it "should fail if multiple methods are defined with the same contract (for pattern-matching)" do expect do Class.new(GenericExample) do Contract Contracts::Num => Contracts::Num def same_param_contract x x + 2 end Contract Contracts::Num => String def same_param_contract x "sdf" end end end.to raise_error(ContractError) end context "when failure_callback was overriden" do before do ::Contract.override_failure_callback do |_data| fail "contract violation" end end it "calls a method when first pattern matches" do expect( subject.process_request(PatternMatchingExample::Success.new(string_with_hello)) ).to eq(PatternMatchingExample::Success.new(expected_decorated_string)) end it "falls through to 2nd pattern when first pattern does not match" do expect( subject.process_request(PatternMatchingExample::Failure.new) ).to be_a(PatternMatchingExample::Failure) end it "if the return contract for a pattern match fails, it should fail instead of trying the next pattern match, even with the failure callback" do expect do subject.double(1) end.to raise_error(ContractError) end it "uses overriden failure_callback when pattern matching fails" do expect do subject.process_request("hello") end.to raise_error(RuntimeError, /contract violation/) end end end describe "usage in singleton class" do it "should work normally when there is no contract violation" do expect(SingletonClassExample.hoge("hoge")).to eq("superhoge") end it "should fail with proper error when there is contract violation" do expect do SingletonClassExample.hoge(3) end.to raise_error(ContractError, /Expected: String/) end describe "builtin contracts usage" do it "allows to use builtin contracts without namespacing and redundant Contracts inclusion" do expect do SingletonClassExample.add("55", 5.6) end.to raise_error(ContractError, /Expected: Num/) end end end describe "usage in the singleton class of a subclass" do subject { SingletonInheritanceExampleSubclass } it "should work with a valid contract on a singleton method" do expect(subject.num(1)).to eq(1) end end describe "no contracts feature" do it "disables normal contract checks" do object = NoContractsSimpleExample.new expect { object.some_method(3) }.not_to raise_error end it "disables invariants" do object = NoContractsInvariantsExample.new object.day = 7 expect { object.next_day }.not_to raise_error end it "does not disable pattern matching" do object = NoContractsPatternMatchingExample.new expect(object.on_response(200, "hello")).to eq("hello!") expect(object.on_response(404, "Not found")).to eq("error 404: Not found") expect { object.on_response(nil, "junk response") }.to raise_error(ContractError) end end describe "module usage" do context "with instance methods" do it "should check contract" do expect { KlassWithModuleExample.new.plus(3, nil) }.to raise_error(ContractError) end end context "with singleton methods" do it "should check contract" do expect { ModuleExample.hoge(nil) }.to raise_error(ContractError) end end context "with singleton class methods" do it "should check contract" do expect { ModuleExample.eat(:food) }.to raise_error(ContractError) end end end describe "singleton methods self in inherited methods" do it "should be a proper self" do expect(SingletonInheritanceExampleSubclass.a_contracted_self).to eq(SingletonInheritanceExampleSubclass) end end describe "anonymous classes" do let(:klass) do Class.new do include Contracts::Core Contract String => String def greeting(name) "hello, #{name}" end end end let(:obj) { klass.new } it "does not fail when contract is satisfied" do expect(obj.greeting("world")).to eq("hello, world") end it "fails with error when contract is violated" do expect { obj.greeting(3) }.to raise_error(ContractError, /Actual: 3/) end end describe "anonymous modules" do let(:mod) do Module.new do include Contracts::Core Contract String => String def greeting(name) "hello, #{name}" end Contract String => String def self.greeting(name) "hello, #{name}" end end end let(:klass) do Class.new.tap { |klass| klass.send(:include, mod) } end let(:obj) { klass.new } it "does not fail when contract is satisfied" do expect(obj.greeting("world")).to eq("hello, world") end it "fails with error when contract is violated" do expect { obj.greeting(3) }.to raise_error(ContractError, /Actual: 3/) end context "when called on module itself" do let(:obj) { mod } it "does not fail when contract is satisfied" do expect(obj.greeting("world")).to eq("hello, world") end it "fails with error when contract is violated" do expect { obj.greeting(3) }.to raise_error(ContractError, /Actual: 3/) end end end describe "instance methods" do it "should allow two classes to have the same method with different contracts" do a = A.new b = B.new expect do a.triple(5) b.triple("a string") end.to_not raise_error end end describe "instance and class methods" do it "should allow a class to have an instance method and a class method with the same name" do a = A.new expect do a.instance_and_class_method(5) A.instance_and_class_method("a string") end.to_not raise_error end end describe "class methods" do it "should pass for correct input" do expect { GenericExample.a_class_method(2) }.to_not raise_error end it "should fail for incorrect input" do expect { GenericExample.a_class_method("bad") }.to raise_error(ContractError) end end describe "classes" do it "should pass for correct input" do expect { @o.hello("calvin") }.to_not raise_error end it "should fail for incorrect input" do expect { @o.hello(1) }.to raise_error(ContractError) end end describe "classes with a valid? class method" do it "should pass for correct input" do expect { @o.double(2) }.to_not raise_error end it "should fail for incorrect input" do expect { @o.double("bad") }.to raise_error(ContractError) end end describe "Procs" do it "should pass for correct input" do expect { @o.square(2) }.to_not raise_error end it "should fail for incorrect input" do expect { @o.square("bad") }.to raise_error(ContractError) end end describe "Arrays" do it "should pass for correct input" do expect { @o.sum_three([1, 2, 3]) }.to_not raise_error end it "should fail for insufficient items" do expect { @o.square([1, 2]) }.to raise_error(ContractError) end it "should fail for some incorrect elements" do expect { @o.sum_three([1, 2, "three"]) }.to raise_error(ContractError) end end describe "Hashes" do it "should pass for exact correct input" do expect { @o.person(:name => "calvin", :age => 10) }.to_not raise_error end it "should pass even if some keys don't have contracts" do expect { @o.person(:name => "calvin", :age => 10, :foo => "bar") }.to_not raise_error end it "should fail if a key with a contract on it isn't provided" do expect { @o.person(:name => "calvin") }.to raise_error(ContractError) end it "should fail for incorrect input" do expect { @o.person(:name => 50, :age => 10) }.to raise_error(ContractError) end end describe "blocks" do it "should pass for correct input" do expect do @o.do_call do 2 + 2 end end.to_not raise_error end it "should fail for incorrect input" do expect do @o.do_call(nil) end.to raise_error(ContractError) end it "should handle properly lack of block when there are other arguments" do expect do @o.double_with_proc(4) end.to raise_error(ContractError, /Actual: nil/) end it "should succeed for maybe proc with no proc" do expect do @o.maybe_call(5) end.to_not raise_error end it "should succeed for maybe proc with proc" do expect do @o.maybe_call(5) do 2 + 2 end end.to_not raise_error end it "should fail for maybe proc with invalid input" do expect do @o.maybe_call("bad") end.to raise_error(ContractError) end describe "varargs are given with a maybe block" do it "when a block is passed in, varargs should be correct" do expect(@o.maybe_call(1, 2, 3) { 1 + 1 }).to eq([1, 2, 3]) end it "when a block is NOT passed in, varargs should still be correct" do expect(@o.maybe_call(1, 2, 3)).to eq([1, 2, 3]) end end end describe "varargs" do it "should pass for correct input" do expect do @o.sum(1, 2, 3) end.to_not raise_error end it "should fail for incorrect input" do expect do @o.sum(1, 2, "bad") end.to raise_error(ContractError) end it "should work with arg before splat" do expect do @o.arg_then_splat(3, "hello", "world") end.to_not raise_error end end describe "varargs with block" do it "should pass for correct input" do expect do @o.with_partial_sums(1, 2, 3) do |partial_sum| 2 * partial_sum + 1 end end.not_to raise_error expect do @o.with_partial_sums_contracted(1, 2, 3) do |partial_sum| 2 * partial_sum + 1 end end.not_to raise_error end it "should fail for incorrect input" do expect do @o.with_partial_sums(1, 2, "bad") do |partial_sum| 2 * partial_sum + 1 end end.to raise_error(ContractError, /Actual: "bad"/) expect do @o.with_partial_sums(1, 2, 3) end.to raise_error(ContractError, /Actual: nil/) expect do @o.with_partial_sums(1, 2, 3, lambda { |x| x }) end.to raise_error(ContractError, /Actual: nil/) end context "when block has Func contract" do it "should fail for incorrect input" do expect do @o.with_partial_sums_contracted(1, 2, "bad") { |partial_sum| 2 * partial_sum + 1 } end.to raise_error(ContractError, /Actual: "bad"/) expect do @o.with_partial_sums_contracted(1, 2, 3) end.to raise_error(ContractError, /Actual: nil/) end end end describe "contracts on functions" do it "should pass for a function that passes the contract" do expect { @o.map([1, 2, 3], lambda { |x| x + 1 }) }.to_not raise_error end it "should pass for a function that passes the contract as in tutorial" do expect { @o.tutorial_map([1, 2, 3], lambda { |x| x + 1 }) }.to_not raise_error end it "should fail for a function that doesn't pass the contract" do expect { @o.map([1, 2, 3], lambda { |_| "bad return value" }) }.to raise_error(ContractError) end it "should pass for a function that passes the contract with weak other args" do expect { @o.map_plain(["hello", "joe"], lambda { |x| x.size }) }.to_not raise_error end it "should fail for a function that doesn't pass the contract with weak other args" do expect { @o.map_plain(["hello", "joe"], lambda { |_| nil }) }.to raise_error(ContractError) end it "should fail for a returned function that doesn't pass the contract" do expect { @o.lambda_with_wrong_return.call("hello") }.to raise_error(ContractError) end it "should fail for a returned function that receives the wrong argument type" do expect { @o.lambda_with_correct_return.call(123) }.to raise_error(ContractError) end it "should not fail for a returned function that passes the contract" do expect { @o.lambda_with_correct_return.call("hello") }.to_not raise_error end end describe "default args to functions" do it "should work for a function call that relies on default args" do expect { @o.default_args }.to_not raise_error expect { @o.default_args("foo") }.to raise_error(ContractError) end end describe "classes" do it "should not fail for an object that is the exact type as the contract" do p = Parent.new expect { @o.id_(p) }.to_not raise_error end it "should not fail for an object that is a subclass of the type in the contract" do c = Child.new expect { @o.id_(c) }.to_not raise_error end end describe "failure callbacks" do before :each do ::Contract.override_failure_callback do |_data| should_call end end context "when failure_callback returns false" do let(:should_call) { false } it "does not call a function for which the contract fails" do res = @o.double("bad") expect(res).to eq(nil) end end context "when failure_callback returns true" do let(:should_call) { true } it "calls a function for which the contract fails" do res = @o.double("bad") expect(res).to eq("badbad") end end end describe "module contracts" do it "passes for instance of class including module" do expect( ModuleContractExample.hello(ModuleContractExample::AClassWithModule.new) ).to eq(:world) end it "passes for instance of class including inherited module" do expect( ModuleContractExample.hello(ModuleContractExample::AClassWithInheritedModule.new) ).to eq(:world) end it "does not pass for instance of class not including module" do expect do ModuleContractExample.hello(ModuleContractExample::AClassWithoutModule.new) end.to raise_error(ContractError, /Expected: ModuleContractExample::AModule/) end it "does not pass for instance of class including another module" do expect do ModuleContractExample.hello(ModuleContractExample::AClassWithAnotherModule.new) end.to raise_error(ContractError, /Expected: ModuleContractExample::AModule/) end it "passes for instance of class including both modules" do expect( ModuleContractExample.hello(ModuleContractExample::AClassWithBothModules.new) ).to eq(:world) end end describe "Contracts to_s formatting in expected" do def not_s(match) Regexp.new "[^\"\']#{match}[^\"\']" end def delim(match) "(#{match})" end it "should not stringify native types" do expect do @o.constanty("bad", nil) end.to raise_error(ContractError, not_s(123)) expect do @o.constanty(123, "bad") end.to raise_error(ContractError, not_s(nil)) end it "should contain to_s representation within a Hash contract" do expect do @o.hash_complex_contracts(:rigged => "bad") end.to raise_error(ContractError, not_s(delim "TrueClass or FalseClass")) end it "should contain to_s representation within a nested Hash contract" do expect do @o.nested_hash_complex_contracts(:rigged => true, :contents => { :kind => 0, :total => 42 }) end.to raise_error(ContractError, not_s(delim "String or Symbol")) end it "should contain to_s representation within an Array contract" do expect do @o.array_complex_contracts(["bad"]) end.to raise_error(ContractError, not_s(delim "TrueClass or FalseClass")) end it "should contain to_s representation within a nested Array contract" do expect do @o.nested_array_complex_contracts([true, [0]]) end.to raise_error(ContractError, not_s(delim "String or Symbol")) end it "should not contain Contracts:: module prefix" do expect do @o.double("bad") end.to raise_error(ContractError, /Expected: Num/) end it "should still show nils, not just blank space" do expect do @o.no_args("bad") end.to raise_error(ContractError, /Expected: nil/) end it 'should show empty quotes as ""' do expect do @o.no_args("") end.to raise_error(ContractError, /Actual: ""/) end it "should not use custom to_s if empty string" do expect do @o.using_empty_contract("bad") end.to raise_error(ContractError, /Expected: EmptyCont/) end end describe "functype" do it "should correctly print out a instance method's type" do expect(@o.functype(:double)).not_to eq("") end it "should correctly print out a class method's type" do expect(A.functype(:a_class_method)).not_to eq("") end end describe "private methods" do it "should raise an error if you try to access a private method" do expect { @o.a_private_method }.to raise_error(NoMethodError, /private/) end it "should raise an error if you try to access a private method" do expect { @o.a_really_private_method }.to raise_error(NoMethodError, /private/) end end describe "protected methods" do it "should raise an error if you try to access a protected method" do expect { @o.a_protected_method }.to raise_error(NoMethodError, /protected/) end it "should raise an error if you try to access a protected method" do expect { @o.a_really_protected_method }.to raise_error(NoMethodError, /protected/) end end describe "inherited methods" do it "should apply the contract to an inherited method" do c = Child.new expect { c.double(2) }.to_not raise_error expect { c.double("asd") }.to raise_error end end describe "classes with extended modules" do let(:klass) do m = Module.new do include Contracts::Core end Class.new do include Contracts::Core extend m Contract String => nil def foo(x) end end end it "is possible to define it" do expect { klass }.not_to raise_error end it "works correctly with methods with passing contracts" do expect { klass.new.foo("bar") }.not_to raise_error end it "works correctly with methods with passing contracts" do expect { klass.new.foo(42) }.to raise_error(ContractError, /Expected: String/) end # See the discussion on this issue: # https://github.com/egonSchiele/contracts.ruby/issues/229 it "should not fail with 'undefined method 'Contract''" do expect do class ModuleThenContracts include ModuleWithContracts include Contracts::Core # fails on this line Contract C::Num => C::Num def double(x) x * 2 end end end.to_not raise_error end end end contracts-0.16.0/spec/methods_spec.rb0000644000004100000410000000236413120354472017570 0ustar www-datawww-dataRSpec.describe "Contracts:" do describe "method called with blocks" do module FuncTest include Contracts::Core include Contracts::Builtin Contract Func[Num=>Num] => nil def foo(&blk) _ = blk.call(2) nil end Contract Num, Func[Num=>Num] => nil def foo2(a, &blk) _ = blk.call(2) nil end Contract Func[Num=>Num] => nil def bar(blk) _ = blk.call(2) nil end Contract Num, Func[Num=>Num] => nil def bar2(a, blk) _ = blk.call(2) nil end end def obj Object.new.tap do |o| o.extend(FuncTest) end end it "should enforce return value inside block with no other parameter" do expect { obj.foo(&:to_s) }.to raise_error end it "should enforce return value inside block with other parameter" do expect { obj.foo2(2) { |x| x.to_s } }.to raise_error end it "should enforce return value inside lambda with no other parameter" do expect { obj.bar lambda { |x| x.to_s } }.to raise_error end it "should enforce return value inside lambda with other parameter" do expect { obj.bar2(2, lambda { |x| x.to_s }) }.to raise_error end end end contracts-0.16.0/spec/invariants_spec.rb0000644000004100000410000000114313120354472020275 0ustar www-datawww-datamodule Contracts RSpec.describe Invariants do def new_subject MyBirthday.new(31, 12) end it "works when all invariants are holding" do expect { new_subject.clever_next_day! }.not_to raise_error expect { new_subject.clever_next_month! }.not_to raise_error end it "raises invariant violation error when any of invariants are not holding" do expect { new_subject.silly_next_day! }.to raise_error(InvariantError, /day condition to be true/) expect { new_subject.silly_next_month! }.to raise_error(InvariantError, /month condition to be true/) end end end contracts-0.16.0/spec/attrs_spec.rb0000644000004100000410000000351413120354472017260 0ustar www-datawww-dataRSpec.describe "Contracts:" do describe "Attrs:" do class Person include Contracts::Core include Contracts::Attrs include Contracts::Builtin def initialize(name) @name_r = name @name_w = name @name_rw = name end attr_reader_with_contract :name_r, String attr_writer_with_contract :name_w, String attr_accessor_with_contract :name_rw, String end context "attr_reader_with_contract" do it "getting valid type" do expect(Person.new("bob").name_r) .to(eq("bob")) end it "getting invalid type" do expect { Person.new(1.3).name_r } .to(raise_error(ReturnContractError)) end it "setting" do expect { Person.new("bob").name_r = "alice" } .to(raise_error(NoMethodError)) end end context "attr_writer_with_contract" do it "getting" do expect { Person.new("bob").name_w } .to(raise_error(NoMethodError)) end it "setting valid type" do expect(Person.new("bob").name_w = "alice") .to(eq("alice")) end it "setting invalid type" do expect { Person.new("bob").name_w = 1.2 } .to(raise_error(ParamContractError)) end end context "attr_accessor_with_contract" do it "getting valid type" do expect(Person.new("bob").name_rw) .to(eq("bob")) end it "getting invalid type" do expect { Person.new(1.2).name_rw } .to(raise_error(ReturnContractError)) end it "setting valid type" do expect(Person.new("bob").name_rw = "alice") .to(eq("alice")) end it "setting invalid type" do expect { Person.new("bob").name_rw = 1.2 } .to(raise_error(ParamContractError)) end end end end contracts-0.16.0/spec/builtin_contracts_spec.rb0000644000004100000410000002676713120354472021670 0ustar www-datawww-dataRSpec.describe "Contracts:" do before :all do @o = GenericExample.new end def fails(&some) expect { some.call }.to raise_error(ContractError) end def passes(&some) expect { some.call }.to_not raise_error end describe "DescendantOf:" do it "should pass for Array" do passes { @o.enumerable_descendant_test(Array) } end it "should pass for a hash" do passes { @o.enumerable_descendant_test(Hash) } end it "should fail for a number class" do fails { @o.enumerable_descendant_test(Integer) } end it "should fail for a non-class" do fails { @o.enumerable_descendant_test(1) } end end describe "Num:" do it "should pass for Fixnums" do passes { @o.double(2) } end it "should pass for Floats" do passes { @o.double(2.2) } end it "should fail for nil and other data types" do fails { @o.double(nil) } fails { @o.double(:x) } fails { @o.double("x") } fails { @o.double(/x/) } end end describe "Pos:" do it "should pass for positive numbers" do passes { @o.pos_test(1) } passes { @o.pos_test(1.6) } end it "should fail for 0" do fails { @o.pos_test(0) } end it "should fail for negative numbers" do fails { @o.pos_test(-1) } fails { @o.pos_test(-1.6) } end it "should fail for nil and other data types" do fails { @o.pos_test(nil) } fails { @o.pos_test(:x) } fails { @o.pos_test("x") } fails { @o.pos_test(/x/) } end end describe "Neg:" do it "should pass for negative numbers" do passes { @o.neg_test(-1) } passes { @o.neg_test(-1.6) } end it "should fail for 0" do fails { @o.neg_test(0) } end it "should fail for positive numbers" do fails { @o.neg_test(1) } fails { @o.neg_test(1.6) } end it "should fail for nil and other data types" do fails { @o.neg_test(nil) } fails { @o.neg_test(:x) } fails { @o.neg_test("x") } fails { @o.neg_test(/x/) } end end describe "Nat:" do it "should pass for 0" do passes { @o.nat_test(0) } end it "should pass for positive whole numbers" do passes { @o.nat_test(1) } end it "should fail for positive non-whole numbers" do fails { @o.nat_test(1.5) } end it "should fail for negative numbers" do fails { @o.nat_test(-1) } fails { @o.nat_test(-1.6) } end it "should fail for nil and other data types" do fails { @o.nat_test(nil) } fails { @o.nat_test(:x) } fails { @o.nat_test("x") } fails { @o.nat_test(/x/) } end end describe "Any:" do it "should pass for numbers" do passes { @o.show(1) } end it "should pass for strings" do passes { @o.show("bad") } end it "should pass for procs" do passes { @o.show(lambda {}) } end it "should pass for nil" do passes { @o.show(nil) } end end describe "None:" do it "should fail for numbers" do fails { @o.fail_all(1) } end it "should fail for strings" do fails { @o.fail_all("bad") } end it "should fail for procs" do fails { @o.fail_all(lambda {}) } end it "should fail for nil" do fails { @o.fail_all(nil) } end end describe "Or:" do it "should pass for nums" do passes { @o.num_or_string(1) } end it "should pass for strings" do passes { @o.num_or_string("bad") } end it "should fail for nil" do fails { @o.num_or_string(nil) } end end describe "Xor:" do it "should pass for an object with a method :good" do passes { @o.xor_test(A.new) } end it "should pass for an object with a method :bad" do passes { @o.xor_test(B.new) } end it "should fail for an object with neither method" do fails { @o.xor_test(1) } end it "should fail for an object with both methods :good and :bad" do fails { @o.xor_test(F.new) } end end describe "And:" do it "should pass for an object of class A that has a method :good" do passes { @o.and_test(A.new) } end it "should fail for an object that has a method :good but isn't of class A" do fails { @o.and_test(F.new) } end end describe "Enum:" do it "should pass for an object that is included" do passes { @o.enum_test(:a) } end it "should fail for an object that is not included" do fails { @o.enum_test(:z) } end end describe "RespondTo:" do it "should pass for an object that responds to :good" do passes { @o.responds_test(A.new) } end it "should fail for an object that doesn't respond to :good" do fails { @o.responds_test(B.new) } end end describe "Send:" do it "should pass for an object that returns true for method :good" do passes { @o.send_test(A.new) } end it "should fail for an object that returns false for method :good" do fails { @o.send_test(F.new) } end end describe "Exactly:" do it "should pass for an object that is exactly a Parent" do passes { @o.exactly_test(Parent.new) } end it "should fail for an object that inherits from Parent" do fails { @o.exactly_test(Child.new) } end it "should fail for an object that is not related to Parent at all" do fails { @o.exactly_test(A.new) } end end describe "Eq:" do it "should pass for a class" do passes { @o.eq_class_test(Foo) } end it "should pass for a module" do passes { @o.eq_module_test(Bar) } end it "should pass for other values" do passes { @o.eq_value_test(Baz) } end it "should fail when not equal" do fails { @o.eq_class_test(Bar) } end it "should fail when given instance of class" do fails { @o.eq_class_test(Foo.new) } end end describe "Not:" do it "should pass for an argument that isn't nil" do passes { @o.not_nil(1) } end it "should fail for nil" do fails { @o.not_nil(nil) } end end describe "ArrayOf:" do it "should pass for an array of nums" do passes { @o.product([1, 2, 3]) } end it "should fail for an array with one non-num" do fails { @o.product([1, 2, 3, "bad"]) } end it "should fail for a non-array" do fails { @o.product(1) } end end describe "RangeOf:" do require "date" it "should pass for a range of nums" do passes { @o.first_in_range_num(3..10) } end it "should pass for a range of dates" do d1 = Date.today d2 = d1 + 18 passes { @o.first_in_range_date(d1..d2) } end it "should fail for a non-range" do fails { @o.first_in_range_num("foo") } fails { @o.first_in_range_num(:foo) } fails { @o.first_in_range_num(5) } fails { @o.first_in_range_num(nil) } end it "should fail for a range with incorrect data type" do fails { @o.first_in_range_num("a".."z") } end it "should fail for a badly-defined range" do # For some reason, Ruby 2.0.0 allows (date .. number) as a range. # Perhaps other Ruby versions do too. # Note that (date .. string) gives ArgumentError. # This test guards against ranges with inconsistent data types. begin d1 = Date.today fails { @o.first_in_range_date(d1..10) } fails { @o.first_in_range_num(d1..10) } rescue ArgumentError # If Ruby doesn't like the range, we ignore the test. :nop end end end describe "SetOf:" do it "should pass for a set of nums" do passes { @o.product_from_set(Set.new([1, 2, 3])) } end it "should fail for an array with one non-num" do fails { @o.product_from_set(Set.new([1, 2, 3, "bad"])) } end it "should fail for a non-array" do fails { @o.product_from_set(1) } end end describe "Bool:" do it "should pass for an argument that is a boolean" do passes { @o.bool_test(true) } passes { @o.bool_test(false) } end it "should fail for nil" do fails { @o.bool_test(nil) } end end describe "Maybe:" do it "should pass for nums" do expect(@o.maybe_double(1)).to eq(2) end it "should pass for nils" do expect(@o.maybe_double(nil)).to eq(nil) end it "should fail for strings" do fails { @o.maybe_double("foo") } end end describe "KeywordArgs:" do it "should pass for exact correct input" do passes { @o.person_keywordargs(:name => "calvin", :age => 10) } end it "should fail if some keys don't have contracts" do fails { @o.person_keywordargs(:name => "calvin", :age => 10, :foo => "bar") } end it "should fail if a key with a contract on it isn't provided" do fails { @o.person_keywordargs(:name => "calvin") } end it "should fail for incorrect input" do fails { @o.person_keywordargs(:name => 50, :age => 10) } fails { @o.hash_keywordargs(:hash => nil) } fails { @o.hash_keywordargs(:hash => 1) } end it "should pass if a method is overloaded with non-KeywordArgs" do passes { @o.person_keywordargs("name", 10) } end end describe "Optional:" do it "can't be used outside of KeywordArgs" do expect do BareOptionalContractUsed.new.something(3, 5) end.to raise_error(ArgumentError, Contracts::Optional::UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH) end end describe "HashOf:" do it "doesn't allow to specify multiple key-value pairs with pretty syntax" do expect do Class.new do include Contracts::Core Contract Contracts::HashOf[Symbol => String, Contracts::Num => Contracts::Num] => nil def something(hash) # ... end end end.to raise_error(ArgumentError, "You should provide only one key-value pair to HashOf contract") end context "given a fulfilled contract" do it { expect(@o.gives_max_value(:panda => 1, :bamboo => 2)).to eq(2) } it { expect(@o.pretty_gives_max_value(:panda => 1, :bamboo => 2)).to eq(2) } end context "given an unfulfilled contract" do it { fails { @o.gives_max_value(:panda => "1", :bamboo => "2") } } it { fails { @o.gives_max_value(nil) } } it { fails { @o.gives_max_value(1) } } it { fails { @o.pretty_gives_max_value(:panda => "1", :bamboo => "2") } } end describe "#to_s" do context "given Symbol => String" do it { expect(Contracts::HashOf[Symbol, String].to_s).to eq("Hash") } end context "given String => Num" do it { expect(Contracts::HashOf[String, Contracts::Num].to_s).to eq("Hash") } end end end describe "StrictHash:" do context "when given an exact correct input" do it "does not raise an error" do passes { @o.strict_person(:name => "calvin", :age => 10) } end end context "when given an input with correct keys but wrong types" do it "raises an error" do fails { @o.strict_person(:name => "calvin", :age => "10") } end end context "when given an input with missing keys" do it "raises an error" do fails { @o.strict_person(:name => "calvin") } end end context "when given an input with extra keys" do it "raises an error" do fails { @o.strict_person(:name => "calvin", :age => 10, :soft => true) } end end context "when given not a hash" do it "raises an error" do fails { @o.strict_person(1337) } end end end end contracts-0.16.0/.travis.yml0000644000004100000410000000056613120354472015747 0ustar www-datawww-datadist: trusty language: ruby rvm: - "1.9.3" - "2.0.0" - "2.1" - "2.2" - "ruby-2.3.0" before_install: - gem install bundler script: - bundle exec rspec - script/cucumber - ruby script/rubocop.rb bundler_args: --without development matrix: include: - rvm: "1.9.2" dist: precise - rvm: jruby-19mode # JRuby in 1.9 mode dist: precise contracts-0.16.0/lib/0000755000004100000410000000000013120354472014375 5ustar www-datawww-datacontracts-0.16.0/lib/contracts.rb0000644000004100000410000001707513120354472016734 0ustar www-datawww-datarequire "contracts/attrs" require "contracts/builtin_contracts" require "contracts/decorators" require "contracts/errors" require "contracts/formatters" require "contracts/invariants" require "contracts/method_reference" require "contracts/support" require "contracts/engine" require "contracts/method_handler" require "contracts/validators" require "contracts/call_with" require "contracts/core" module Contracts def self.included(base) base.send(:include, Core) end def self.extended(base) base.send(:extend, Core) end end # This is the main Contract class. When you write a new contract, you'll # write it as: # # Contract [contract names] => return_value # # This class also provides useful callbacks and a validation method. # # For #make_validator and related logic see file # lib/contracts/validators.rb # For #call_with and related logic see file # lib/contracts/call_with.rb class Contract < Contracts::Decorator extend Contracts::Validators include Contracts::CallWith # Default implementation of failure_callback. Provided as a block to be able # to monkey patch #failure_callback only temporary and then switch it back. # First important usage - for specs. DEFAULT_FAILURE_CALLBACK = proc do |data| if data[:return_value] # this failed on the return contract fail ReturnContractError.new(failure_msg(data), data) else # this failed for a param contract fail data[:contracts].failure_exception.new(failure_msg(data), data) end end attr_reader :args_contracts, :ret_contract, :klass, :method def initialize(klass, method, *contracts) unless contracts.last.is_a?(Hash) unless contracts.one? fail %{ It looks like your contract for #{method.name} doesn't have a return value. A contract should be written as `Contract arg1, arg2 => return_value`. }.strip end contracts = [nil => contracts[-1]] end # internally we just convert that return value syntax back to an array @args_contracts = contracts[0, contracts.size - 1] + contracts[-1].keys @ret_contract = contracts[-1].values[0] @args_validators = args_contracts.map do |contract| Contract.make_validator(contract) end @args_contract_index = args_contracts.index do |contract| contract.is_a? Contracts::Args end @ret_validator = Contract.make_validator(ret_contract) @pattern_match = false # == @has_proc_contract last_contract = args_contracts.last is_a_proc = last_contract.is_a?(Class) && (last_contract <= Proc || last_contract <= Method) maybe_a_proc = last_contract.is_a?(Contracts::Maybe) && last_contract.include_proc? @has_proc_contract = is_a_proc || maybe_a_proc || last_contract.is_a?(Contracts::Func) # ==== # == @has_options_contract last_contract = args_contracts.last penultimate_contract = args_contracts[-2] @has_options_contract = if @has_proc_contract penultimate_contract.is_a?(Hash) || penultimate_contract.is_a?(Contracts::Builtin::KeywordArgs) else last_contract.is_a?(Hash) || last_contract.is_a?(Contracts::Builtin::KeywordArgs) end # === @klass, @method = klass, method end def pretty_contract c c.is_a?(Class) ? c.name : c.class.name end def to_s args = args_contracts.map { |c| pretty_contract(c) }.join(", ") ret = pretty_contract(ret_contract) ("#{args} => #{ret}").gsub("Contracts::Builtin::", "") end # Given a hash, prints out a failure message. # This function is used by the default #failure_callback method # and uses the hash passed into the failure_callback method. def self.failure_msg(data) expected = Contracts::Formatters::Expected.new(data[:contract]).contract position = Contracts::Support.method_position(data[:method]) method_name = Contracts::Support.method_name(data[:method]) header = if data[:return_value] "Contract violation for return value:" else "Contract violation for argument #{data[:arg_pos]} of #{data[:total_args]}:" end %{#{header} Expected: #{expected}, Actual: #{data[:arg].inspect} Value guarded in: #{data[:class]}::#{method_name} With Contract: #{data[:contracts]} At: #{position} } end # Callback for when a contract fails. By default it raises # an error and prints detailed info about the contract that # failed. You can also monkeypatch this callback to do whatever # you want...log the error, send you an email, print an error # message, etc. # # Example of monkeypatching: # # def Contract.failure_callback(data) # puts "You had an error!" # puts failure_msg(data) # exit # end def self.failure_callback(data, use_pattern_matching = true) if data[:contracts].pattern_match? && use_pattern_matching return DEFAULT_FAILURE_CALLBACK.call(data) end fetch_failure_callback.call(data) end # Used to override failure_callback without monkeypatching. # # Takes: block parameter, that should accept one argument - data. # # Example usage: # # Contract.override_failure_callback do |data| # puts "You had an error" # puts failure_msg(data) # exit # end def self.override_failure_callback(&blk) @failure_callback = blk end # Used to restore default failure callback def self.restore_failure_callback @failure_callback = DEFAULT_FAILURE_CALLBACK end def self.fetch_failure_callback @failure_callback ||= DEFAULT_FAILURE_CALLBACK end # Used to verify if an argument satisfies a contract. # # Takes: an argument and a contract. # # Returns: a tuple: [Boolean, metadata]. The boolean indicates # whether the contract was valid or not. If it wasn't, metadata # contains some useful information about the failure. def self.valid?(arg, contract) make_validator(contract)[arg] end def [](*args, &blk) call(*args, &blk) end def call(*args, &blk) call_with(nil, *args, &blk) end # if we specified a proc in the contract but didn't pass one in, # it's possible we are going to pass in a block instead. So lets # append a nil to the list of args just so it doesn't fail. # a better way to handle this might be to take this into account # before throwing a "mismatched # of args" error. # returns true if it appended nil def maybe_append_block! args, blk return false unless @has_proc_contract && !blk && (@args_contract_index || args.size < args_contracts.size) args << nil true end # Same thing for when we have named params but didn't pass any in. # returns true if it appended nil def maybe_append_options! args, blk return false unless @has_options_contract if @has_proc_contract && (args_contracts[-2].is_a?(Hash) || args_contracts[-2].is_a?(Contracts::Builtin::KeywordArgs)) && !args[-2].is_a?(Hash) args.insert(-2, {}) elsif (args_contracts[-1].is_a?(Hash) || args_contracts[-1].is_a?(Contracts::Builtin::KeywordArgs)) && !args[-1].is_a?(Hash) args << {} end true end # Used to determine type of failure exception this contract should raise in case of failure def failure_exception if pattern_match? PatternMatchingError else ParamContractError end end # @private # Used internally to mark contract as pattern matching contract def pattern_match! @pattern_match = true end # Used to determine if contract is a pattern matching contract def pattern_match? @pattern_match == true end end contracts-0.16.0/lib/contracts/0000755000004100000410000000000013120354472016375 5ustar www-datawww-datacontracts-0.16.0/lib/contracts/call_with.rb0000644000004100000410000001020513120354472020666 0ustar www-datawww-datamodule Contracts module CallWith def call_with(this, *args, &blk) args << blk if blk # Explicitly append blk=nil if nil != Proc contract violation anticipated nil_block_appended = maybe_append_block!(args, blk) # Explicitly append options={} if Hash contract is present maybe_append_options!(args, blk) # Loop forward validating the arguments up to the splat (if there is one) (@args_contract_index || args.size).times do |i| contract = args_contracts[i] arg = args[i] validator = @args_validators[i] unless validator && validator[arg] return unless Contract.failure_callback(:arg => arg, :contract => contract, :class => klass, :method => method, :contracts => self, :arg_pos => i+1, :total_args => args.size, :return_value => false) end if contract.is_a?(Contracts::Func) && blk && !nil_block_appended blk = Contract.new(klass, arg, *contract.contracts) elsif contract.is_a?(Contracts::Func) args[i] = Contract.new(klass, arg, *contract.contracts) end end # If there is a splat loop backwards to the lower index of the splat # Once we hit the splat in this direction set its upper index # Keep validating but use this upper index to get the splat validator. if @args_contract_index splat_upper_index = @args_contract_index (args.size - @args_contract_index).times do |i| arg = args[args.size - 1 - i] if args_contracts[args_contracts.size - 1 - i].is_a?(Contracts::Args) splat_upper_index = i end # Each arg after the spat is found must use the splat validator j = i < splat_upper_index ? i : splat_upper_index contract = args_contracts[args_contracts.size - 1 - j] validator = @args_validators[args_contracts.size - 1 - j] unless validator && validator[arg] return unless Contract.failure_callback(:arg => arg, :contract => contract, :class => klass, :method => method, :contracts => self, :arg_pos => i-1, :total_args => args.size, :return_value => false) end if contract.is_a?(Contracts::Func) args[args.size - 1 - i] = Contract.new(klass, arg, *contract.contracts) end end end # If we put the block into args for validating, restore the args # OR if we added a fake nil at the end because a block wasn't passed in. args.slice!(-1) if blk || nil_block_appended result = if method.respond_to?(:call) # proc, block, lambda, etc method.call(*args, &blk) else # original method name referrence added_block = blk ? lambda { |*params| blk.call(*params) } : nil method.send_to(this, *args, &added_block) end unless @ret_validator[result] Contract.failure_callback(:arg => result, :contract => ret_contract, :class => klass, :method => method, :contracts => self, :return_value => true) end this.verify_invariants!(method) if this.respond_to?(:verify_invariants!) if ret_contract.is_a?(Contracts::Func) result = Contract.new(klass, result, *ret_contract.contracts) end result end end end contracts-0.16.0/lib/contracts/decorators.rb0000644000004100000410000000231013120354472021063 0ustar www-datawww-datamodule Contracts module MethodDecorators def self.extended(klass) Engine.apply(klass) end def inherited(subclass) Engine.fetch_from(subclass).set_eigenclass_owner super end def method_added(name) MethodHandler.new(name, false, self).handle super end def singleton_method_added(name) MethodHandler.new(name, true, self).handle super end end class Decorator # an attr_accessor for a class variable: class << self; attr_accessor :decorators; end def self.inherited(klass) name = klass.name.gsub(/^./) { |m| m.downcase } return if name =~ /^[^A-Za-z_]/ || name =~ /[^0-9A-Za-z_]/ # the file and line parameters set the text for error messages # make a new method that is the name of your decorator. # that method accepts random args and a block. # inside, `decorate` is called with those params. MethodDecorators.module_eval <<-ruby_eval, __FILE__, __LINE__ + 1 def #{klass}(*args, &blk) ::Contracts::Engine.fetch_from(self).decorate(#{klass}, *args, &blk) end ruby_eval end def initialize(klass, method) @method = method end end end contracts-0.16.0/lib/contracts/errors.rb0000644000004100000410000000330413120354472020236 0ustar www-datawww-data# @private # Base class for Contract errors # # If default failure callback is used it stores failure data class ContractBaseError < ArgumentError attr_reader :data def initialize(message, data) super(message) @data = data end # Used to convert to simple ContractError from other contract errors def to_contract_error self end end # Default contract error # # If default failure callback is used, users normally see only these contract errors class ContractError < ContractBaseError end class ParamContractError < ContractError end class ReturnContractError < ContractError end # @private # Special contract error used internally to detect pattern failure during pattern matching class PatternMatchingError < ContractBaseError # Used to convert to ContractError from PatternMatchingError def to_contract_error ContractError.new(to_s, data) end end # Base invariant violation error class InvariantError < StandardError def to_contract_error self end end module Contracts # Error issued when user haven't included Contracts in original class but used Contract definition in singleton class # # Provides useful description for user of the gem and an example of correct usage. class ContractsNotIncluded < TypeError DEFAULT_MESSAGE = %{In order to use contracts in singleton class, please include Contracts module in original class Example: ```ruby class Example include Contracts # this line is required class << self # you can use `Contract` definition here now end end ```} attr_reader :message alias_method :to_s, :message def initialize(message = DEFAULT_MESSAGE) @message = message end end end contracts-0.16.0/lib/contracts/method_reference.rb0000644000004100000410000000507313120354472022225 0ustar www-datawww-datamodule Contracts # MethodReference represents original method reference that was # decorated by contracts.ruby. Used for instance methods. class MethodReference attr_reader :name # name - name of the method # method - method object def initialize(name, method) @name = name @method = method end # Returns method_position, delegates to Support.method_position def method_position Support.method_position(@method) end # Makes a method re-definition in proper way def make_definition(this, &blk) is_private = private?(this) is_protected = protected?(this) alias_target(this).send(:define_method, name, &blk) make_private(this) if is_private make_protected(this) if is_protected end # Aliases original method to a special unique name, which is known # only to this class. Usually done right before re-defining the # method. def make_alias(this) _aliased_name = aliased_name original_name = name alias_target(this).class_eval do alias_method _aliased_name, original_name end end # Calls original method on specified `this` argument with # specified arguments `args` and block `&blk`. def send_to(this, *args, &blk) this.send(aliased_name, *args, &blk) end private # Makes a method private def make_private(this) original_name = name alias_target(this).class_eval { private original_name } end def private?(this) this.private_instance_methods.map(&:to_sym).include?(name) end def protected?(this) this.protected_instance_methods.map(&:to_sym).include?(name) end # Makes a method protected def make_protected(this) original_name = name alias_target(this).class_eval { protected original_name } end # Returns alias target for instance methods, subject to be # overriden in subclasses. def alias_target(this) this end def aliased_name @_original_name ||= construct_unique_name end def construct_unique_name :"__contracts_ruby_original_#{name}_#{Support.unique_id}" end end # The same as MethodReference, but used for singleton methods. class SingletonMethodReference < MethodReference private def private?(this) this.private_methods.map(&:to_sym).include?(name) end def protected?(this) this.protected_methods.map(&:to_sym).include?(name) end # Return alias target for singleton methods. def alias_target(this) Support.eigenclass_of this end end end contracts-0.16.0/lib/contracts/support.rb0000644000004100000410000000246213120354472020442 0ustar www-datawww-datamodule Contracts module Support class << self def method_position(method) return method.method_position if method.is_a?(MethodReference) file, line = method.source_location if file.nil? || line.nil? "" else file + ":" + line.to_s end end def method_name(method) method.is_a?(Proc) ? "Proc" : method.name end # Generates unique id, which can be used as a part of identifier # # Example: # Contracts::Support.unique_id # => "i53u6tiw5hbo" def unique_id # Consider using SecureRandom.hex here, and benchmark which one is better (Time.now.to_f * 1000).to_i.to_s(36) + rand(1_000_000).to_s(36) end def contract_id(contract) contract.object_id end def eigenclass_hierarchy_supported? RUBY_PLATFORM != "java" || RUBY_VERSION.to_f >= 2.0 end def eigenclass_of(target) class << target; self; end end def eigenclass?(target) module_eigenclass?(target) || target <= eigenclass_of(Object) end private # Module eigenclass can be detected by its ancestor chain # containing a Module def module_eigenclass?(target) target < Module end end end end contracts-0.16.0/lib/contracts/engine.rb0000644000004100000410000000117613120354472020174 0ustar www-datawww-datarequire "contracts/engine/base" require "contracts/engine/target" require "contracts/engine/eigenclass" require "forwardable" module Contracts # Engine facade, normally you shouldn't refer internals of Engine # module directly. module Engine class << self extend Forwardable # .apply(klass) - enables contracts engine on klass # .applied?(klass) - returns true if klass has contracts engine # .fetch_from(klass) - returns contracts engine for klass def_delegators :base_engine, :apply, :applied?, :fetch_from private def base_engine Base end end end end contracts-0.16.0/lib/contracts/method_handler.rb0000644000004100000410000001340113120354472021676 0ustar www-datawww-datamodule Contracts # Handles class and instance methods addition # Represents single such method class MethodHandler METHOD_REFERENCE_FACTORY = { :class_methods => SingletonMethodReference, :instance_methods => MethodReference } RAW_METHOD_STRATEGY = { :class_methods => lambda { |target, name| target.method(name) }, :instance_methods => lambda { |target, name| target.instance_method(name) } } # Creates new instance of MethodHandler # # @param [Symbol] method_name # @param [Bool] is_class_method # @param [Class] target - class that method got added to def initialize(method_name, is_class_method, target) @method_name = method_name @is_class_method = is_class_method @target = target end # Handles method addition def handle return unless engine? return if decorators.empty? validate_decorators! validate_pattern_matching! engine.add_method_decorator(method_type, method_name, decorator) mark_pattern_matching_decorators method_reference.make_alias(target) redefine_method end private attr_reader :method_name, :is_class_method, :target def engine? Engine.applied?(target) end def engine Engine.fetch_from(target) end def decorators @_decorators ||= engine.all_decorators end def method_type @_method_type ||= is_class_method ? :class_methods : :instance_methods end # _method_type is required for assigning it to local variable with # the same name. See: #redefine_method alias_method :_method_type, :method_type def method_reference @_method_reference ||= METHOD_REFERENCE_FACTORY[method_type].new(method_name, raw_method) end def raw_method RAW_METHOD_STRATEGY[method_type].call(target, method_name) end def ignore_decorators? ENV["NO_CONTRACTS"] && !pattern_matching? end def decorated_methods @_decorated_methods ||= engine.decorated_methods_for(method_type, method_name) end def pattern_matching? return @_pattern_matching if defined?(@_pattern_matching) @_pattern_matching = decorated_methods.any? { |x| x.method != method_reference } end def mark_pattern_matching_decorators return unless pattern_matching? decorated_methods.each(&:pattern_match!) end def decorator @_decorator ||= decorator_class.new(target, method_reference, *decorator_args) end def decorator_class decorators.first[0] end def decorator_args decorators.first[1] end def redefine_method return if ignore_decorators? # Those are required for instance_eval to be able to refer them name = method_name method_type = _method_type current_engine = engine # We are gonna redefine original method here method_reference.make_definition(target) do |*args, &blk| engine = current_engine.nearest_decorated_ancestor # If we weren't able to find any ancestor that has decorated methods # FIXME : this looks like untested code (commenting it out doesn't make specs red) unless engine fail "Couldn't find decorator for method " + self.class.name + ":#{name}.\nDoes this method look correct to you? If you are using contracts from rspec, rspec wraps classes in it's own class.\nLook at the specs for contracts.ruby as an example of how to write contracts in this case." end # Fetch decorated methods out of the contracts engine decorated_methods = engine.decorated_methods_for(method_type, name) # This adds support for overloading methods. Here we go # through each method and call it with the arguments. # If we get a failure_exception, we move to the next # function. Otherwise we return the result. # If we run out of functions, we raise the last error, but # convert it to_contract_error. success = false i = 0 result = nil expected_error = decorated_methods[0].failure_exception until success decorated_method = decorated_methods[i] i += 1 begin success = true result = decorated_method.call_with(self, *args, &blk) rescue expected_error => error success = false unless decorated_methods[i] begin ::Contract.failure_callback(error.data, false) rescue expected_error => final_error raise final_error.to_contract_error end end end end # Return the result of successfully called method result end end def validate_decorators! return if decorators.size == 1 fail %{ Oops, it looks like method '#{name}' has multiple contracts: #{decorators.map { |x| x[1][0].inspect }.join("\n")} Did you accidentally put more than one contract on a single function, like so? Contract String => String Contract Num => String def foo x end If you did NOT, then you have probably discovered a bug in this library. Please file it along with the relevant code at: https://github.com/egonSchiele/contracts.ruby/issues } end def validate_pattern_matching! new_args_contract = decorator.args_contracts matched = decorated_methods.select do |contract| contract.args_contracts == new_args_contract end return if matched.empty? fail ContractError.new(%{ It looks like you are trying to use pattern-matching, but multiple definitions for function '#{method_name}' have the same contract for input parameters: #{(matched + [decorator]).map(&:to_s).join("\n")} Each definition needs to have a different contract for the parameters. }, {}) end end end contracts-0.16.0/lib/contracts/version.rb0000644000004100000410000000005213120354472020404 0ustar www-datawww-datamodule Contracts VERSION = "0.16.0" end contracts-0.16.0/lib/contracts/core.rb0000644000004100000410000000250213120354472017651 0ustar www-datawww-datamodule Contracts module Core def self.included(base) common(base) end def self.extended(base) common(base) end def self.common(base) base.extend(MethodDecorators) base.instance_eval do def functype(funcname) contracts = Engine.fetch_from(self).decorated_methods_for(:class_methods, funcname) if contracts.nil? "No contract for #{self}.#{funcname}" else "#{funcname} :: #{contracts[0]}" end end end # NOTE: Workaround for `defined?(super)` bug in ruby 1.9.2 # source: http://stackoverflow.com/a/11181685 # bug: https://bugs.ruby-lang.org/issues/6644 base.class_eval <<-RUBY # TODO: deprecate # Required when contracts are included in global scope def Contract(*args) if defined?(super) super else self.class.Contract(*args) end end RUBY base.class_eval do def functype(funcname) contracts = Engine.fetch_from(self.class).decorated_methods_for(:instance_methods, funcname) if contracts.nil? "No contract for #{self.class}.#{funcname}" else "#{funcname} :: #{contracts[0]}" end end end end end end contracts-0.16.0/lib/contracts/attrs.rb0000644000004100000410000000073213120354472020061 0ustar www-datawww-datamodule Contracts module Attrs def attr_reader_with_contract(*names, contract) Contract Contracts::None => contract attr_reader(*names) end def attr_writer_with_contract(*names, contract) Contract contract => contract attr_writer(*names) end def attr_accessor_with_contract(*names, contract) attr_reader_with_contract(*names, contract) attr_writer_with_contract(*names, contract) end end include Attrs end contracts-0.16.0/lib/contracts/formatters.rb0000644000004100000410000000722613120354472021117 0ustar www-datawww-datamodule Contracts # A namespace for classes related to formatting. module Formatters # Used to format contracts for the `Expected:` field of error output. class Expected # @param full [Boolean] if false only unique `to_s` values will be output, # non unique values become empty string. def initialize(contract, full = true) @contract, @full = contract, full end # Formats any type of Contract. def contract(contract = @contract) if contract.is_a?(Hash) hash_contract(contract) elsif contract.is_a?(Array) array_contract(contract) else InspectWrapper.create(contract, @full) end end # Formats Hash contracts. def hash_contract(hash) @full = true # Complex values output completely, overriding @full hash.inject({}) do |repr, (k, v)| repr.merge(k => InspectWrapper.create(contract(v), @full)) end.inspect end # Formats Array contracts. def array_contract(array) @full = true array.map { |v| InspectWrapper.create(contract(v), @full) }.inspect end end # A wrapper class to produce correct inspect behaviour for different # contract values - constants, Class contracts, instance contracts etc. module InspectWrapper # InspectWrapper is a factory, will never be an instance # @return [ClassInspectWrapper, ObjectInspectWrapper] def self.create(value, full = true) if value.class == Class ClassInspectWrapper else ObjectInspectWrapper end.new(value, full) end # @param full [Boolean] if false only unique `to_s` values will be output, # non unique values become empty string. def initialize(value, full) @value, @full = value, full end # Inspect different types of contract values. # Contracts module prefix will be removed from classes. # Custom to_s messages will be wrapped in round brackets to differentiate # from standard Strings. # Primitive values e.g. 42, true, nil will be left alone. def inspect return "" unless full? return @value.inspect if empty_val? return @value.to_s if plain? return delim(@value.to_s) if useful_to_s? useful_inspect end def delim(value) @full ? "(#{value})" : "#{value}" end # Eliminates eronious quotes in output that plain inspect includes. def to_s inspect end private def empty_val? @value.nil? || @value == "" end def full? @full || @value.is_a?(Hash) || @value.is_a?(Array) || (!plain? && useful_to_s?) end def plain? # Not a type of contract that can have a custom to_s defined !@value.is_a?(Builtin::CallableClass) && @value.class != Class end def useful_to_s? # Useless to_s value or no custom to_s behavious defined !empty_to_s? && custom_to_s? end def empty_to_s? @value.to_s.empty? end def strip_prefix(val) val.gsub(/^Contracts::Builtin::/, "") end end class ClassInspectWrapper include InspectWrapper def custom_to_s? @value.to_s != @value.name end def useful_inspect strip_prefix(empty_to_s? ? @value.name : @value.inspect) end end class ObjectInspectWrapper include InspectWrapper def custom_to_s? !@value.to_s.match(/#\<\w+:.+\>/) end def useful_inspect strip_prefix(empty_to_s? ? @value.class.name : @value.inspect) end end end end contracts-0.16.0/lib/contracts/validators.rb0000644000004100000410000000670313120354472021100 0ustar www-datawww-datamodule Contracts module Validators DEFAULT_VALIDATOR_STRATEGIES = { # e.g. lambda {true} Proc => lambda { |contract| contract }, # e.g. [Num, String] # TODO: account for these errors too Array => lambda do |contract| lambda do |arg| return false unless arg.is_a?(Array) && arg.length == contract.length arg.zip(contract).all? do |_arg, _contract| Contract.valid?(_arg, _contract) end end end, # e.g. { :a => Num, :b => String } Hash => lambda do |contract| lambda do |arg| return false unless arg.is_a?(Hash) contract.keys.all? do |k| Contract.valid?(arg[k], contract[k]) end end end, Range => lambda do |contract| lambda do |arg| contract.include?(arg) end end, Regexp => lambda do |contract| lambda do |arg| arg =~ contract end end, Contracts::Args => lambda do |contract| lambda do |arg| Contract.valid?(arg, contract.contract) end end, Contracts::Func => lambda do |_| lambda do |arg| arg.is_a?(Method) || arg.is_a?(Proc) end end, :valid => lambda do |contract| lambda { |arg| contract.valid?(arg) } end, :class => lambda do |contract| lambda { |arg| arg.is_a?(contract) } end, :default => lambda do |contract| lambda { |arg| contract == arg } end }.freeze # Allows to override validator with custom one. # Example: # Contract.override_validator(Array) do |contract| # lambda do |arg| # # .. implementation for Array contract .. # end # end # # Contract.override_validator(:class) do |contract| # lambda do |arg| # arg.is_a?(contract) || arg.is_a?(RSpec::Mocks::Double) # end # end def override_validator(name, &block) validator_strategies[name] = block end # This is a little weird. For each contract # we pre-make a proc to validate it so we # don't have to go through this decision tree every time. # Seems silly but it saves us a bunch of time (4.3sec vs 5.2sec) def make_validator!(contract) klass = contract.class key = if validator_strategies.key?(klass) klass else if contract.respond_to? :valid? :valid elsif klass == Class || klass == Module :class else :default end end validator_strategies[key].call(contract) end def make_validator(contract) contract_id = Support.contract_id(contract) if memoized_validators.key?(contract_id) return memoized_validators[contract_id] end memoized_validators[contract_id] = make_validator!(contract) end # @private def reset_validators clean_memoized_validators restore_validators end # @private def validator_strategies @_validator_strategies ||= restore_validators end # @private def restore_validators @_validator_strategies = DEFAULT_VALIDATOR_STRATEGIES.dup end # @private def memoized_validators @_memoized_validators ||= clean_memoized_validators end # @private def clean_memoized_validators @_memoized_validators = {} end end end contracts-0.16.0/lib/contracts/builtin_contracts.rb0000644000004100000410000003255513120354472022462 0ustar www-datawww-datarequire "contracts/formatters" require "set" # rdoc # This module contains all the builtin contracts. # If you want to use them, first: # # import Contracts # # And then use these or write your own! # # A simple example: # # Contract Num, Num => Num # def add(a, b) # a + b # end # # The contract is Contract Num, Num, Num. # That says that the +add+ function takes two numbers and returns a number. module Contracts module Builtin # Check that an argument is +Numeric+. class Num def self.valid? val val.is_a? Numeric end end # Check that an argument is a positive number. class Pos def self.valid? val val && val.is_a?(Numeric) && val > 0 end end # Check that an argument is a negative number. class Neg def self.valid? val val && val.is_a?(Numeric) && val < 0 end end # Check that an argument is an +Integer+. class Int def self.valid? val val && val.is_a?(Integer) end end # Check that an argument is a natural number (includes zero). class Nat def self.valid? val val && val.is_a?(Integer) && val >= 0 end end # Check that an argument is a positive natural number (excludes zero). class NatPos def self.valid? val val && val.is_a?(Integer) && val > 0 end end # Passes for any argument. class Any def self.valid? val true end end # Fails for any argument. class None def self.valid? val false end end # Use this when you are writing your own contract classes. # Allows your contract to be called with [] instead of .new: # # Old: Or.new(param1, param2) # # New: Or[param1, param2] # # Of course, .new still works. class CallableClass include ::Contracts::Formatters def self.[](*vals) new(*vals) end end # Takes a variable number of contracts. # The contract passes if any of the contracts pass. # Example: Or[Fixnum, Float] class Or < CallableClass def initialize(*vals) @vals = vals end def valid?(val) @vals.any? do |contract| res, _ = Contract.valid?(val, contract) res end end def to_s @vals[0, @vals.size-1].map do |x| InspectWrapper.create(x) end.join(", ") + " or " + InspectWrapper.create(@vals[-1]).to_s end end # Takes a variable number of contracts. # The contract passes if exactly one of those contracts pass. # Example: Xor[Fixnum, Float] class Xor < CallableClass def initialize(*vals) @vals = vals end def valid?(val) results = @vals.map do |contract| res, _ = Contract.valid?(val, contract) res end results.count(true) == 1 end def to_s @vals[0, @vals.size-1].map do |x| InspectWrapper.create(x) end.join(", ") + " xor " + InspectWrapper.create(@vals[-1]).to_s end end # Takes a variable number of contracts. # The contract passes if all contracts pass. # Example: And[Fixnum, Float] class And < CallableClass def initialize(*vals) @vals = vals end def valid?(val) @vals.all? do |contract| res, _ = Contract.valid?(val, contract) res end end def to_s @vals[0, @vals.size-1].map do |x| InspectWrapper.create(x) end.join(", ") + " and " + InspectWrapper.create(@vals[-1]).to_s end end # Takes a variable number of method names as symbols. # The contract passes if the argument responds to all # of those methods. # Example: RespondTo[:password, :credit_card] class RespondTo < CallableClass def initialize(*meths) @meths = meths end def valid?(val) @meths.all? do |meth| val.respond_to? meth end end def to_s "a value that responds to #{@meths.inspect}" end end # Takes a variable number of method names as symbols. # Given an argument, all of those methods are called # on the argument one by one. If they all return true, # the contract passes. # Example: Send[:valid?] class Send < CallableClass def initialize(*meths) @meths = meths end def valid?(val) @meths.all? do |meth| val.send(meth) end end def to_s "a value that returns true for all of #{@meths.inspect}" end end # Takes a class +A+. If argument is object of type +A+, the contract passes. # If it is a subclass of A (or not related to A in any way), it fails. # Example: Exactly[Numeric] class Exactly < CallableClass def initialize(cls) @cls = cls end def valid?(val) val.class == @cls end def to_s "exactly #{@cls.inspect}" end end # Takes a list of values, e.g. +[:a, :b, :c]+. If argument is included in # the list, the contract passes. # # Example: Enum[:a, :b, :c]? class Enum < CallableClass def initialize(*vals) @vals = vals end def valid?(val) @vals.include? val end end # Takes a value +v+. If the argument is +.equal+ to +v+, the contract passes, # otherwise the contract fails. # Example: Eq[Class] class Eq < CallableClass def initialize(value) @value = value end def valid?(val) @value.equal?(val) end def to_s "to be equal to #{@value.inspect}" end end # Takes a variable number of contracts. The contract # passes if all of those contracts fail for the given argument. # Example: Not[nil] class Not < CallableClass def initialize(*vals) @vals = vals end def valid?(val) @vals.all? do |contract| res, _ = Contract.valid?(val, contract) !res end end def to_s "a value that is none of #{@vals.inspect}" end end # @private # Takes a collection(responds to :each) type and a contract. # The related argument must be of specified collection type. # Checks the contract against every element of the collection. # If it passes for all elements, the contract passes. # Example: CollectionOf[Array, Num] class CollectionOf < CallableClass def initialize(collection_class, contract) @collection_class = collection_class @contract = contract end def valid?(vals) return false unless vals.is_a?(@collection_class) vals.all? do |val| res, _ = Contract.valid?(val, @contract) res end end def to_s "a collection #{@collection_class} of #{@contract}" end class Factory def initialize(collection_class, &before_new) @collection_class = collection_class @before_new = before_new end def new(contract) @before_new && @before_new.call CollectionOf.new(@collection_class, contract) end alias_method :[], :new end end # Takes a contract. The related argument must be an array. # Checks the contract against every element of the array. # If it passes for all elements, the contract passes. # Example: ArrayOf[Num] ArrayOf = CollectionOf::Factory.new(Array) # Takes a contract. The related argument must be a set. # Checks the contract against every element of the set. # If it passes for all elements, the contract passes. # Example: SetOf[Num] SetOf = CollectionOf::Factory.new(Set) # Used for *args (variadic functions). Takes a contract # and uses it to validate every element passed in # through *args. # Example: Args[Or[String, Num]] class Args < CallableClass attr_reader :contract def initialize(contract) @contract = contract end def to_s "Args[#{@contract}]" end end class Bool def self.valid? val val.is_a?(TrueClass) || val.is_a?(FalseClass) end end # Use this to specify a Range object of a particular datatype. # Example: RangeOf[Nat], RangeOf[Date], ... class RangeOf < CallableClass def initialize(contract) @contract = contract end def valid?(val) val.is_a?(Range) && Contract.valid?(val.first, @contract) && Contract.valid?(val.last, @contract) end def to_s "a range of #{@contract}" end end # Use this to specify the Hash characteristics. Takes two contracts, # one for hash keys and one for hash values. # Example: HashOf[Symbol, String] class HashOf < CallableClass INVALID_KEY_VALUE_PAIR = "You should provide only one key-value pair to HashOf contract" def initialize(key, value = nil) if value @key = key @value = value else validate_hash(key) @key = key.keys.first @value = key[@key] end end def valid?(hash) return false unless hash.is_a?(Hash) keys_match = hash.keys.map { |k| Contract.valid?(k, @key) }.all? vals_match = hash.values.map { |v| Contract.valid?(v, @value) }.all? [keys_match, vals_match].all? end def to_s "Hash<#{@key}, #{@value}>" end private def validate_hash(hash) fail ArgumentError, INVALID_KEY_VALUE_PAIR unless hash.count == 1 end end # Use this to specify the Hash characteristics. This contracts fails # if there are any extra keys that don't have contracts on them. # Example: StrictHash[{ a: String, b: Bool }] class StrictHash < CallableClass attr_reader :contract_hash def initialize(contract_hash) @contract_hash = contract_hash end def valid?(arg) return false unless arg.is_a?(Hash) return false unless arg.keys.sort.eql?(contract_hash.keys.sort) contract_hash.all? do |key, contract| contract_hash.key?(key) && Contract.valid?(arg[key], contract) end end end # Use this for specifying contracts for keyword arguments # Example: KeywordArgs[ e: Range, f: Optional[Num] ] class KeywordArgs < CallableClass def initialize(options) @options = options end def valid?(hash) return false unless hash.is_a?(Hash) return false unless hash.keys - options.keys == [] options.all? do |key, contract| Optional._valid?(hash, key, contract) end end def to_s "KeywordArgs[#{options}]" end def inspect to_s end private attr_reader :options end # Use this for specifying contracts for class arguments # Example: DescendantOf[ e: Range, f: Optional[Num] ] class DescendantOf < CallableClass def initialize(parent_class) @parent_class = parent_class end def valid?(given_class) given_class.is_a?(Class) && given_class.ancestors.include?(parent_class) end def to_s "DescendantOf[#{parent_class}]" end def inspect to_s end private attr_reader :parent_class end # Use this for specifying optional keyword argument # Example: Optional[Num] class Optional < CallableClass UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH = "Unable to use Optional contract outside of KeywordArgs contract" def self._valid?(hash, key, contract) return Contract.valid?(hash[key], contract) unless contract.is_a?(Optional) contract.within_opt_hash! !hash.key?(key) || Contract.valid?(hash[key], contract) end def initialize(contract) @contract = contract @within_opt_hash = false end def within_opt_hash! @within_opt_hash = true self end def valid?(value) ensure_within_opt_hash Contract.valid?(value, contract) end def to_s "Optional[#{formatted_contract}]" end def inspect to_s end private attr_reader :contract, :within_opt_hash def ensure_within_opt_hash return if within_opt_hash fail ArgumentError, UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH end def formatted_contract Formatters::InspectWrapper.create(contract) end end # Takes a Contract. # The contract passes if the contract passes or the given value is nil. # Maybe(foo) is equivalent to Or[foo, nil]. class Maybe < Or def initialize(*vals) super(*(vals + [nil])) end def include_proc? @vals.include? Proc end end # Used to define contracts on functions passed in as arguments. # Example: Func[Num => Num] # the function should take a number and return a number class Func < CallableClass attr_reader :contracts def initialize(*contracts) @contracts = contracts end end end # Users can still include `Contracts::Core` & `Contracts::Builtin` include Builtin end contracts-0.16.0/lib/contracts/engine/0000755000004100000410000000000013120354472017642 5ustar www-datawww-datacontracts-0.16.0/lib/contracts/engine/eigenclass.rb0000644000004100000410000000267713120354472022320 0ustar www-datawww-datamodule Contracts module Engine # Special case of contracts engine for eigenclasses # We don't care about eigenclass of eigenclass at this point class Eigenclass < Base # Class that owns this eigenclass attr_accessor :owner_class # Automatically enables eigenclass engine if it is not # Returns its engine # NOTE: Required by jruby in 1.9 mode. Otherwise inherited # eigenclasses don't have their engines # # @param [Class] eigenclass - class in question # @param [Class] owner - owner of eigenclass # @return [Engine::Eigenclass] def self.lift(eigenclass, owner) return Engine.fetch_from(eigenclass) if Engine.applied?(eigenclass) Target.new(eigenclass).apply(Eigenclass) eigenclass.extend(MethodDecorators) # FIXME; this should detect what user uses `include Contracts` or # `include Contracts;;Core` eigenclass.send(:include, Contracts) Engine.fetch_from(owner).set_eigenclass_owner Engine.fetch_from(eigenclass) end # No-op for eigenclasses def set_eigenclass_owner end # Fetches just eigenclasses decorators def all_decorators pop_decorators end private # Fails when contracts are not included in owner class def validate! fail ContractsNotIncluded unless owner? end def owner? !!owner_class end end end end contracts-0.16.0/lib/contracts/engine/target.rb0000644000004100000410000000341213120354472021455 0ustar www-datawww-datamodule Contracts module Engine # Represents class in question class Target # Creates new instance of Target # # @param [Class] target - class in question def initialize(target) @target = target end # Enable contracts engine for target # - it is no-op if contracts engine is already enabled # - it automatically enables contracts engine for its eigenclass # - it sets owner class to target for its eigenclass # # @param [Engine::Base:Class] engine_class - type of engine to # enable (Base or Eigenclass) def apply(engine_class = Base) return if applied? apply_to_eigenclass eigenclass.class_eval do define_method(:__contracts_engine) do @__contracts_engine ||= engine_class.new(self) end end engine.set_eigenclass_owner end # Returns true if target has contracts engine already # # @return [Bool] def applied? target.respond_to?(:__contracts_engine) end # Returns contracts engine of target # # @return [Engine::Base or Engine::Eigenclass] def engine applied? && target.__contracts_engine end private attr_reader :target def apply_to_eigenclass return unless meaningless_eigenclass? self.class.new(eigenclass).apply(Eigenclass) eigenclass.extend(MethodDecorators) # FIXME; this should detect what user uses `include Contracts` or # `include Contracts;;Core` eigenclass.send(:include, Contracts) end def eigenclass Support.eigenclass_of(target) end def meaningless_eigenclass? !Support.eigenclass?(target) end end end end contracts-0.16.0/lib/contracts/engine/base.rb0000644000004100000410000000715413120354472021110 0ustar www-datawww-datamodule Contracts module Engine # Contracts engine class Base # Enable contracts engine for klass # # @param [Class] klass - target class def self.apply(klass) Engine::Target.new(klass).apply end # Returns true if klass has contracts engine # # @param [Class] klass - target class # @return [Bool] def self.applied?(klass) Engine::Target.new(klass).applied? end # Fetches contracts engine out of klass # # @param [Class] klass - target class # @return [Engine::Base or Engine::Eigenclass] def self.fetch_from(klass) Engine::Target.new(klass).engine end # Creates new instance of contracts engine # # @param [Class] klass - class that owns this engine def initialize(klass) @klass = klass end # Adds provided decorator to the engine # It validates that decorator can be added to this engine at the # moment # # @param [Decorator:Class] decorator_class # @param args - arguments for decorator def decorate(decorator_class, *args) validate! decorators << [decorator_class, args] end # Sets eigenclass' owner to klass def set_eigenclass_owner eigenclass_engine.owner_class = klass end # Fetches all accumulated decorators (both this engine and # corresponding eigenclass' engine) # It clears all accumulated decorators # # @return [ArrayOf[Decorator]] def all_decorators pop_decorators + eigenclass_engine.all_decorators end # Fetches decorators of specified type for method with name # # @param [Or[:class_methods, :instance_methods]] type - method type # @param [Symbol] name - method name # @return [ArrayOf[Decorator]] def decorated_methods_for(type, name) Array(decorated_methods[type][name]) end # Returns true if there are any decorated methods # # @return [Bool] def decorated_methods? !decorated_methods[:class_methods].empty? || !decorated_methods[:instance_methods].empty? end # Adds method decorator # # @param [Or[:class_methods, :instance_methods]] type - method type # @param [Symbol] name - method name # @param [Decorator] decorator - method decorator def add_method_decorator(type, name, decorator) decorated_methods[type][name] ||= [] decorated_methods[type][name] << decorator end # Returns nearest ancestor's engine that has decorated methods # # @return [Engine::Base or Engine::Eigenclass] def nearest_decorated_ancestor current = klass current_engine = self ancestors = current.ancestors[1..-1] while current && current_engine && !current_engine.decorated_methods? current = ancestors.shift current_engine = Engine.fetch_from(current) end current_engine end private attr_reader :klass def decorated_methods @_decorated_methods ||= { :class_methods => {}, :instance_methods => {} } end # No-op because it is safe to add decorators to normal classes def validate! end def pop_decorators decorators.tap { clear_decorators } end def eigenclass Support.eigenclass_of(klass) end def eigenclass_engine Eigenclass.lift(eigenclass, klass) end def decorators @_decorators ||= [] end def clear_decorators @_decorators = [] end end end end contracts-0.16.0/lib/contracts/invariants.rb0000644000004100000410000000317013120354472021101 0ustar www-datawww-datamodule Contracts module Invariants def self.included(base) common base end def self.extended(base) common base end def self.common(base) return if base.respond_to?(:Invariant) base.extend(InvariantExtension) end def verify_invariants!(method) return unless self.class.respond_to?(:invariants) self.class.invariants.each do |invariant| invariant.check_on(self, method) end end module InvariantExtension def invariant(name, &condition) return if ENV["NO_CONTRACTS"] invariants << Invariant.new(self, name, &condition) end def invariants @invariants ||= [] end end class Invariant def initialize(klass, name, &condition) @klass, @name, @condition = klass, name, condition end def expected "#{@name} condition to be true" end def check_on(target, method) return if target.instance_eval(&@condition) self.class.failure_callback(:expected => expected, :actual => false, :target => target, :method => method) end def self.failure_callback(data) fail InvariantError, failure_msg(data) end def self.failure_msg(data) %{Invariant violation: Expected: #{data[:expected]} Actual: #{data[:actual]} Value guarded in: #{data[:target].class}::#{Support.method_name(data[:method])} At: #{Support.method_position(data[:method])}} end end end end contracts-0.16.0/cucumber.yml0000644000004100000410000000003413120354472016154 0ustar www-datawww-datadefault: --require features contracts-0.16.0/.rubocop.yml0000644000004100000410000000570113120354472016104 0ustar www-datawww-dataAllCops: Exclude: - "tmp/**/*" # forces method defs to have params in parens Style/MethodDefParentheses: Enabled: false Style/StringLiterals: EnforcedStyle: double_quotes # changes x != nil to !x.nil? Style/NonNilCheck: Enabled: false # changes %{} to %() Style/PercentLiteralDelimiters: Enabled: false # changes lambdas to ruby 1.9-style -> # not compatible with ruby 1.8 Style/Lambda: Enabled: false # changes to new hash syntax # not compatible with ruby 1.8 Style/HashSyntax: Enabled: false # we use unused method args a lot in tests/fixtures (a quirk of this lib) Lint/UnusedMethodArgument: Enabled: false # changes x ** 2 to x**2 Style/SpaceAroundOperators: Enabled: false # doesn't allow vars starting with _ Lint/UnderscorePrefixedVariableName: Enabled: false # enforces method length of 10 lines Metrics/MethodLength: Enabled: false # forces you to document classes # TODO: try to enable this cop Style/Documentation: Enabled: false # enforces line length of 80 # TODO enable Metrics/LineLength: Enabled: false # triggered by Contract ({ :name => String, :age => Fixnum }) => nil Lint/ParenthesesAsGroupedExpression: Enabled: false # checks how much a method branches. # TODO try to get this cop enabled Metrics/AbcSize: Enabled: false # checks complexity of method # TODO try to get this cop enabled Metrics/CyclomaticComplexity: Enabled: false # checks for too many nested ifs, whiles or rescues (but not too many nested blocks) # TODO: try to get this cop enabled eventually Metrics/BlockNesting: Enabled: false # calls out class variables. # TODO: try to get this cop enabled eventually Style/ClassVars: Enabled: false # enforces class length of < 100 lines Metrics/ClassLength: Enabled: false # TODO: try to get this cop enabled eventually Metrics/PerceivedComplexity: Enabled: false # checks for duplicate methods, but contracts # provides this functionality (multi-dispatch) Lint/DuplicateMethods: Enabled: false # checks to see if you could've used attr_accessor instead. # nice in theory but noisy cop with false positives Style/TrivialAccessors: Enabled: false Style/MultilineOperationIndentation: EnforcedStyle: indented # Asks you to use %w{array of words} if possible. # Not a style I like. Style/WordArray: Enabled: false # conflicts with contracts # we define contracts like `Baz = 1` Style/ConstantName: Enabled: false # `Contract` violates this, otherwise a good cop (enforces snake_case method names) # TODO possible to get this enabled but ignore `Contract`? Style/MethodName: Enabled: false # checks for !! Style/DoubleNegation: Enabled: false # enforces < 5 params for a function. # contracts-specific (long parameter list for test) Metrics/ParameterLists: Enabled: false # Checks that braces used for hash literals have or don't have surrounding space depending on configuration. Style/SpaceInsideHashLiteralBraces: Enabled: false # TODO enable Style/SpecialGlobalVars: Enabled: false contracts-0.16.0/contracts.gemspec0000644000004100000410000000115613120354472017177 0ustar www-datawww-datarequire File.expand_path(File.join(__FILE__, "../lib/contracts/version")) Gem::Specification.new do |s| s.name = "contracts" s.version = Contracts::VERSION s.summary = "Contracts for Ruby." s.description = "This library provides contracts for Ruby. Contracts let you clearly express how your code behaves, and free you from writing tons of boilerplate, defensive code." s.author = "Aditya Bhargava" s.email = "bluemangroupie@gmail.com" s.files = `git ls-files`.split("\n") s.homepage = "http://github.com/egonSchiele/contracts.ruby" s.license = "BSD-2-Clause" end contracts-0.16.0/.gitignore0000644000004100000410000000004613120354472015617 0ustar www-datawww-data*.swp *.swo .* Gemfile.lock tmp/aruba contracts-0.16.0/LICENSE0000644000004100000410000000242613120354472014640 0ustar www-datawww-dataCopyright (c) 2012-2016 Aditya Bhargava All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. contracts-0.16.0/benchmarks/0000755000004100000410000000000013120354472015744 5ustar www-datawww-datacontracts-0.16.0/benchmarks/hash.rb0000644000004100000410000000275513120354472017225 0ustar www-datawww-datarequire "./lib/contracts" require "benchmark" require "rubygems" require "method_profiler" require "ruby-prof" include Contracts def add opts opts[:a] + opts[:b] end Contract ({ :a => Num, :b => Num}) => Num def contracts_add opts opts[:a] + opts[:b] end def explicit_add opts a = opts[:a] b = opts[:b] fail unless a.is_a?(Numeric) fail unless b.is_a?(Numeric) c = a + b fail unless c.is_a?(Numeric) c end def benchmark Benchmark.bm 30 do |x| x.report "testing add" do 1_000_000.times do |_| add(:a => rand(1000), :b => rand(1000)) end end x.report "testing contracts add" do 1_000_000.times do |_| contracts_add(:a => rand(1000), :b => rand(1000)) end end end end def profile profilers = [] profilers << MethodProfiler.observe(Contract) profilers << MethodProfiler.observe(Object) profilers << MethodProfiler.observe(Contracts::MethodDecorators) profilers << MethodProfiler.observe(Contracts::Decorator) profilers << MethodProfiler.observe(Contracts::Support) profilers << MethodProfiler.observe(UnboundMethod) 10_000.times do |_| contracts_add(:a => rand(1000), :b => rand(1000)) end profilers.each { |p| puts p.report } end def ruby_prof RubyProf.start 100_000.times do |_| contracts_add(:a => rand(1000), :b => rand(1000)) end result = RubyProf.stop printer = RubyProf::FlatPrinter.new(result) printer.print(STDOUT) end benchmark profile ruby_prof if ENV["FULL_BENCH"] # takes some time contracts-0.16.0/benchmarks/wrap_test.rb0000644000004100000410000000172513120354472020306 0ustar www-datawww-datarequire "benchmark" module Wrapper def self.extended(klass) klass.class_eval do @@methods = {} def self.methods @@methods end def self.set_method k, v @@methods[k] = v end end end def method_added name return if methods.include?(name) puts "#{name} added" set_method(name, instance_method(name)) class_eval %{ def #{name}(*args) self.class.methods[#{name.inspect}].bind(self).call(*args) end }, __FILE__, __LINE__ + 1 end end class NotWrapped def add a, b a + b end end class Wrapped extend ::Wrapper def add a, b a + b end end w = Wrapped.new nw = NotWrapped.new # p w.add(1, 4) # exit # 30 is the width of the output column Benchmark.bm 30 do |x| x.report "wrapped" do 100_000.times do |_| w.add(rand(1000), rand(1000)) end end x.report "not wrapped" do 100_000.times do |_| nw.add(rand(1000), rand(1000)) end end end contracts-0.16.0/benchmarks/bench.rb0000644000004100000410000000256213120354472017355 0ustar www-datawww-datarequire "./lib/contracts" require "benchmark" require "rubygems" require "method_profiler" require "ruby-prof" include Contracts def add a, b a + b end Contract Num, Num => Num def contracts_add a, b a + b end def explicit_add a, b fail unless a.is_a?(Numeric) fail unless b.is_a?(Numeric) c = a + b fail unless c.is_a?(Numeric) c end def benchmark Benchmark.bm 30 do |x| x.report "testing add" do 1_000_000.times do |_| add(rand(1000), rand(1000)) end end x.report "testing contracts add" do 1_000_000.times do |_| contracts_add(rand(1000), rand(1000)) end end end end def profile profilers = [] profilers << MethodProfiler.observe(Contract) profilers << MethodProfiler.observe(Object) profilers << MethodProfiler.observe(Contracts::MethodDecorators) profilers << MethodProfiler.observe(Contracts::Decorator) profilers << MethodProfiler.observe(Contracts::Support) profilers << MethodProfiler.observe(UnboundMethod) 10_000.times do |_| contracts_add(rand(1000), rand(1000)) end profilers.each { |p| puts p.report } end def ruby_prof RubyProf.start 100_000.times do |_| contracts_add(rand(1000), rand(1000)) end result = RubyProf.stop printer = RubyProf::FlatPrinter.new(result) printer.print(STDOUT) end benchmark profile ruby_prof if ENV["FULL_BENCH"] # takes some time contracts-0.16.0/benchmarks/invariants.rb0000644000004100000410000000362413120354472020454 0ustar www-datawww-datarequire "./lib/contracts" require "benchmark" require "rubygems" require "method_profiler" require "ruby-prof" class Obj include Contracts attr_accessor :value def initialize value @value = value end Contract Num, Num => Num def contracts_add a, b a + b end end class ObjWithInvariants include Contracts include Contracts::Invariants invariant(:value_not_nil) { value != nil } invariant(:value_not_string) { !value.is_a?(String) } attr_accessor :value def initialize value @value = value end Contract Num, Num => Num def contracts_add a, b a + b end end def benchmark obj = Obj.new(3) obj_with_invariants = ObjWithInvariants.new(3) Benchmark.bm 30 do |x| x.report "testing contracts add" do 1_000_000.times do |_| obj.contracts_add(rand(1000), rand(1000)) end end x.report "testing contracts add with invariants" do 1_000_000.times do |_| obj_with_invariants.contracts_add(rand(1000), rand(1000)) end end end end def profile obj_with_invariants = ObjWithInvariants.new(3) profilers = [] profilers << MethodProfiler.observe(Contract) profilers << MethodProfiler.observe(Object) profilers << MethodProfiler.observe(Contracts::Support) profilers << MethodProfiler.observe(Contracts::Invariants) profilers << MethodProfiler.observe(Contracts::Invariants::InvariantExtension) profilers << MethodProfiler.observe(UnboundMethod) 10_000.times do |_| obj_with_invariants.contracts_add(rand(1000), rand(1000)) end profilers.each { |p| puts p.report } end def ruby_prof RubyProf.start obj_with_invariants = ObjWithInvariants.new(3) 100_000.times do |_| obj_with_invariants.contracts_add(rand(1000), rand(1000)) end result = RubyProf.stop printer = RubyProf::FlatPrinter.new(result) printer.print(STDOUT) end benchmark profile ruby_prof if ENV["FULL_BENCH"] # takes some time contracts-0.16.0/benchmarks/io.rb0000644000004100000410000000247313120354472016706 0ustar www-datawww-datarequire "./lib/contracts" require "benchmark" require "rubygems" require "method_profiler" require "ruby-prof" require "open-uri" include Contracts def download url open("http://www.#{url}/").read end Contract String => String def contracts_download url open("http://www.#{url}").read end @urls = %w{google.com bing.com} def benchmark Benchmark.bm 30 do |x| x.report "testing download" do 100.times do |_| download(@urls.sample) end end x.report "testing contracts download" do 100.times do |_| contracts_download(@urls.sample) end end end end def profile profilers = [] profilers << MethodProfiler.observe(Contract) profilers << MethodProfiler.observe(Object) profilers << MethodProfiler.observe(Contracts::MethodDecorators) profilers << MethodProfiler.observe(Contracts::Decorator) profilers << MethodProfiler.observe(Contracts::Support) profilers << MethodProfiler.observe(UnboundMethod) 10.times do |_| contracts_download(@urls.sample) end profilers.each { |p| puts p.report } end def ruby_prof RubyProf.start 10.times do |_| contracts_download(@urls.sample) end result = RubyProf.stop printer = RubyProf::FlatPrinter.new(result) printer.print(STDOUT) end benchmark profile ruby_prof if ENV["FULL_BENCH"] # takes some time contracts-0.16.0/CHANGELOG.markdown0000644000004100000410000001657013120354472016673 0ustar www-datawww-data## v0.16.0 - **Support for Ruby 1.8 has been discontinued** - [Corey Farwell](https://github.com/frewsxcv) [#256](https://github.com/egonSchiele/contracts.ruby/pull/256) - Enhancement: Add a `Contracts::Attrs` module containing attribute w/ contracts utilities - [Corey Farwell](https://github.com/frewsxcv) [#255](https://github.com/egonSchiele/contracts.ruby/pull/255) - Bugfix: Fix StrictHash contract for extra keys - [Maciej Malecki](https://github.com/smt116) [#254](https://github.com/egonSchiele/contracts.ruby/pull/254) ## v0.15.0 - Bugfix: Func contract's return value isn't enforced with blocks - [Piotr Szmielew](https://github.com/esse) [#251](https://github.com/egonSchiele/contracts.ruby/pull/251) - Bugfx: Fix contracts used in AR-models - [Gert Goet](https://github.com/eval) [#237](https://github.com/egonSchiele/contracts.ruby/pull/237) ## v0.14.0 - Enhancement: Add StrictHash contract - [Fyodor](https://github.com/cbrwizard) [#236](https://github.com/egonSchiele/contracts.ruby/pull/236) - Bugfix: dont fail if something other than a hash is passed to a KeywordArgs - [Dan Padilha](https://github.com/dpad) [#234](https://github.com/egonSchiele/contracts.ruby/pull/234) - LICENSE ADDED: Simplified BSD (same as what is specified in the readme) - [Charles Dale](https://github.com/chuckd) [#233](https://github.com/egonSchiele/contracts.ruby/pull/233) - Bugfix: fix constant looking when including a module that includes contracts (requires removing the check to see if contracts is already included) - [Aditya Bhargava](https://github.com/egonSchiele) [#232](https://github.com/egonSchiele/contracts.ruby/pull/232) - Bugfix for err case when KeywordArgs and Proc are used together - [Aditya Bhargava](https://github.com/egonSchiele) [#230](https://github.com/egonSchiele/contracts.ruby/pull/230) - Enhancement: Add DescendantOf contract - [Miguel Palhas](https://github.com/naps62) [#227](https://github.com/egonSchiele/contracts.ruby/pull/227) ## v0.13.0 - Enhancement: Add support for Ruby 2.3 - [Oleksii Fedorov](https://github.com/waterlink) [#216](https://github.com/egonSchiele/contracts.ruby/pull/216) - Enhancement: Added Int, Nat and NatPos builtin contracts - [Simon George](https://github.com/sfcgeorge) [#212](https://github.com/egonSchiele/contracts.ruby/pull/212) - Bugfix: Allow contracts on singleton of subclass - [Oleksii Federov](https://github.com/waterlink) [#211](https://github.com/egonSchiele/contracts.ruby/pull/211) ## v0.12.0 - Feature: add `Regexp` validator - [Gert Goet](https://github.com/eval) [#196](https://github.com/egonSchiele/contracts.ruby/pull/196) - Docs: bootstrap cucumber/aruba/relish setup - [Oleksii Fedorov](https://github.com/waterlink) [#195](https://github.com/egonSchiele/contracts.ruby/pull/195) - Bugfix: allow to `extend` module, that has `Contracts` or `Contracts::Core` included without harming current module/class `Contracts` functionality, see: [#176](https://github.com/egonSchiele/contracts.ruby/issues/176) - [Oleksii Fedorov](https://github.com/waterlink) [#198](https://github.com/egonSchiele/contracts.ruby/pull/198) - Enhancement: add `include Contracts::Builtin` to allow users to use builtin contracts without `Contracts::` prefix together with `include Contracts::Core` - [PikachuEXE](https://github.com/PikachuEXE) [#199](https://github.com/egonSchiele/contracts.ruby/pull/199) ## v0.11.0 - Enhancement: add `include Contracts::Core` that doesn't pollute the namespace as much as `include Contracts` - [Oleksii Federov](https://github.com/waterlink) [#185](https://github.com/egonSchiele/contracts.ruby/pull/185) - Bugfix: fail if a non-hash is provided to a `HashOf` contract - [Abe Voelker](https://github.com/abevoelker) [#190](https://github.com/egonSchiele/contracts.ruby/pull/190) - Bugfix: bugfix for using varargs and `Maybe[Proc]` together - [Adit Bhargava](https://github.com/egonSchiele) [#188](https://github.com/egonSchiele/contracts.ruby/pull/188) - Bugfix: make KeywordArgs fail if unexpected keys are passed in - [Abe Voelker](https://github.com/abevoelker) [#187](https://github.com/egonSchiele/contracts.ruby/pull/187) - Feature: range contract added - [Oleksii Fedorov](https://github.com/waterlink) [#184](https://github.com/egonSchiele/contracts.ruby/pull/184) - Feature: enum contract added - [Dennis Günnewig](https://github.com/dg-ratiodata) [#181](https://github.com/egonSchiele/contracts.ruby/pull/181) ## v0.10.1 - Enhancement: make `@pattern_match` instance variable not render ruby warning. Required to use new aruba versions in rspec tests - [Dennis Günnewig](https://github.com/dg-ratiodata) [#179](https://github.com/egonSchiele/contracts.ruby/pull/179) ## v0.10 - Bugfix: make `Maybe[Proc]` work correctly - [Simon George](https://github.com/sfcgeorge) [#142](https://github.com/egonSchiele/contracts.ruby/pull/142) - Bugfix: make `Func` contract verified when used as return contract - [Rob Rosenbaum](https://github.com/robnormal) [#145](https://github.com/egonSchiele/contracts.ruby/pull/145) - Bugfix: make `Pos`, `Neg` and `Nat` contracts handle non-numeric values correctly - [Matt Griffin](https://github.com/betamatt) and [Gavin Sinclair](https://github.com/gsinclair) [#147](https://github.com/egonSchiele/contracts.ruby/pull/147) [#173](https://github.com/egonSchiele/contracts.ruby/pull/173) - Enhancement: reduce user class pollution through introduction of contracts engine - [Oleksii Fedorov](https://github.com/waterlink) [#141](https://github.com/egonSchiele/contracts.ruby/pull/141) - Feature: add builtin `KeywordArgs` and `Optional` contracts for keyword arguments handling - [Oleksii Fedorov](https://github.com/waterlink) [#151](https://github.com/egonSchiele/contracts.ruby/pull/151) - Feature: recognize module as a class contract - [Oleksii Fedorov](https://github.com/waterlink) [#153](https://github.com/egonSchiele/contracts.ruby/pull/153) - Feature: custom validators with `Contract.override_validator` - [Oleksii Fedorov](https://github.com/waterlink) [#159](https://github.com/egonSchiele/contracts.ruby/pull/159) - Feature: add builtin `RangeOf[...]` contract - [Gavin Sinclair](https://github.com/gsinclair) [#171](https://github.com/egonSchiele/contracts.ruby/pull/171) ## v0.9 - MAJOR fix in pattern-matching: If the return contract for a pattern-matched function fails, it should NOT try the next pattern-match function. Pattern-matching is only for params, not return values. - raise an error if multiple defns have the same contract for pattern matching. - New syntax for functions with no input params (the old style still works) Old way: ```ruby Contract nil => 1 def one ``` New way: ``` Contract 1 def one ``` - Prettier HashOf contract can now be written like this: `HashOf[Num => String]` - Add `SetOf` contract - various small fixes ## v0.8 - code refactored (very slight loss of performance, big increase in readability) - fail when defining a contract on a module without `include Contracts::Modules` - fixed several bugs in argument parsing, functions with complex params get contracts applied correctly now. - added rubocop to ci. - if a contract is set on a protected method, it should not become public. - fixed pattern matching when the multiple definitions of functions have different arities. - couple of new built-in contracts: Nat, Eq. - changed `Invariant` to `invariant`: `invariant(:day) { 1 <= day && day <= 31 }` - prettier error messages (`Contracts::Num` is now just `Num`, for example) - support for yard-contracts contracts-0.16.0/TUTORIAL.md0000644000004100000410000006467513120354472015436 0ustar www-datawww-data# The contracts.ruby tutorial ## Introduction contracts.ruby brings code contracts to the Ruby language. Code contracts allow you make some assertions about your code, and then checks them to make sure they hold. This lets you - catch bugs faster - make it very easy to catch certain types of bugs - make sure that the user gets proper messaging when a bug occurs. ## Installation gem install contracts ## Basics A simple example: ```ruby Contract Contracts::Num, Contracts::Num => Contracts::Num def add(a, b) a + b end ``` Here, the contract is `Contract Num, Num => Num`. This says that the `add` function takes two numbers and returns a number. Copy this code into a file and run it: ```ruby require 'contracts' class Math include Contracts::Core Contract Contracts::Num, Contracts::Num => Contracts::Num def self.add(a, b) a + b end end puts Math.add(1, "foo") ``` You'll see a detailed error message like so: ./contracts.rb:60:in `failure_callback': Contract violation: (RuntimeError) Expected: Contracts::Num, Actual: "foo" Value guarded in: Object::add With Contract: Contracts::Num, Contracts::Num At: foo.rb:6 That tells you that your contract was violated! `add` expected a `Num`, and got a string (`"foo"`) instead. By default, an exception is thrown when a contract fails. This can be changed to do whatever you want. More on this later. You can also see the contract for a function with the `functype` method: functype(:add) => "add :: Num, Num => Num" This can be useful if you're in a REPL and want to figure out how a function should be used. ## Built-in Contracts `Num` is one of the built-in contracts that contracts.ruby comes with. The built-in contracts are in the `Contracts` namespace. The easiest way to use them is to include the `Contracts::Builtin` module in your class/module. contracts.ruby comes with a lot of built-in contracts, including the following: * Basic types * [`Num`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Num) – checks that the argument is `Numeric` * [`Pos`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Pos) – checks that the argument is a positive number * [`Neg`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Neg) – checks that the argument is a negative number * [`Int`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Int) – checks that the argument is an integer * [`Nat`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Nat) – checks that the argument is a natural number (>= 0) * [`NatPos`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/NatPos) – checks that the argument is a positive natural number (> 0) * [`Bool`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Bool) – checks that the argument is `true` or `false` * [`Any`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Any) – Passes for any argument. Use when the argument has no constraints. * [`None`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/None) – Fails for any argument. Use when the method takes no arguments. * Logical combinations * [`Maybe`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Maybe) – specifies that a value _may be_ nil, e.g. `Maybe[String]` (equivalent to `Or[String,nil]`) * [`Or`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Or) – passes if any of the given contracts pass, e.g. `Or[Fixnum, Float]` * [`Xor`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Xor) – passes if exactly one of the given contracts pass, e.g. `Xor[Fixnum, Float]` * [`And`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/And) – passes if all contracts pass, e.g. `And[Nat, -> (n) { n.even? }]` * [`Not`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Not) – passes if all contracts fail for the given argument, e.g. `Not[nil]` * Collections * [`ArrayOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/ArrayOf) – checks that the argument is an array, and all elements pass the given contract, e.g. `ArrayOf[Num]` * [`SetOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/SetOf) – checks that the argument is a set, and all elements pass the given contract, e.g. `SetOf[Num]` * [`HashOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/HashOf) – checks that the argument is a hash, and all keys and values pass the given contract, e.g. `HashOf[Symbol => String]` or `HashOf[Symbol,String]` * [`StrictHash`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/StrictHash) – checks that the argument is a hash, and every key passed is present in the given contract, e.g. `StrictHash[{ :description => String, :number => Fixnum }]` * [`RangeOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/RangeOf) – checks that the argument is a range whose elements (#first and #last) pass the given contract, e.g. `RangeOf[Date]` * [`Enum`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Enum) – checks that the argument is part of a given collection of objects, e.g. `Enum[:a, :b, :c]` * Keyword arguments * [`KeywordArgs`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/KeywordArgs) – checks that the argument is an options hash, and all required keyword arguments are present, and all values pass their respective contracts, e.g. `KeywordArgs[:number => Num, :description => Optional[String]]` * [`Optional`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Optional) – checks that the keyword argument is either not present or pass the given contract, can not be used outside of `KeywordArgs` contract, e.g. `Optional[Num]` * Duck typing * [`RespondTo`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/RespondTo) – checks that the argument responds to all of the given methods, e.g. `RespondTo[:password, :credit_card]` * [`Send`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Send) – checks that all named methods return a truthy value, e.g. `Send[:valid?]` * Miscellaneous * [`Exactly`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Exactly) – checks that the argument has the given type, not accepting sub-classes, e.g. `Exactly[Numeric]`. * [`Eq`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Eq) – checks that the argument is precisely equal to the given value, e.g. `Eq[String]` matches the class `String` and not a string instance. * [`Func`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Func) – specifies the contract for a proc/lambda e.g. `Contract ArrayOf[Num], Func[Num => Num] => ArrayOf[Num]`. See section "Contracts On Functions". To see all the built-in contracts and their full descriptions, check out the [RDoc](http://rubydoc.info/gems/contracts/Contracts/Builtin). It is recommended to use shortcut for referring builtin contracts: ```ruby # define shortcut somewhere at the top level of your codebase: C = Contracts # and use it: Contract C::Maybe[C::Num], String => C::Num ``` Shortcut name should not be necessary `C`, can be anything that you are comfort with while typing and anything that does not conflict with libraries you use. All examples after this point assume you have chosen a shortcut as `C::`. If you are sure, that builtin contracts will not nameclash with your own code and libraries you may use, then you can include all builtin contracts in your class/module: ```ruby class Example include Contracts::Core include Contracts::Builtin Contract Maybe[Num], Or[Float, String] => Bool def complicated_algorithm(a, b) # ... end end ``` ## More Examples ### Hello, World ```ruby Contract String => nil def hello(name) puts "hello, #{name}!" end ``` You always need to specify a contract for the return value. In this example, `hello` doesn't return anything, so the contract is `nil`. Now you know that you can use a constant like `nil` as the end of a contract. Valid values for a contract are: - the name of a class (like `String` or `Fixnum`) - a constant (like `nil` or `1`) - a `Proc` that takes a value and returns true or false to indicate whether the contract passed or not - a class that responds to the `valid?` class method (more on this later) - an instance of a class that responds to the `valid?` method (more on this later) ### A Double Function ```ruby Contract C::Or[Fixnum, Float] => C::Or[Fixnum, Float] def double(x) 2 * x end ``` Sometimes you want to be able to choose between a few contracts. `Or` takes a variable number of contracts and checks the argument against all of them. If it passes for any of the contracts, then the `Or` contract passes. This introduces some new syntax. One of the valid values for a contract is an instance of a class that responds to the `valid?` method. This is what `Or[Fixnum, Float]` is. The longer way to write it would have been: ```ruby Contract C::Or.new(Fixnum, Float) => C::Or.new(Fixnum, Float) ``` All the built-in contracts have overridden the square brackets (`[]`) to give the same functionality. So you could write ```ruby Contract C::Or[Fixnum, Float] => C::Or[Fixnum, Float] ``` or ```ruby Contract C::Or.new(Fixnum, Float) => C::Or.new(Fixnum, Float) ``` whichever you prefer. They both mean the same thing here: make a new instance of `Or` with `Fixnum` and `Float`. Use that instance to validate the argument. ### A Product Function ```ruby Contract C::ArrayOf[C::Num] => C::Num def product(vals) total = 1 vals.each do |val| total *= val end total end ``` This contract uses the `ArrayOf` contract. Here's how `ArrayOf` works: it takes a contract. It expects the argument to be a list. Then it checks every value in that list to see if it satisfies that contract. ```ruby # passes product([1, 2, 3, 4]) # fails product([1, 2, 3, "foo"]) ``` ### Another Product Function ```ruby Contract C::Args[C::Num] => C::Num def product(*vals) total = 1 vals.each do |val| total *= val end total end ``` This function uses varargs (`*args`) instead of an array. To make a contract on varargs, use the `Args` contract. It takes one contract as an argument and uses it to validate every element passed in through `*args`. So for example, `Args[Num]` means they should all be numbers. `Args[Or[Num, String]]` means they should all be numbers or strings. `Args[Any]` means all arguments are allowed (`Any` is a contract that passes for any argument). ### Contracts On Arrays If an array is one of the arguments and you know how many elements it's going to have, you can put a contract on it: ```ruby # a function that takes an array of two elements...a person's age and a person's name. Contract [C::Num, String] => nil def person(data) p data end ``` If you don't know how many elements it's going to have, use `ArrayOf`. ### Contracts On Hashes Here's a contract that requires a Hash. We can put contracts on each of the keys: ```ruby # note the parentheses around the hash; without those you would get a syntax error Contract ({ :age => C::Num, :name => String }) => nil def person(data) p data end ``` Then if someone tries to call the function with bad data, it will fail: ```ruby # error: age can't be nil! person({:name => "Adit", :age => nil}) ``` You don't need to put a contract on every key. So this call would succeed: ```ruby person({:name => "Adit", :age => 42, :foo => "bar"}) ``` even though we don't specify a type for `:foo`. If you need this check though, use `StrictHash` instead. Peruse this contract on the keys and values of a Hash. ```ruby Contract C::HashOf[Symbol, C::Num] => C::Num def give_largest_value(hsh) hsh.values.max end ``` Which you use like so: ```ruby # succeeds give_largest_value(a: 1, b: 2, c: 3) # returns 3 # fails give_largest_value("a" => 1, 2 => 2, c: 3) ``` ### Contracts On Strings When you want a contract to match not just any string (i.e. `Contract String => nil`), you can use regular expressions: ```ruby Contract /World|Mars/i => nil def greet(name) puts "Hello #{name}!" end ``` Using logical combinations you can combine existing definitions, instead of writing 1 big regular expression: ```ruby Contract C::And[default_mail_regexp, /#{AppConfig.domain}\z/] => nil def send_admin_invite(email) ``` ### Contracts On Keyword Arguments ruby 2.0+, but can be used for normal hashes too, when keyword arguments are not available Lets say you are writing a simple function and require a bunch of keyword arguments: ```ruby def connect(host, port:, user:, password:) ``` You can of course put `Hash` contract on it: ```ruby Contract String, { :port => C::Num, :user => String, :password => String } => Connection def connect(host, port:, user:, password:) ``` But this will not quite work if you want to have a default values: ```ruby Contract String, { :port => C::Num, :user => String, :password => String } => Connection def connect(host, port: 5000, user:, password:) # ... end # No value is passed for port connect("example.org", user: "me", password: "none") ``` Results in: ``` ContractError: Contract violation for argument 2 of 2: Expected: {:port=>Num, :user=>String, :password=>String}, Actual: {:user=>"me", :password=>"none"} Value guarded in: Object::connect With Contract: String, Hash => Connection At: (irb):12 ``` This can be fixed with contract `{ :port => C::Maybe[C::Num], ... }`, but that will allow `nil` to be passed in, which is not the original intent. So that is where `KeywordArgs` and `Optional` contracts jump in: ```ruby Contract String, C::KeywordArgs[ :port => C::Optional[C::Num], :user => String, :password => String ] => Connection def connect(host, port: 5000, user:, password:) ``` It looks just like the hash contract, but wrapped in `KeywordArgs` contract. Notice the usage of `Optional` contract - this way you specify that `:port` argument is optional. And it will not fail, when you omit this argument, but it will fail when you pass in `nil`. ### Contracts On Functions Lets say you are writing a simple map function: ```ruby def map(arr, func) ``` `map` takes an array, and a function. Suppose you want to add a contract to this function. You could try this: ```ruby Contract C::ArrayOf[C::Any], Proc => C::ArrayOf[C::Any] def map(arr, func) ``` This says that the second argument should be a `Proc`. You can call the function like so: ```ruby p map([1, 2, 3], lambda { |x| x + 1 }) # works ``` But suppose you want to have a contract on the Proc too! Suppose you want to make sure that the Proc returns a number. Use the `Func` contract. `Func` takes a contract as its argument, and uses that contract on the function that you pass in. Here's a `map` function that requires an array of numbers, and a function that takes a number and returns a number: ```ruby Contract C::ArrayOf[C::Num], C::Func[C::Num => C::Num] => C::ArrayOf[C::Num] def map(arr, func) ret = [] arr.each do |x| ret << func[x] end ret end ``` Earlier, we used `Proc`, which just says "make sure the second variable is a Proc". Now we are using `Func[Num => Num]`, which says "make sure the second variable is a Proc that takes a number and returns a number". Better! Try this map function with these two examples: ```ruby p map([1, 2, 3], lambda { |x| x + 1 }) # works p map([1, 2, 3], lambda { |x| "oops" }) # fails, the lambda returns a string. ``` The above examples showed a method accepting a `Proc` as the last argument, but the same contract works on methods that accept a block: ```ruby def map(arr, &block) ``` NOTE: This is not valid: ```ruby Contract C::ArrayOf[C::Num], C::Func => C::ArrayOf[C::Num] def map(arr, &func) ``` Here I am using `Func` without specifying a contract, like `Func[Num => Num]`. That's not a legal contract. If you just want to validate that the second argument is a proc, use `Proc`. ### Returning Multiple Values Treat the return value as an array. For example, here's a function that returns two numbers: ```ruby Contract C::Num => [C::Num, C::Num] def mult(x) return x, x+1 end ``` ## Synonyms For Contracts If you use a contract a lot, it's a good idea to give it a meaningful synonym that tells the reader more about what your code returns. For example, suppose you have many functions that return a `Hash` or `nil`. If a `Hash` is returned, it contains information about a person. Your contact might look like this: ```ruby Contract String => C::Or[Hash, nil] def some_func(str) ``` You can make your contract more meaningful with a synonym: ```ruby # the synonym Person = Or[Hash, nil] # use the synonym here Contract String => Person def some_func(str) ``` Now you can use `Person` wherever you would have used `Or[Hash, nil]`. Your code is now cleaner and more clearly says what the function is doing. ## Defining Your Own Contracts Contracts are very easy to define. To re-iterate, there are 5 kinds of contracts: - the name of a class (like `String` or `Fixnum`) - a constant (like `nil` or `1`) - a `Proc` that takes a value and returns true or false to indicate whether the contract passed or not - a class that responds to the `valid?` class method (more on this later) - an instance of a class that responds to the `valid?` method (more on this later) The first two don't need any extra work to define: you can just use any constant or class name in your contract and it should just work. Here are examples for the rest: ### A Proc ```ruby Contract lambda { |x| x.is_a? Numeric } => C::Num def double(x) ``` The lambda takes one parameter: the argument that is getting passed to the function. It checks to see if it's a `Numeric`. If it is, it returns true. Otherwise it returns false. It's not good practice to write a lambda right in your contract...if you find yourself doing it often, write it as a class instead: ### A Class With `valid?` As a Class Method Here's how the `Num` class is defined. It does exactly what the `lambda` did in the previous example: ```ruby class Num def self.valid? val val.is_a? Numeric end end ``` The `valid?` class method takes one parameter: the argument that is getting passed to the function. It returns true or false. ### A Class With `valid?` As an Instance Method Here's how the `Or` class is defined: ```ruby class Or < CallableClass def initialize(*vals) @vals = vals end def valid?(val) @vals.any? do |contract| res, _ = Contract.valid?(val, contract) res end end end ``` The `Or` contract takes a sequence of contracts, and passes if any of them pass. It uses `Contract.valid?` to validate the value against the contracts. This class inherits from `CallableClass`, which allows us to use `[]` when using the class: ```ruby Contract C::Or[Fixnum, Float] => C::Num def double(x) 2 * x end ``` Without `CallableClass`, we would have to use `.new` instead: ```ruby Contract C::Or.new(Fixnum, Float) => C::Num def double(x) # etc ``` You can use `CallableClass` in your own contracts to make them callable using `[]`. ## Customizing Error Messages When a contract fails, part of the error message prints the contract: ... Expected: Contracts::Num, ... You can customize this message by overriding the `to_s` method on your class or proc. For example, suppose we overrode `Num`'s `to_s` method: ```ruby def Num.to_s "a number please" end ``` Now the error says: ... Expected: a number please, ... ## Failure Callbacks Supposing you don't want contract failures to become exceptions. You run a popular website, and when there's a contract exception you would rather log it and continue than throw an exception and break your site. contracts.ruby provides a failure callback that gets called when a contract fails. For example, here we log every failure instead of raising an error: ```ruby Contract.override_failure_callback do |data| puts "You had an error" puts failure_msg(data) end ``` `failure_msg` is a function that prints out information about the failure. Your failure callback gets a hash with the following values: { :arg => the argument to the method, :contract => the contract that got violated, :class => the method's class, :method => the method, :contracts => the contract object } If your failure callback returns `false`, the method that the contract is guarding will not be called (the default behaviour). ## Providing your own custom validators This can be done with `Contract.override_validator`: ```ruby # Make contracts accept all RSpec doubles Contract.override_validator(:class) do |contract| lambda do |arg| arg.is_a?(RSpec::Mocks::Double) || arg.is_a?(contract) end end ``` The block you provide should always return lambda accepting one argument - validated argument. Block itself accepts contract as an argument. Possible validator overrides: - `override_validator(MyCustomContract)` - allows to add some special behaviour for custom contracts, - `override_validator(Proc)` - e.g. `lambda { true }`, - `override_validator(Array)` - e.g. `[C::Num, String]`, - `override_validator(Hash)` - e.g. `{ :a => C::Num, :b => String }`, - `override_validator(Range)` - e.g. `(1..10)`, - `override_validator(Regexp)` - e.g. `/foo/`, - `override_validator(Contracts::Args)` - e.g. `C::Args[C::Num]`, - `override_validator(Contracts::Func)` - e.g. `C::Func[C::Num => C::Num]`, - `override_validator(:valid)` - allows to override how contracts that respond to `:valid?` are handled, - `override_validator(:class)` - allows to override how class/module contract constants are handled, - `override_validator(:default)` - otherwise, raw value contracts. Default validators can be found here: [lib/contracts/validators.rb](https://github.com/egonSchiele/contracts.ruby/blob/master/lib/contracts/validators.rb). ## Contracts with attributes You can include the `Contracts::Attrs` module in your class/module to get access to attribute utilities: - `attr_reader_with_contract ..., ` - Wraps `attr_reader`, validates contract upon 'getting' - `attr_writer_with_contract ..., ` - Wraps `attr_writer`, validates contract upon 'setting' - `attr_accessor_with_contract ..., ` - Wraps `attr_accessor`, validates contract upon 'getting' or 'setting' ### Example ```ruby class Person include Contracts::Core include Contracts::Attrs attr_accessor_with_contract :name, String end person = Person.new person.name = 'Jane' person.name = 1.4 # This results in a contract error! ``` ## Disabling contracts If you want to disable contracts, set the `NO_CONTRACTS` environment variable. This will disable contracts and you won't have a performance hit. Pattern matching will still work if you disable contracts in this way! With NO_CONTRACTS only pattern-matching contracts are defined. ## Method overloading You can use contracts for method overloading! This is commonly called "pattern matching" in functional programming languages. For example, here's a factorial function without method overloading: ```ruby Contract C::Num => C::Num def fact x if x == 1 x else x * fact(x - 1) end end ``` Here it is again, re-written with method overloading: ```ruby Contract 1 => 1 def fact x x end Contract C::Num => C::Num def fact x x * fact(x - 1) end ``` For an argument, each function will be tried in order. The first function that doesn't raise a `ContractError` will be used. So in this case, if x == 1, the first function will be used. For all other values, the second function will be used. This allows you write methods more declaratively, rather than using conditional branching. This feature is not only useful for recursion; you can use it to keep parallel use cases separate: ```ruby Contract lambda{|n| n < 12 } => Ticket def get_ticket(age) ChildTicket.new(age: age) end Contract lambda{|n| n >= 12 } => Ticket def get_ticket(age) AdultTicket.new(age: age) end ``` Note that the second `get_ticket` contract above could have been simplified to: ```ruby Contract C::Num => Ticket ``` This is because the first contract eliminated the possibility of `age` being less than 12. However, the simpler contract is less explicit; you may want to "spell out" the age condition for clarity, especially if the method is overloaded with many contracts. ## Contracts in modules Usage is the same as contracts in classes: ```ruby module M include Contracts::Core Contract String => String def self.parse # do some hard parsing end end ``` ## Invariants Invariants are conditions on objects that should always hold. If after any method call on given object, any of the Invariants fails, then Invariant violation error will be generated. **NOTE**: Only methods with contracts will be affected. A simple example: ```ruby class MyBirthday < Struct.new(:day, :month) include Contracts::Core include Contracts::Invariants invariant(:day) { 1 <= day && day <= 31 } invariant(:month) { 1 <= month && month <= 12 } Contract C::None => Fixnum def silly_next_day! self.day += 1 end end birthday = MyBirthday.new(31, 12) birthday.silly_next_day! ``` If you run it, last line will generate invariant violation: ```ruby ./invariant.rb:38:in `failure_callback': Invariant violation: (RuntimeError) Expected: day condition to be true Actual: false Value guarded in: MyBirthday::silly_next_day! At: main.rb:9 ``` Which means, that after `#silly_next_day!` all checks specified in `invariant` statement will be verified, and if at least one fail, then invariant violation error will be raised. ## Using contracts within your own code contracts.ruby is obviously designed to check method parameters and return values. But if you want to check whether some other data obeys a contract, you can use `Contract.valid?(value, contract)`. For instance: ```ruby data = parse(user_input) unless Contract.valid?(data, HashOf[String,Nat]) raise UserInputError.new(user_input) end ``` ## Auto-generate documentation using contracts If you are generating documentation for your code with [YARD](http://yardoc.org/), check out [yard-contracts](https://github.com/sfcgeorge/yard-contracts). It will automatically annotate your functions with contracts information. Instead of documenting each parameter for a function yourself, you can just add a contract and yard-contracts will generate the documentation for you! ## Misc Please submit any bugs [here](https://github.com/egonSchiele/contracts.ruby/issues) and I'll try to get them resolved ASAP! See any mistakes in this tutorial? I try to make it bug-free, but they can creep in. [File an issue](https://github.com/egonSchiele/contracts.ruby/issues). If you're using the library, please [let me know](https://github.com/egonSchiele) what project you're using it on :) See the [wiki](https://github.com/egonSchiele/contracts.ruby/wiki) for more info. Happy Coding! contracts-0.16.0/README.md0000644000004100000410000000576113120354472015117 0ustar www-datawww-data# contracts.ruby [![Build Status](https://travis-ci.org/egonSchiele/contracts.ruby.png?branch=master)](https://travis-ci.org/egonSchiele/contracts.ruby) [![Join the chat at https://gitter.im/egonSchiele/contracts.ruby](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg)](https://gitter.im/egonSchiele/contracts.ruby) Contracts let you clearly – even beautifully – express how your code behaves, and free you from writing tons of boilerplate, defensive code. You can think of contracts as `assert` on steroids. ## Installation gem install contracts ## Hello World A contract is one line of code that you write above a method definition. It validates the arguments to the method, and validates the return value of the method. Here is a simple contract: ```ruby Contract Num => Num def double(x) ``` This says that double expects a number and returns a number. Here's the full code: ```ruby require 'contracts' class Example include Contracts::Core include Contracts::Builtin Contract Num => Num def double(x) x * 2 end end puts Example.new.double("oops") ``` Save this in a file and run it. Notice we are calling `double` with `"oops"`, which is not a number. The contract fails with a detailed error message: ``` ParamContractError: Contract violation for argument 1 of 1: Expected: Num, Actual: "oops" Value guarded in: Example::double With Contract: Num => Num At: main.rb:8 ...stack trace... ``` Instead of throwing an exception, you could log it, print a clean error message for your user...whatever you want. contracts.ruby is here to help you handle bugs better, not to get in your way. ## Tutorial Check out [this awesome tutorial](http://egonschiele.github.com/contracts.ruby). ## Use Cases Check out [this screencast](https://vimeo.com/85883356). ## Development To get started do the following: 1. Install required gems for development `bundle install` 2. Run our test suite `bundle exec rspec` 3. Run our code style checks `bundle exec rubocop` ## Performance Using contracts.ruby results in very little slowdown. Check out [this blog post](http://adit.io/posts/2013-03-04-How-I-Made-My-Ruby-Project-10x-Faster.html#seconds-6) for more info. **Q.** What Rubies can I use this with? **A.** It's been tested with `1.9.2`, `1.9.3`, `2.0.0`, `2.1`, `2.2`, and `jruby` (1.9 mode). If you're using the library, please [let me know](https://github.com/egonSchiele) what project you're using it on :) ## Testimonials > Contracts literally saves us hours of pain at Snowplow every day Alexander Dean, creator of [Snowplow](https://github.com/snowplow/snowplow) > Contracts caught a bug that saved us several hundred dollars. It took less than 30 seconds to add the contract. Michael Tomer ## Credits Inspired by [contracts.coffee](http://disnet.github.io/contracts.coffee/). Copyright 2012-2015 [Aditya Bhargava](http://adit.io). Major improvements by [Alexey Fedorov](https://github.com/waterlink). BSD Licensed.