deprecation_helper 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +39 -0
- data/LICENSE +21 -0
- data/README.md +211 -0
- data/deprecation_helper.gemspec +25 -0
- data/lib/deprecation_helper.rb +60 -0
- data/lib/deprecation_helper/deprecation_exception.rb +12 -0
- data/lib/deprecation_helper/private.rb +8 -0
- data/lib/deprecation_helper/private/allow_list.rb +18 -0
- data/lib/deprecation_helper/private/configuration.rb +22 -0
- data/lib/deprecation_helper/strategies/base_strategy_interface.rb +17 -0
- data/lib/deprecation_helper/strategies/error_strategy_interface.rb +24 -0
- data/lib/deprecation_helper/strategies/log_error.rb +22 -0
- data/lib/deprecation_helper/strategies/raise_error.rb +19 -0
- data/spec/deprecation_helper_spec.rb +129 -0
- data/spec/spec_helper.rb +27 -0
- data/test.sh +11 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e69bb4ef93cda5c44fba549cfd072e0965c9fa8f323a44c96a87be315ad021be
|
4
|
+
data.tar.gz: 220dc767f5dde12538c03f3be75a9ce88c7321ef2a2ee701f15c2cc49edc5c53
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7fcf096afe79fd910d4d07d39b1770094a7eaa48def28d0cdf030b6843d8c23f7e5ab203431b43c760a0d3ad6e84d380b8e50f6960921c066a09ba74bee2927b
|
7
|
+
data.tar.gz: 7bd9162f156db65ede8358265ba9b3820357d799f9bf0f057bc854dd330cfcf0daf2c7bea84d59ccd2a3331a82a2f6d66c5d2ae9eed5d393fee6f5c38442f778
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
deprecation_helper (1.0.0.pre.gusto.pre.pre)
|
5
|
+
sorbet-runtime (~> 0.5.6293)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
diff-lcs (1.4.4)
|
11
|
+
rspec (3.10.0)
|
12
|
+
rspec-core (~> 3.10.0)
|
13
|
+
rspec-expectations (~> 3.10.0)
|
14
|
+
rspec-mocks (~> 3.10.0)
|
15
|
+
rspec-core (3.10.1)
|
16
|
+
rspec-support (~> 3.10.0)
|
17
|
+
rspec-expectations (3.10.1)
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
19
|
+
rspec-support (~> 3.10.0)
|
20
|
+
rspec-mocks (3.10.1)
|
21
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
22
|
+
rspec-support (~> 3.10.0)
|
23
|
+
rspec-support (3.10.1)
|
24
|
+
sorbet (0.5.6293)
|
25
|
+
sorbet-static (= 0.5.6293)
|
26
|
+
sorbet-runtime (0.5.6293)
|
27
|
+
sorbet-static (0.5.6293-universal-darwin-17)
|
28
|
+
|
29
|
+
PLATFORMS
|
30
|
+
ruby
|
31
|
+
x86_64-darwin-17
|
32
|
+
|
33
|
+
DEPENDENCIES
|
34
|
+
deprecation_helper!
|
35
|
+
rspec (~> 3.0)
|
36
|
+
sorbet (~> 0.5.6293)
|
37
|
+
|
38
|
+
BUNDLED WITH
|
39
|
+
2.2.4
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 Gusto
|
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
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
# DeprecationHelper
|
2
|
+
|
3
|
+
[![Build status](https://badge.buildkite.com/ca0732598feeb90c404e6883a2d79ad32610ff356020f11639.svg?branch=main)](https://buildkite.com/gusto/deprecationhelper)
|
4
|
+
|
5
|
+
The purpose of this gem is to help Ruby developers change code safely. It provides a basic framework for deprecating code. Since Ruby is an untyped language, you can't be sure when ceratin types of changes you're trying to make, such as deleting or renaming a method, will break production. This gem is provides an opinionated roadmap for deprecating code.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'deprecation_helper'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle install
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install deprecation_helper
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
### Configuration
|
25
|
+
First, you'll need to configure `DeprecationHelper`.
|
26
|
+
|
27
|
+
Here is an example configuration:
|
28
|
+
```ruby
|
29
|
+
DeprecationHelper.configure do |config|
|
30
|
+
if Rails.env.test? || Rails.env.development?
|
31
|
+
config.deprecation_strategies = [
|
32
|
+
DeprecationHelper::Strategies::LogError.new(logger: Rails.logger),
|
33
|
+
DeprecationHelper::Strategies::RaiseError.new,
|
34
|
+
]
|
35
|
+
else
|
36
|
+
config.deprecation_strategies = [
|
37
|
+
DeprecationHelper::Strategies::LogError.new(logger: Rails.logger),
|
38
|
+
MyCustomStrategy.new, # See more on this below
|
39
|
+
]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
In this configuration, we always log an error in any environment. In the test environment, this allows to generate TODO lists of code that is exercised by your test suite that is using deprecated methods. These TODO lists can then be passed into the allow list (see below). In test and development, an error is raised as well, which in this configuration forces users to either add the deprecations to the TODO list or to address them prior to landing the deprecation.
|
45
|
+
|
46
|
+
Note that global configuration is optional -- you can also pass in `deprecation_strategies` directly to your `deprecate!` call (see the advanced usage section below).
|
47
|
+
|
48
|
+
### Deprecating Code
|
49
|
+
There is one main method which is called with a string
|
50
|
+
```ruby
|
51
|
+
DeprecationHelper.deprecate!('your message value')
|
52
|
+
```
|
53
|
+
|
54
|
+
By inlining these into your code, when the deprecation is called, it will apply the strategies that you've configured.
|
55
|
+
|
56
|
+
### Allow lists
|
57
|
+
The main method accept an `allow_list` argument, as such:
|
58
|
+
```ruby
|
59
|
+
DeprecationHelper.deprecate!('your message value', allow_list: [/your/, /allow/, /list/]))
|
60
|
+
```
|
61
|
+
|
62
|
+
If your callstack (determined by `Kernel#caller`) matches *any* element in the allow list, then no deprecation strategy will be invoked -- the deprecation will be skipped. It is recommended to use `allow_list` primarily as a TODO list.
|
63
|
+
|
64
|
+
Note also that your allow list entries should be as specific to avoid unintended matches, especially if the entries represent a todo list of specific lines from specific files.
|
65
|
+
|
66
|
+
**Examples of allow lists**
|
67
|
+
|
68
|
+
Here are a couple of examples of `allow_list` values that you might want:
|
69
|
+
- `bin/rails` - Perhaps you want to permit everything that happens in your rails console
|
70
|
+
- `my_method_wrapper` - In some cases, you want to just use this gem to help you find all places that use the code and wrap them in something that makes it safe to use the deprecated method, but easier to grep for. Perhaps you have a class like:
|
71
|
+
```ruby
|
72
|
+
class MyDeprecator
|
73
|
+
def self.my_method_wrapper
|
74
|
+
yield
|
75
|
+
end
|
76
|
+
end
|
77
|
+
```
|
78
|
+
This class doesn't do anything, but now you can call `MyDeprecator.my_method_wrapper { my_deprecated_code }` and it will be allow-listed using `my_method_wrapper` as the allow-list value.
|
79
|
+
- `some_exempt_folder` - Perhaps you'd like an entire folder to be exempt from this deprecation for the time being, for whatever reason
|
80
|
+
- `factory_bot` - Perhaps a gem like `factory_bot` or another test only piece of code is using your deprecated functionality and you don't mind permitting it to unblock other tests.
|
81
|
+
|
82
|
+
### Deprecation Strategies
|
83
|
+
There are several strategies, here's what they do:
|
84
|
+
|
85
|
+
**Strategy: Do nothing on use of deprecated code**
|
86
|
+
|
87
|
+
Where to find it: Configure `deprecation_strategies` to be an empty list (this is also the default)
|
88
|
+
This might be useful if you want to take no action in certain environments.
|
89
|
+
|
90
|
+
**Strategy: Raising on use of deprecated code**
|
91
|
+
|
92
|
+
Where to find it: `DeprecationHelper::Strategies::RaiseError`
|
93
|
+
|
94
|
+
This is useful if you believe you've already addressed all deprecations in the environment that uses this strategy OR perhaps if the use of the deprecated code is more negatively impactful than raising.
|
95
|
+
|
96
|
+
Note that this strategy will construct a `DeprecationHelper::DeprecationException` with the message equal to the input value to `deprecate!` and raise that error.
|
97
|
+
|
98
|
+
**Strategy: Logging on use of deprecated code**
|
99
|
+
|
100
|
+
Where to find it: `DeprecationHelper::Strategies::LogError`
|
101
|
+
|
102
|
+
This is useful if you want to generate an allow list to stop the bleeding.
|
103
|
+
|
104
|
+
**Strategy: Report to your bug tracking tool on use of deprecated code**
|
105
|
+
|
106
|
+
Where to find it: This you'll need to create yourself, since it has a dependency on bugsnag.
|
107
|
+
One option is to use the `ThrottledBugsnag` or `Bugsnag` gem, by creating your own deprecation strategy (see advanced usage below).
|
108
|
+
|
109
|
+
### Advanced Usage
|
110
|
+
**Overriding global configuration**
|
111
|
+
|
112
|
+
You can pass in an array of `deprecation_strategies` to `deprecate!` if you'd like to override the global configuration for `DeprecationHelper`.
|
113
|
+
|
114
|
+
**Creating your own deprecation strategies**
|
115
|
+
|
116
|
+
You can construct your own deprecation strategies by implementing any `StrategyInterface` class, which means including the class and implementing the method(s) it requires.
|
117
|
+
|
118
|
+
Here are the interfaces you can include to construct your own strategies:
|
119
|
+
- `DeprecationHelper::Strategies::ErrorStrategyInterface`
|
120
|
+
- `DeprecationHelper::Strategies::BaseStrategyInterface`
|
121
|
+
|
122
|
+
Here is an example of them being used:
|
123
|
+
```ruby
|
124
|
+
class SlackNotifierDeprecationStrategy
|
125
|
+
include DeprecationHelper::Strategies::BaseStrategyInterface
|
126
|
+
extend T::Sig
|
127
|
+
|
128
|
+
sig { override.params(message: String, logger: T.nilable(Logger)).void }
|
129
|
+
def apply!(message, logger: nil)
|
130
|
+
# This takes in an exception that is message equal to the message passed into `deprecate!`
|
131
|
+
SlackNotifier.notify(message)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class BugsnagDeprecationStrategy
|
136
|
+
include DeprecationHelper::Strategies::ErrorStrategyInterface
|
137
|
+
extend T::Sig
|
138
|
+
|
139
|
+
sig { override.params(exception: StandardError, logger: T.nilable(Logger)).void }
|
140
|
+
def apply_to_exception!(exception, logger: nil)
|
141
|
+
# This takes in an exception that is a `DeprecationHelper::DeprecationException`
|
142
|
+
# with a `message` value equal to the message passed into `deprecate!`
|
143
|
+
Bugsnag.notify(exception)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
```
|
147
|
+
You can create your own deprecation strategy by including StrategyInterface and implementing the method `apply!` that takes in an `exception` and an optional `logger`.
|
148
|
+
|
149
|
+
Here is an example:
|
150
|
+
```ruby
|
151
|
+
class BugsnagDeprecationStrategy
|
152
|
+
include DeprecationHelper::Strategies::BaseStrategyInterface
|
153
|
+
extend T::Sig
|
154
|
+
sig { override.params(exception: StandardError).void }
|
155
|
+
def apply!(exception)
|
156
|
+
ThrottledBugsnag.notify(exception) # or Bugsnag.notify(exception)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
```
|
160
|
+
|
161
|
+
This is useful to report instances of use of deprecated code in production without actually raising an error in production. The use of ThrottledBugsnag is recommended in case there are high-frequency, untested code paths that use the deprecated code to prevent sampling or throttling issues in your bug tracking tool.
|
162
|
+
|
163
|
+
## Types of things to deprecate
|
164
|
+
There are endless things you might deprecate, here are some examples
|
165
|
+
- Public API change - A method call. You might want to deprecate a method, either deleting it entirely or renaming it. Tossing a `deprecate!` call in there will let clients know when they need to migrate.
|
166
|
+
- Public API change - arguments. You might want to change an optional argument to be required, add or remove an argument, or change an argument's type. You can check for future incompatibility and use this gem when a client is invoking deprecated behavior.
|
167
|
+
- A product scenario. You might want to deprecate when a query method returns `nil` when the thing you are looking for, such as a `User`, should always exist.
|
168
|
+
- A database condition. Perhaps you want to add a `non-nil` column, but aren't positive that `nil` is never persisted into that column (when querying the column is not enough, because `nil` could happen in a non-terminal condition in an ongoing request). If you use something like `ActiveRecord`, you could create a validator for this:
|
169
|
+
```ruby
|
170
|
+
# Usage:
|
171
|
+
#
|
172
|
+
# class User < ApplicationRecord
|
173
|
+
# validates_with PresenceOfColumnSoftValidator, columns: [:name]
|
174
|
+
# end
|
175
|
+
#
|
176
|
+
class PresenceOfColumnSoftValidator < ActiveModel::Validator
|
177
|
+
extend T::Sig
|
178
|
+
|
179
|
+
sig { params(model: T.untyped).returns(T::Boolean) }
|
180
|
+
def validate(model)
|
181
|
+
columns_to_validate = options[:columns]
|
182
|
+
columns_to_validate.each do |column|
|
183
|
+
next unless model.public_send(column).nil?
|
184
|
+
DeprecationHelper.deprecate!(
|
185
|
+
"#{model.class.name}##{column} should never be nil",
|
186
|
+
)
|
187
|
+
end
|
188
|
+
|
189
|
+
true
|
190
|
+
end
|
191
|
+
end
|
192
|
+
```
|
193
|
+
- A graceful exit. A place in your code might `rescue` some arbitrary condition, and you'd like to later on stop rescuing. You can use `deprecate!` as a safe way to remove that rescue.
|
194
|
+
- Any general assumption. You might want to make a simplifying change to your application, but that change relies on a hard-to-statically-verify assumption in your system, such as some other state in the system, or some sequence of operations. You can use `deprecate!` as a general way to verify assumptions, and call `deprecate!` when your assumption turns out to be false.
|
195
|
+
|
196
|
+
## Why you might not want to use this gem
|
197
|
+
In an ideal world, we could "deprecate" by simply calling `raise "This is deprecated"`, and we've fully covered all supported scenarios in our test suite, and running our test suite would reveal all deprecations before we go to production
|
198
|
+
|
199
|
+
In another ideal world, we recognize our test suite isn't perfect, but as a forcing function, we continue to `raise` and backfill tests as errors come into production.
|
200
|
+
|
201
|
+
However if your application is like the one I work in, a lot of supported and critical scenarios are in fact untested. This means applying this approach will potentially have negative customer impact.
|
202
|
+
|
203
|
+
When using this gem, it is generally recommended to backfill test coverage for any deprecation that isn't caught by your test suite. Ultimately, when this gem is used, it means you cannot fully trust your test suite. As a long-term goal, it might be advantageous to move towards getting to a place where you can confidently just call `raise` in your codebase and rely on your test suite. Another better systematic approach is to statically type your code base, in which case certain types of deprecations, such as removing a method, can be caught before going to production even without a test suite.
|
204
|
+
|
205
|
+
## Development
|
206
|
+
|
207
|
+
Run `bundle exec rspec` to run all tests.
|
208
|
+
|
209
|
+
## Contributing
|
210
|
+
|
211
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/deprecation_helper.
|
@@ -0,0 +1,25 @@
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = 'deprecation_helper'
|
6
|
+
spec.version = '0.1.0'
|
7
|
+
spec.authors = ['Alex Evanczuk']
|
8
|
+
spec.email = ['alex.evanczuk@gusto.com']
|
9
|
+
|
10
|
+
spec.summary = 'This is a simple, low-dependency gem for managing deprecations.'
|
11
|
+
|
12
|
+
# Specify which files should be added to the gem when it is released.
|
13
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
14
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
15
|
+
`git ls-files -z`.split("\u0000").reject { |f| f.match(%r{A(test|spec|features)/}) }
|
16
|
+
end
|
17
|
+
spec.bindir = 'exe'
|
18
|
+
spec.executables = spec.files.grep(%r{Aexe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'sorbet-runtime', '~> 0.5.6293'
|
22
|
+
|
23
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
24
|
+
spec.add_development_dependency 'sorbet', '~> 0.5.6293'
|
25
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'sorbet-runtime'
|
5
|
+
require 'deprecation_helper/private'
|
6
|
+
require 'deprecation_helper/private/configuration'
|
7
|
+
require 'deprecation_helper/private/allow_list'
|
8
|
+
|
9
|
+
require 'deprecation_helper/strategies/base_strategy_interface'
|
10
|
+
require 'deprecation_helper/strategies/error_strategy_interface'
|
11
|
+
require 'deprecation_helper/strategies/raise_error'
|
12
|
+
require 'deprecation_helper/strategies/log_error'
|
13
|
+
|
14
|
+
require 'deprecation_helper/deprecation_exception'
|
15
|
+
|
16
|
+
module DeprecationHelper
|
17
|
+
extend T::Sig
|
18
|
+
|
19
|
+
################################################################################################
|
20
|
+
#### PUBLIC API
|
21
|
+
################################################################################################
|
22
|
+
sig { params(blk: T.proc.params(arg0: Private::Configuration).void).void }
|
23
|
+
def self.configure(&blk)
|
24
|
+
blk.call config
|
25
|
+
end
|
26
|
+
|
27
|
+
sig do
|
28
|
+
params(
|
29
|
+
message: String,
|
30
|
+
allow_list: T::Array[Regexp],
|
31
|
+
deprecation_strategies: T.nilable(T::Array[Strategies::BaseStrategyInterface]),
|
32
|
+
).void
|
33
|
+
end
|
34
|
+
def self.deprecate!(message, allow_list: [], deprecation_strategies: nil)
|
35
|
+
backtrace = caller
|
36
|
+
return if Private::AllowList.allowed?(allow_list, backtrace)
|
37
|
+
(deprecation_strategies || config.deprecation_strategies).each do |strategy|
|
38
|
+
strategy.apply!(message, backtrace)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# This method is exposed as it might be useful for other systems that want to
|
43
|
+
# reuse the global configuration more explicitly
|
44
|
+
sig { returns(T::Array[Strategies::BaseStrategyInterface]) }
|
45
|
+
def self.deprecation_strategies
|
46
|
+
config.deprecation_strategies
|
47
|
+
end
|
48
|
+
|
49
|
+
################################################################################################
|
50
|
+
#### PRIVATE API
|
51
|
+
################################################################################################
|
52
|
+
|
53
|
+
sig { returns(Private::Configuration) }
|
54
|
+
def self.config
|
55
|
+
@config = T.let(@config, T.nilable(Private::Configuration))
|
56
|
+
@config ||= Private::Configuration.new
|
57
|
+
end
|
58
|
+
|
59
|
+
private_class_method :config
|
60
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DeprecationHelper
|
4
|
+
module Private
|
5
|
+
class AllowList
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { params(allowable_frames: T::Array[Regexp], exception_frames: T::Array[String]).returns(T::Boolean) }
|
9
|
+
def self.allowed?(allowable_frames, exception_frames)
|
10
|
+
allowable_frames.any? do |allowable_frame|
|
11
|
+
exception_frames.any? do |exception_frame|
|
12
|
+
exception_frame.match?(allowable_frame)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DeprecationHelper
|
4
|
+
module Private
|
5
|
+
class Configuration
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { params(deprecation_strategies: T::Array[DeprecationHelper::Strategies::BaseStrategyInterface]).void }
|
9
|
+
attr_writer :deprecation_strategies
|
10
|
+
|
11
|
+
sig { void }
|
12
|
+
def initialize
|
13
|
+
@deprecation_strategies = T.let(@deprecation_strategies, T.nilable(T::Array[DeprecationHelper::Strategies::BaseStrategyInterface]))
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { returns(T::Array[DeprecationHelper::Strategies::BaseStrategyInterface]) }
|
17
|
+
def deprecation_strategies
|
18
|
+
@deprecation_strategies || []
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DeprecationHelper
|
4
|
+
module Strategies
|
5
|
+
# This is the interface for all strategies
|
6
|
+
module BaseStrategyInterface
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Helpers
|
9
|
+
|
10
|
+
interface!
|
11
|
+
|
12
|
+
sig { abstract.params(message: String, backtrace: T::Array[String]).void }
|
13
|
+
def apply!(message, backtrace)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DeprecationHelper
|
4
|
+
module Strategies
|
5
|
+
module ErrorStrategyInterface
|
6
|
+
extend T::Sig
|
7
|
+
extend T::Helpers
|
8
|
+
include BaseStrategyInterface
|
9
|
+
|
10
|
+
abstract!
|
11
|
+
|
12
|
+
sig { override.params(message: String, backtrace: T::Array[String]).void }
|
13
|
+
def apply!(message, backtrace)
|
14
|
+
exception = DeprecationException.new(message)
|
15
|
+
exception.set_backtrace(backtrace)
|
16
|
+
apply_to_exception!(exception)
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { abstract.params(exception: StandardError).void }
|
20
|
+
def apply_to_exception!(exception)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DeprecationHelper
|
4
|
+
module Strategies
|
5
|
+
# This strategy does nothing
|
6
|
+
class LogError
|
7
|
+
include BaseStrategyInterface
|
8
|
+
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { params(logger: T.nilable(Logger)).void }
|
12
|
+
def initialize(logger: nil)
|
13
|
+
@logger = T.let(logger || Logger.new(STDOUT), Logger)
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { override.params(message: String, backtrace: T::Array[String]).void }
|
17
|
+
def apply!(message, backtrace) # rubocop:disable Lint/UnusedMethodArgument
|
18
|
+
@logger.warn(message)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DeprecationHelper
|
4
|
+
module Strategies
|
5
|
+
# This strategy raises the original error
|
6
|
+
class RaiseError
|
7
|
+
include BaseStrategyInterface
|
8
|
+
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { override.params(message: String, backtrace: T::Array[String]).void }
|
12
|
+
def apply!(message, backtrace)
|
13
|
+
exception = DeprecationException.new(message)
|
14
|
+
exception.set_backtrace(backtrace)
|
15
|
+
raise exception
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
RSpec.describe DeprecationHelper do
|
2
|
+
describe 'deprecate!' do
|
3
|
+
subject { DeprecationHelper.deprecate!(input_parameter) }
|
4
|
+
|
5
|
+
let(:error_backtrace) do
|
6
|
+
['frame0', 'frame1', 'frame2', 'some_special_complicated_frame']
|
7
|
+
end
|
8
|
+
let(:expected_exception_class) { DeprecationHelper::DeprecationException }
|
9
|
+
let(:message) { 'This thing is deprecated' }
|
10
|
+
let(:input_parameter) { message }
|
11
|
+
|
12
|
+
let(:logger) { sorbet_double(Logger, warn: nil) }
|
13
|
+
|
14
|
+
before do
|
15
|
+
allow(Logger).to receive(:new).with(STDOUT).and_return(logger)
|
16
|
+
allow(DeprecationHelper).to receive(:caller).and_return(error_backtrace)
|
17
|
+
end
|
18
|
+
|
19
|
+
shared_examples 'it raises the error' do |expected_message|
|
20
|
+
it do
|
21
|
+
expect { subject }.to raise_error(expected_exception_class, expected_message)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
shared_examples 'it does not raise an error' do
|
26
|
+
it do
|
27
|
+
expect { subject }.to_not raise_error
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'configured to do nothing' do
|
32
|
+
before do
|
33
|
+
DeprecationHelper.configure do |config|
|
34
|
+
config.deprecation_strategies = []
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it_behaves_like 'it does not raise an error'
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'configured to raise' do
|
42
|
+
before do
|
43
|
+
DeprecationHelper.configure do |config|
|
44
|
+
config.deprecation_strategies = [DeprecationHelper::Strategies::RaiseError.new]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'allow list is not passed in' do
|
49
|
+
it_behaves_like 'it raises the error', 'This thing is deprecated'
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'allow list is passed in' do
|
53
|
+
before { allow(DeprecationHelper).to receive(:caller).and_return(['frame0', 'frame1', 'frame2', 'some_special_complicated_frame']) }
|
54
|
+
|
55
|
+
subject { DeprecationHelper.deprecate!(input_parameter, allow_list: allow_list) }
|
56
|
+
|
57
|
+
context 'allow list is passed in, but empty' do
|
58
|
+
let(:allow_list) { [] }
|
59
|
+
|
60
|
+
it_behaves_like 'it raises the error', 'This thing is deprecated'
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'allow list is passed in, but does not cover permit this exception' do
|
64
|
+
let(:allow_list) { ['frame10'] }
|
65
|
+
|
66
|
+
it_behaves_like 'it raises the error', 'This thing is deprecated'
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'allow list permits this exception with a simple string match list' do
|
70
|
+
let(:allow_list) { ['frame1'] }
|
71
|
+
|
72
|
+
it_behaves_like 'it does not raise an error'
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'allow list permits this exception with a simple regexp match list' do
|
76
|
+
let(:allow_list) { [/frame1/] }
|
77
|
+
|
78
|
+
it_behaves_like 'it does not raise an error'
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'allow list permits this exception with a more complex regexp match list' do
|
82
|
+
let(:allow_list) { [/special.*?frame/] }
|
83
|
+
|
84
|
+
it_behaves_like 'it does not raise an error'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'configured to do log' do
|
90
|
+
before do
|
91
|
+
DeprecationHelper.configure do |config|
|
92
|
+
config.deprecation_strategies = [DeprecationHelper::Strategies::LogError.new]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'logger is not set' do
|
97
|
+
before do
|
98
|
+
DeprecationHelper.configure do |config|
|
99
|
+
config.deprecation_strategies = [DeprecationHelper::Strategies::LogError.new]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it_behaves_like 'it does not raise an error'
|
104
|
+
|
105
|
+
it 'logs an error' do
|
106
|
+
expect(logger).to receive(:warn).with('This thing is deprecated')
|
107
|
+
subject
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'configured for multiple purposes' do
|
113
|
+
let(:logger) { Logger.new(STDOUT) }
|
114
|
+
|
115
|
+
before do
|
116
|
+
DeprecationHelper.configure do |config|
|
117
|
+
config.deprecation_strategies = [DeprecationHelper::Strategies::LogError.new(logger: logger), DeprecationHelper::Strategies::RaiseError.new]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
it_behaves_like 'it raises the error', 'This thing is deprecated'
|
122
|
+
|
123
|
+
it 'logs an error' do
|
124
|
+
expect(logger).to receive(:warn).with('This thing is deprecated')
|
125
|
+
expect { subject }.to raise_error(StandardError)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'deprecation_helper'
|
3
|
+
|
4
|
+
RSpec.configure do |config|
|
5
|
+
# Enable flags like --only-failures and --next-failure
|
6
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
7
|
+
|
8
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
9
|
+
config.disable_monkey_patching!
|
10
|
+
|
11
|
+
config.expect_with :rspec do |c|
|
12
|
+
c.syntax = :expect
|
13
|
+
end
|
14
|
+
|
15
|
+
config.before do
|
16
|
+
# Reset the config to defaults prior to each test
|
17
|
+
DeprecationHelper.configure do |helper_config|
|
18
|
+
helper_config.deprecation_strategies = []
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def sorbet_double(stubbed_class, attr_map = {})
|
24
|
+
instance_double(stubbed_class, attr_map).tap do |dbl|
|
25
|
+
allow(dbl).to receive(:is_a?) { |tested_class| stubbed_class.ancestors.include?(tested_class) }
|
26
|
+
end
|
27
|
+
end
|
data/test.sh
ADDED
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deprecation_helper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Evanczuk
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-02-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sorbet-runtime
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.5.6293
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.5.6293
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sorbet
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.5.6293
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.5.6293
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- alex.evanczuk@gusto.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rspec"
|
64
|
+
- Gemfile
|
65
|
+
- Gemfile.lock
|
66
|
+
- LICENSE
|
67
|
+
- README.md
|
68
|
+
- deprecation_helper.gemspec
|
69
|
+
- lib/deprecation_helper.rb
|
70
|
+
- lib/deprecation_helper/deprecation_exception.rb
|
71
|
+
- lib/deprecation_helper/private.rb
|
72
|
+
- lib/deprecation_helper/private/allow_list.rb
|
73
|
+
- lib/deprecation_helper/private/configuration.rb
|
74
|
+
- lib/deprecation_helper/strategies/base_strategy_interface.rb
|
75
|
+
- lib/deprecation_helper/strategies/error_strategy_interface.rb
|
76
|
+
- lib/deprecation_helper/strategies/log_error.rb
|
77
|
+
- lib/deprecation_helper/strategies/raise_error.rb
|
78
|
+
- spec/deprecation_helper_spec.rb
|
79
|
+
- spec/spec_helper.rb
|
80
|
+
- test.sh
|
81
|
+
homepage:
|
82
|
+
licenses: []
|
83
|
+
metadata: {}
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubygems_version: 3.0.3
|
100
|
+
signing_key:
|
101
|
+
specification_version: 4
|
102
|
+
summary: This is a simple, low-dependency gem for managing deprecations.
|
103
|
+
test_files: []
|