Skip to content

Commit

Permalink
feat: Added additional Boolean, Union and Nilable types
Browse files Browse the repository at this point in the history
fixes #6
  • Loading branch information
joelmoss committed Apr 4, 2024
1 parent 4898a9a commit 68ef60c
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 3 deletions.
9 changes: 8 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ Style/Documentation:
Enabled: false
Style/HashAsLastArrayItem:
Enabled: false
Style/CaseEquality:
Enabled: false
Style/NilComparison:
Enabled: false

Naming/MethodName:
Exclude:
- "lib/delivered/types.rb"

Layout/LineLength:
Max: 100
Expand All @@ -23,7 +31,6 @@ Metrics/CyclomaticComplexity:
Enabled: false
Metrics/PerceivedComplexity:
Enabled: false

Metrics/BlockLength:
Exclude:
- "test/**/*"
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,57 @@ def create(name, age:)
"User #{name} created with age #{age}"
end
```

### Delivered Types

As well as Ruby's native types (ie. `String`, `Integer`, etc.), _Delivered_ provides a couple of
extra types in `Delivered::Types`.

You can call these directly with `Delivered::Types.Boolean`, or for brevity, assign
`Delivered::Types` to `T` in your classes:

```ruby
class User
extend Delivered::Signature
T = Delivered::Types
end
```

The following examples all use the `T` alias, and assumes the above.

#### `Boolean`

Value **MUST** be `true` or `false`. Does not support "truthy" or "falsy" values.

```ruby
sig validate: T.Boolean
def create(validate:); end
```
#### `Union`
Value **MUST** be a union of the given list of values, that is, the value must be one of the given list.
```ruby
sig T.Union(:male, :female)
def create(gender); end
```
#### `Nilable`
When a type is given, the value **MUST** be nil or of the given type.
```ruby
sig save: T.Nilable(String)
def create(save: nil); end

sig T.Nilable(String)
def update(name = nil); end
```
If no type is given, the value **CAN** be nil. This essentially allows any value, including nil.
```ruby
sig save: T.Nilable
def create(save: nil); end
```
1 change: 1 addition & 0 deletions lib/delivered.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

module Delivered
autoload :Signature, 'delivered/signature'
autoload :Types, 'delivered/types'
end
2 changes: 0 additions & 2 deletions lib/delivered/signature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

module Delivered
module Signature
NULL = Object.new

def sig(*sig_args, **sig_kwargs, &return_blk)
# ap [sig_args, sig_kwargs, return_blk]

Expand Down
41 changes: 41 additions & 0 deletions lib/delivered/types.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

module Delivered
class UnionType
def initialize(*types)
@types = types
end

def ===(value)
@types.any? { |type| type === value }
end
end

class NilableType
def initialize(type = nil)
@type = type
end

def ===(value)
(@type.nil? ? true : nil === value) || @type === value
end
end

class BooleanType
def initialize
freeze
end

def ===(value)
[true, false].include?(value)
end
end

module Types
module_function

def Nilable(type = nil) = NilableType.new(type)
def Union(*types) = UnionType.new(*types)
def Boolean = BooleanType.new
end
end
8 changes: 8 additions & 0 deletions test/delivered/signature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ def to_s = "#{@name}, #{@age}"
sig { Integer }
def age = @age.to_s

sig(Delivered::Types.Boolean)
def active=(val); end

sig(String, _age: Integer) { Array }
def self.where(_name, _age: nil) = []

Expand Down Expand Up @@ -106,6 +109,11 @@ def self.find_by_name(name) = User.new(name)
end
end

it 'raises on incorrect Delivered type' do
user = User.new('Joel')
expect { user.active = 1 }.to raise_exception NoMatchingPatternError
end

it 'raises on missing args' do
expect { User.new }.to raise_exception NoMatchingPatternError
end
Expand Down
47 changes: 47 additions & 0 deletions test/delivered/types.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

describe Delivered::Types do
T = Delivered::Types

describe 'Union' do
it 'is within union' do
assert T.Union(:one, :two) === :one
end

it 'raises when value is not in union' do
expect { :three => ^(T.Union(:one, :two)) }.to raise_exception NoMatchingPatternError
expect { :one => ^(T.Union(:one, :two)) }.not.to raise_exception
end
end

describe 'Boolean' do
it 'should be true or false' do
assert T.Boolean === true
assert T.Boolean === false
end

it 'raises when not boolean' do
expect { 0 => ^(T.Boolean) }.to raise_exception NoMatchingPatternError
end
end

describe 'Nilable' do
it 'can be nil' do
assert T.Nilable === nil
end

with 'no given type' do
it 'can be any type' do
assert T.Nilable === 'hello'
expect { 'hello' => ^(T.Nilable) }.not.to raise_exception NoMatchingPatternError
end
end

it 'can receive another type' do
assert T.Nilable(String) === 'hello'
assert T.Nilable(String) === nil
expect { 'hello' => ^(T.Nilable(String)) }.not.to raise_exception
expect { 1 => ^(T.Nilable(String)) }.to raise_exception NoMatchingPatternError
end
end
end

0 comments on commit 68ef60c

Please sign in to comment.