safe_type 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.travis.yml +18 -0
- data/CONTRIBUTING.md +22 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/README.md +22 -26
- data/Rakefile +7 -0
- data/lib/safe_type.rb +6 -6
- data/lib/safe_type/errors.rb +37 -12
- data/lib/safe_type/rule.rb +7 -11
- data/safe_type.gemspec +22 -0
- metadata +40 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 129ba2fcf5b08749ade1b5a843a39939875dc9ee
|
4
|
+
data.tar.gz: 356f3af3411031f961ff7c5716dc7a631227db65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 314e4f6f4ca111a75c0fe924b63952e48efdce36d75947ee0811f488b98829734a1c2ab69562bc66bd68f093858ad6f397733001328236e40948e38c56169e26
|
7
|
+
data.tar.gz: 77727ad30bb03c10fea5db6cf380176d9eb69bb828dc1855239ada81a6d8c840726b77918a41d5973975c4b93f084c1baa0e96e080f3ddac47d4ee07f16c0c3b
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
rvm:
|
4
|
+
- 2.2.9
|
5
|
+
- 2.3.5
|
6
|
+
- 2.4.3
|
7
|
+
- 2.5.0
|
8
|
+
|
9
|
+
bundler_args: --binstubs
|
10
|
+
|
11
|
+
before_install:
|
12
|
+
- gem install bundler --no-doc
|
13
|
+
|
14
|
+
script:
|
15
|
+
- bundle exec rake
|
16
|
+
|
17
|
+
after_success:
|
18
|
+
- bundle exec codeclimate-test-reporter
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
## Issues
|
4
|
+
If there are any issues, feel free to create an issue on the GitHub repository issue page.
|
5
|
+
|
6
|
+
## Development
|
7
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
8
|
+
|
9
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to rubygems.org.
|
10
|
+
|
11
|
+
If there are issues with running the `bin` files, try to `chmod +x bin/*` the files first.
|
12
|
+
|
13
|
+
## Contribution Guidelines
|
14
|
+
1. Make commits that are logically well isolated and have descriptive commit messages.
|
15
|
+
|
16
|
+
2. Make sure that there are tests in the [spec](./spec) directory for the code you wrote.
|
17
|
+
|
18
|
+
3. Make sure that changes to public methods or interfaces are documented in this README.
|
19
|
+
|
20
|
+
4. Run tests and address any errors.
|
21
|
+
|
22
|
+
5. Open a pull request with a descriptive message that describes the change, the need, and verification that the change is tested.
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2018 Chan Zuckerberg Initiative
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -1,18 +1,16 @@
|
|
1
1
|
---
|
2
|
-
|
2
|
+
safe_type
|
3
3
|
---
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/safe_type.svg)](https://badge.fury.io/rb/safe_type)
|
5
5
|
[![Build Status](https://travis-ci.org/chanzuckerberg/safe_type.svg?branch=master)](https://travis-ci.org/chanzuckerberg/safe_type)
|
6
6
|
[![Maintainability](https://api.codeclimate.com/v1/badges/7fbc9a4038b86ef639e1/maintainability)](https://codeclimate.com/github/chanzuckerberg/safe_type/maintainability)
|
7
7
|
[![Test Coverage](https://api.codeclimate.com/v1/badges/7fbc9a4038b86ef639e1/test_coverage)](https://codeclimate.com/github/chanzuckerberg/safe_type/test_coverage)
|
8
8
|
|
9
|
-
While working with environment variables, routing parameters,
|
10
|
-
or other Hash-like objects require parsing,
|
11
|
-
we often need type coercion to assure expected behaviors.
|
9
|
+
While working with environment variables, routing parameters, network responses, or other Hash-like objects that require parsing, we often need type coercion to assure expected behaviors.
|
12
10
|
|
13
|
-
***
|
11
|
+
***safe_type*** provides an intuitive type coercion interface and type enhancement.
|
14
12
|
|
15
|
-
#
|
13
|
+
# Installation
|
16
14
|
|
17
15
|
We can install `safe_type` using `gem install`:
|
18
16
|
|
@@ -69,7 +67,7 @@ SafeType::coerce!(params, rules)
|
|
69
67
|
params["course_id"] # => 101
|
70
68
|
params["start_date"] # => <Date: 2018-10-01 ((2458393j,0s,0n),+0s,2299161j)>
|
71
69
|
```
|
72
|
-
##
|
70
|
+
## Ruby Hashes
|
73
71
|
```ruby
|
74
72
|
json = {
|
75
73
|
"names" => ["Alice", "Bob", "Chris"],
|
@@ -99,7 +97,7 @@ SafeType::coerce!(json, {
|
|
99
97
|
]
|
100
98
|
})
|
101
99
|
```
|
102
|
-
##
|
100
|
+
## Network Responses
|
103
101
|
```ruby
|
104
102
|
class ResponseType; end
|
105
103
|
|
@@ -129,7 +127,7 @@ The parameters are
|
|
129
127
|
- `required` indicates whether empty values are allowed
|
130
128
|
|
131
129
|
## `strict` vs `default`
|
132
|
-
The primitive types in
|
130
|
+
The primitive types in `SafeType` provide `default` and `strict` mode, which are
|
133
131
|
- `SafeType::Boolean`
|
134
132
|
- `SafeType::Date`
|
135
133
|
- `SafeType::DateTime`
|
@@ -139,13 +137,13 @@ The primitive types in *SafeType* provide `default` and `strict` mode, which are
|
|
139
137
|
- `SafeType::Symbol`
|
140
138
|
- `SafeType::Time`
|
141
139
|
|
142
|
-
Under the hood, they are all just
|
140
|
+
Under the hood, they are all just `Rule` classes with parameters:
|
143
141
|
- `default`: a rule with default value specified
|
144
142
|
- `strict`: a rule with `required: true`, so no empty values are allowed, or it throws `EmptyValueError`
|
145
143
|
|
146
|
-
## Apply the
|
147
|
-
As we've seen in the use cases, we can call `coerce` to apply a set of `SafeType::Rule`
|
148
|
-
|
144
|
+
## Apply the Rules
|
145
|
+
As we've seen in the use cases, we can call `coerce` to apply a set of `SafeType::Rule` classes.
|
146
|
+
`Rule` classes can be bundled together as elements in an array or values in a hash.
|
149
147
|
|
150
148
|
### `coerce` vs `coerce!`
|
151
149
|
- `SafeType::coerce` returns a new object, corresponding to the rules. The unspecified fields will not be included in the new object.
|
@@ -162,7 +160,7 @@ Note those two examples are equivalent:
|
|
162
160
|
SafeType::coerce(ENV["PORT"], SafeType::Integer.default(3000))
|
163
161
|
SafeType::Integer.default(3000).coerce(ENV["PORT"])
|
164
162
|
```
|
165
|
-
For the
|
163
|
+
For the `SafeType` primitive types, applying the rule on the class itself will use the default rule.
|
166
164
|
|
167
165
|
## Customized Types
|
168
166
|
We can inherit from a `SafeType::Rule` to create a customized type.
|
@@ -174,15 +172,13 @@ We can override following methods if needed:
|
|
174
172
|
- Override `handle_exceptions` to change the behavior of exceptions handling (e.g: send to the logger, or no exception)
|
175
173
|
- Override `default` or `strict` to modify the default and strict rule.
|
176
174
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
## License
|
188
|
-
`safe_type` is released under an MIT license.
|
175
|
+
## Prior Art
|
176
|
+
`safe_type` pulls heavy inspiration from [dry-types](https://github.com/dry-rb/dry-types) and [rails_param](https://github.com/nicolasblanco/rails_param).
|
177
|
+
The interface in `safe_type` looks similar to the interface in `dry-types`, however, `safe_type` supports some additional features such as in-place coercion
|
178
|
+
using the Ruby bang method interface and the ability to define schemas using Ruby hashes and arrays. `safe_type` also borrows some concepts from `rails_param`,
|
179
|
+
but with some fundamental differences such as `safe_type` not relying on Rails and `safe_type` not having a focus on only typing Rails controller parameters.
|
180
|
+
The goal of `safe_type` is to provide a good tradeoff between complexity and flexibility by enabling type checking through a clean and easy-to-use interface.
|
181
|
+
`safe_type` should be useful when working with any string or hash where the values could be ambiguously typed, such as `ENV` variables, rails `params`, or network responses..
|
182
|
+
|
183
|
+
# License
|
184
|
+
The `safe_type` project is licensed and available open source under the terms of the [MIT license](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/lib/safe_type.rb
CHANGED
@@ -12,12 +12,12 @@ require 'safe_type/primitive/time'
|
|
12
12
|
|
13
13
|
module SafeType
|
14
14
|
class << self
|
15
|
-
def coerce(input, rule)
|
16
|
-
return rule.coerce(input) if rule.is_a?(SafeType::Rule)
|
15
|
+
def coerce(input, rule, coerce_key=nil)
|
16
|
+
return rule.coerce(input, coerce_key) if rule.is_a?(SafeType::Rule)
|
17
17
|
if rule.class == ::Hash
|
18
18
|
result = {}
|
19
19
|
rule.each do |key, val|
|
20
|
-
result[key] = coerce(input[key], val)
|
20
|
+
result[key] = coerce(input[key], val, key)
|
21
21
|
end
|
22
22
|
return result
|
23
23
|
end
|
@@ -26,7 +26,7 @@ module SafeType
|
|
26
26
|
result = ::Array.new(input.length)
|
27
27
|
i = 0
|
28
28
|
while i < input.length
|
29
|
-
result[i] = coerce(input[i], rule[i % rule.length])
|
29
|
+
result[i] = coerce(input[i], rule[i % rule.length], i)
|
30
30
|
i += 1
|
31
31
|
end
|
32
32
|
return result
|
@@ -40,7 +40,7 @@ module SafeType
|
|
40
40
|
if val.class == ::Hash
|
41
41
|
coerce!(input[key], val)
|
42
42
|
else
|
43
|
-
input[key] = coerce(input[key], val)
|
43
|
+
input[key] = coerce(input[key], val, key)
|
44
44
|
end
|
45
45
|
end
|
46
46
|
return nil
|
@@ -48,7 +48,7 @@ module SafeType
|
|
48
48
|
if rule.class == ::Array
|
49
49
|
i = 0
|
50
50
|
while i < input.length
|
51
|
-
input[i] = coerce(input[i], rule[i % rule.length])
|
51
|
+
input[i] = coerce(input[i], rule[i % rule.length], i)
|
52
52
|
i += 1
|
53
53
|
end
|
54
54
|
return nil
|
data/lib/safe_type/errors.rb
CHANGED
@@ -1,27 +1,52 @@
|
|
1
1
|
module SafeType
|
2
|
-
class Error < StandardError; end
|
3
2
|
|
4
|
-
class CoercionError <
|
5
|
-
|
6
|
-
|
3
|
+
class CoercionError < StandardError
|
4
|
+
attr_reader :key
|
5
|
+
attr_reader :value
|
6
|
+
attr_reader :desired_type
|
7
|
+
|
8
|
+
def initialize(value, desired_type, key=nil)
|
9
|
+
super("Could not coerce " + (key.nil? ? '' : "key (#{key}) with ") +
|
10
|
+
"value (#{value.inspect}) of type (#{value.class}) to desired type (#{desired_type})")
|
11
|
+
|
12
|
+
@key = key
|
13
|
+
@value = value
|
14
|
+
@desired_type = desired_type
|
7
15
|
end
|
8
16
|
end
|
9
17
|
|
10
|
-
class ValidationError <
|
11
|
-
|
12
|
-
|
18
|
+
class ValidationError < StandardError
|
19
|
+
attr_reader :key
|
20
|
+
attr_reader :value
|
21
|
+
attr_reader :desired_type
|
22
|
+
|
23
|
+
def initialize(value, desired_type, key=nil)
|
24
|
+
super("Validation for " + (key.nil? ? '' : "key (#{key}) with ") +
|
25
|
+
"value (#{value.inspect}) of " +
|
26
|
+
"type (#{value.class}) to desired type (#{desired_type}) has failed")
|
27
|
+
|
28
|
+
@key = key
|
29
|
+
@value = value
|
30
|
+
@desired_type = desired_type
|
13
31
|
end
|
14
32
|
end
|
15
33
|
|
16
|
-
class EmptyValueError <
|
17
|
-
|
18
|
-
|
34
|
+
class EmptyValueError < StandardError
|
35
|
+
attr_reader :key
|
36
|
+
attr_reader :desired_type
|
37
|
+
|
38
|
+
def initialize(desired_type, key=nil)
|
39
|
+
super("Expected a " + (key.nil? ? '' : "key (#{key}) with ") +
|
40
|
+
"value of desired type (#{desired_type}), but received a nil value")
|
41
|
+
|
42
|
+
@key = key
|
43
|
+
@desired_type = desired_type
|
19
44
|
end
|
20
45
|
end
|
21
46
|
|
22
47
|
class InvalidRuleError < ArgumentError
|
23
|
-
def initialize(
|
24
|
-
super
|
48
|
+
def initialize()
|
49
|
+
super("Coercion rule does not exist or is not valid")
|
25
50
|
end
|
26
51
|
end
|
27
52
|
end
|
data/lib/safe_type/rule.rb
CHANGED
@@ -24,10 +24,6 @@ module SafeType
|
|
24
24
|
input
|
25
25
|
end
|
26
26
|
|
27
|
-
def handle_exceptions(e)
|
28
|
-
raise SafeType::CoercionError
|
29
|
-
end
|
30
|
-
|
31
27
|
def self.coerce(input)
|
32
28
|
default.coerce(input)
|
33
29
|
end
|
@@ -40,19 +36,19 @@ module SafeType
|
|
40
36
|
new(required: true)
|
41
37
|
end
|
42
38
|
|
43
|
-
def coerce(input)
|
44
|
-
raise SafeType::EmptyValueError if input.nil? && @required
|
39
|
+
def coerce(input, key=nil)
|
40
|
+
raise SafeType::EmptyValueError.new(@type, key) if input.nil? && @required
|
45
41
|
input = before(input)
|
46
42
|
input = Converter.to_type(input, @type)
|
47
|
-
raise SafeType::ValidationError unless is_valid?(input)
|
43
|
+
raise SafeType::ValidationError.new(input, @type, key) unless is_valid?(input)
|
48
44
|
result = after(input)
|
49
|
-
raise SafeType::EmptyValueError if result.nil? && @required
|
45
|
+
raise SafeType::EmptyValueError.new(@type, key) if result.nil? && @required
|
50
46
|
return @default if result.nil?
|
51
|
-
raise SafeType::CoercionError unless result.is_a?(@type)
|
47
|
+
raise SafeType::CoercionError.new(result, @type, key) unless result.is_a?(@type)
|
52
48
|
result
|
53
|
-
rescue TypeError, ArgumentError, NoMethodError
|
49
|
+
rescue TypeError, ArgumentError, NoMethodError
|
54
50
|
return @default if input.nil? && !@required
|
55
|
-
|
51
|
+
raise SafeType::CoercionError.new(input, @type, key)
|
56
52
|
end
|
57
53
|
end
|
58
54
|
end
|
data/safe_type.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'safe_type'
|
3
|
+
s.version = '1.1.0'
|
4
|
+
s.date = '2018-07-18'
|
5
|
+
s.summary = 'Type Coercion & Type Enhancement'
|
6
|
+
s.description = %q{
|
7
|
+
Type Coercion & Type Enhancement
|
8
|
+
}
|
9
|
+
s.authors = ['Donald Dong', 'Edmund Loo', 'Jacob Gable']
|
10
|
+
s.email = ['mail@ddong.me', 'edmundloo@outlook.com', 'jgable@chanzuckerberg.com']
|
11
|
+
s.homepage = 'https://github.com/chanzuckerberg/safe_type'
|
12
|
+
s.license = 'MIT'
|
13
|
+
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - ['bin/console', 'bin/setup']
|
14
|
+
|
15
|
+
s.rdoc_options = ['--charset=UTF-8']
|
16
|
+
s.require_paths = ['lib']
|
17
|
+
|
18
|
+
s.add_development_dependency 'bundler', '~> 1.16'
|
19
|
+
s.add_development_dependency 'rake', '~> 12.3'
|
20
|
+
s.add_development_dependency 'rspec', '~> 3.8'
|
21
|
+
|
22
|
+
end
|
metadata
CHANGED
@@ -1,50 +1,76 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: safe_type
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Donald Dong
|
8
|
+
- Edmund Loo
|
9
|
+
- Jacob Gable
|
8
10
|
autorequire:
|
9
11
|
bindir: bin
|
10
12
|
cert_chain: []
|
11
13
|
date: 2018-07-18 00:00:00.000000000 Z
|
12
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: bundler
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - "~>"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.16'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - "~>"
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '1.16'
|
13
29
|
- !ruby/object:Gem::Dependency
|
14
30
|
name: rake
|
15
31
|
requirement: !ruby/object:Gem::Requirement
|
16
32
|
requirements:
|
17
|
-
- - "
|
33
|
+
- - "~>"
|
18
34
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
35
|
+
version: '12.3'
|
20
36
|
type: :development
|
21
37
|
prerelease: false
|
22
38
|
version_requirements: !ruby/object:Gem::Requirement
|
23
39
|
requirements:
|
24
|
-
- - "
|
40
|
+
- - "~>"
|
25
41
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
42
|
+
version: '12.3'
|
27
43
|
- !ruby/object:Gem::Dependency
|
28
44
|
name: rspec
|
29
45
|
requirement: !ruby/object:Gem::Requirement
|
30
46
|
requirements:
|
31
|
-
- - "
|
47
|
+
- - "~>"
|
32
48
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
49
|
+
version: '3.8'
|
34
50
|
type: :development
|
35
51
|
prerelease: false
|
36
52
|
version_requirements: !ruby/object:Gem::Requirement
|
37
53
|
requirements:
|
38
|
-
- - "
|
54
|
+
- - "~>"
|
39
55
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
41
|
-
description: " \n Type
|
42
|
-
email:
|
56
|
+
version: '3.8'
|
57
|
+
description: " \n Type Coercion & Type Enhancement\n "
|
58
|
+
email:
|
59
|
+
- mail@ddong.me
|
60
|
+
- edmundloo@outlook.com
|
61
|
+
- jgable@chanzuckerberg.com
|
43
62
|
executables: []
|
44
63
|
extensions: []
|
45
64
|
extra_rdoc_files: []
|
46
65
|
files:
|
66
|
+
- ".gitignore"
|
67
|
+
- ".rspec"
|
68
|
+
- ".travis.yml"
|
69
|
+
- CONTRIBUTING.md
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE
|
47
72
|
- README.md
|
73
|
+
- Rakefile
|
48
74
|
- lib/safe_type.rb
|
49
75
|
- lib/safe_type/converter.rb
|
50
76
|
- lib/safe_type/errors.rb
|
@@ -59,6 +85,7 @@ files:
|
|
59
85
|
- lib/safe_type/primitive/symbol.rb
|
60
86
|
- lib/safe_type/primitive/time.rb
|
61
87
|
- lib/safe_type/rule.rb
|
88
|
+
- safe_type.gemspec
|
62
89
|
homepage: https://github.com/chanzuckerberg/safe_type
|
63
90
|
licenses:
|
64
91
|
- MIT
|
@@ -80,8 +107,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
107
|
version: '0'
|
81
108
|
requirements: []
|
82
109
|
rubyforge_project:
|
83
|
-
rubygems_version: 2.
|
110
|
+
rubygems_version: 2.6.14
|
84
111
|
signing_key:
|
85
112
|
specification_version: 4
|
86
|
-
summary: Type
|
113
|
+
summary: Type Coercion & Type Enhancement
|
87
114
|
test_files: []
|