safe_type 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
[](https://badge.fury.io/rb/safe_type)
|
5
5
|
[](https://travis-ci.org/chanzuckerberg/safe_type)
|
6
6
|
[](https://codeclimate.com/github/chanzuckerberg/safe_type/maintainability)
|
7
7
|
[](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: []
|