Skip to content

Commit

Permalink
Merge pull request #195 from waterlink/docs
Browse files Browse the repository at this point in the history
Docs
  • Loading branch information
egonSchiele committed Sep 7, 2015
2 parents 458780d + f1cd0ca commit d03e292
Show file tree
Hide file tree
Showing 37 changed files with 1,537 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -2,3 +2,4 @@
*.swo
.*
Gemfile.lock
tmp/aruba
4 changes: 4 additions & 0 deletions .rubocop.yml
@@ -1,3 +1,7 @@
AllCops:
Exclude:
- "tmp/**/*"

# forces method defs to have params in parens
Style/MethodDefParentheses:
Enabled: false
Expand Down
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -14,6 +14,7 @@ before_install:

script:
- bundle exec rspec
- script/cucumber
- ruby script/rubocop.rb

bundler_args: --without development
3 changes: 3 additions & 0 deletions Gemfile
Expand Up @@ -4,10 +4,13 @@ gemspec

group :test do
gem "rspec"
gem "aruba"
gem "cucumber", "~> 1.3.20"
gem "rubocop", "~> 0.29.1", :platform => [:ruby_20, :ruby_21, :ruby_22]
end

group :development do
gem "relish"
gem "method_profiler"
gem "ruby-prof"
gem "rake"
Expand Down
1 change: 1 addition & 0 deletions cucumber.yml
@@ -0,0 +1 @@
default: --require features
17 changes: 17 additions & 0 deletions features/README.md
@@ -0,0 +1,17 @@
contracts.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).
76 changes: 76 additions & 0 deletions features/basics/functype.feature
@@ -0,0 +1,76 @@
Feature: 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 => String
def th(number)
suffix = {
1 => "st",
2 => "nd",
3 => "rd"
}.fetch(x % 10, "th")
"#{number}#{suffix}"
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(:th)
"""
When I run `ruby singleton_method_functype.rb`
Then the output should contain:
"""
th :: Num => String
"""
210 changes: 210 additions & 0 deletions features/basics/simple_example.feature
@@ -0,0 +1,210 @@
Feature: 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
"""
22 changes: 22 additions & 0 deletions features/builtin_contracts/README.md
@@ -0,0 +1,22 @@
To 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!*

0 comments on commit d03e292

Please sign in to comment.