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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b5ef8f748c40fe69ebe9bde7525c18a26227e922
4
- data.tar.gz: c5dbba13dd9778bc27048e4f78ad80f1a81d8c80
3
+ metadata.gz: 129ba2fcf5b08749ade1b5a843a39939875dc9ee
4
+ data.tar.gz: 356f3af3411031f961ff7c5716dc7a631227db65
5
5
  SHA512:
6
- metadata.gz: 18b50e3f3ffdd15d9fb98242ddd44e32777c9f7add2a9c3539177c5226e2934092a98832f284107fa9168a06ba84fcc899a05dd4c8147beb5850857c140816f3
7
- data.tar.gz: b4a87e49171f26eff48bda4bee778780fd67c5b3e45e199ddf6f4d5fbd1189db5405ae8fccb52a512cf55ec2a7007dbb18a3b0b0bed030462c2ee97e413dbb52
6
+ metadata.gz: 314e4f6f4ca111a75c0fe924b63952e48efdce36d75947ee0811f488b98829734a1c2ab69562bc66bd68f093858ad6f397733001328236e40948e38c56169e26
7
+ data.tar.gz: 77727ad30bb03c10fea5db6cf380176d9eb69bb828dc1855239ada81a6d8c840726b77918a41d5973975c4b93f084c1baa0e96e080f3ddac47d4ee07f16c0c3b
@@ -0,0 +1,11 @@
1
+ .bundle
2
+ Gemfile.lock
3
+ *.gem
4
+ coverage
5
+ tags
6
+
7
+ # Swap
8
+ [._]*.s[a-v][a-z]
9
+ [._]*.sw[a-p]
10
+ [._]s[a-v][a-z]
11
+ [._]sw[a-p]
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require ./spec/spec_helper
@@ -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
@@ -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
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem "codeclimate-test-reporter", require: false
7
+ gem 'simplecov', require: false
8
+ end
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
- SafeType
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, API responses,
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
- ***SafeType*** provides an intuitive type coercion interface and type enhancement.
11
+ ***safe_type*** provides an intuitive type coercion interface and type enhancement.
14
12
 
15
- # Install
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
- ## JSON Response
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
- ## Http Response
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 *SafeType* provide `default` and `strict` mode, which are
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 SafeType rules.
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 rules
147
- As we've seen in the use cases, we can call `coerce` to apply a set of `SafeType::Rule`s.
148
- Rules can be bundled together as elements in an array or values in a hash.
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 *SafeType* primitive types, applying the rule on the class itself will use the default rule.
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
- # Prior Art
178
- This gem was inspired by [rails_param](https://github.com/nicolasblanco/rails_param)
179
- and [dry-types](https://github.com/dry-rb/dry-types). `dry-types` has a complex interface.
180
- Also it does not support in place coercion, and it will be complicated to `ENV` since the design of its
181
- `Hash Schemas`. `rails_param` relies on Rails and it is only for the `params`.
182
- Therefore, `safe_type` was created. It integrated some ideas from both gems,
183
- and it was designed specifically for type checking to provide an clean and easy-to-use interface.
184
- It should be useful when working with any string or hash where the values are coming from an external source,
185
- such as `ENV` variables, rails `params`, or API calls.
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).
@@ -0,0 +1,7 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec) do |t|
4
+ t.rspec_opts = ['--format progress']
5
+ end
6
+
7
+ task :default => :spec
@@ -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
@@ -1,27 +1,52 @@
1
1
  module SafeType
2
- class Error < StandardError; end
3
2
 
4
- class CoercionError < Error
5
- def initialize(message="unable to transform into the requested type")
6
- super
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 < Error
11
- def initialize(message="failed to validate")
12
- super
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 < Error
17
- def initialize(message="the value should not be empty")
18
- super
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(message="invalid coercion rule")
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
@@ -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 => e
49
+ rescue TypeError, ArgumentError, NoMethodError
54
50
  return @default if input.nil? && !@required
55
- handle_exceptions(e)
51
+ raise SafeType::CoercionError.new(input, @type, key)
56
52
  end
57
53
  end
58
54
  end
@@ -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.0.0
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: '0'
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: '0'
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: '0'
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: '0'
41
- description: " \n Type coercion & Type Enhancement\n "
42
- email: mail@ddong.me
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.5.2.1
110
+ rubygems_version: 2.6.14
84
111
  signing_key:
85
112
  specification_version: 4
86
- summary: Type coercion & Type Enhancement
113
+ summary: Type Coercion & Type Enhancement
87
114
  test_files: []