env_control 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 54ba47c111ff32909e22570a446fce080e3bb125e79b4f169d43356d5d386017
4
+ data.tar.gz: d8c92caccb90d4bfb6761355a96a6d302216284828e275c26c0d023641ee65ae
5
+ SHA512:
6
+ metadata.gz: afecf900b23cf3660dbd9734f6f6dbf2430010ca65198fc4324e89918175156563561e4a5431985777fb39b614eae590ef3ff7be626f5f3ea3a39ab7d2781dc0
7
+ data.tar.gz: '09696d656df4a917f9ec29f3f1ca0dbc5221d9e91fb18bfff6fd692bb6b548941547893fb73e9df1cbb77c6a84a1a8e5c102c34223bf653013c1fa11f5d3ce49'
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rspec"
data/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.5.0)
5
+ rspec (3.11.0)
6
+ rspec-core (~> 3.11.0)
7
+ rspec-expectations (~> 3.11.0)
8
+ rspec-mocks (~> 3.11.0)
9
+ rspec-core (3.11.0)
10
+ rspec-support (~> 3.11.0)
11
+ rspec-expectations (3.11.0)
12
+ diff-lcs (>= 1.2.0, < 2.0)
13
+ rspec-support (~> 3.11.0)
14
+ rspec-mocks (3.11.1)
15
+ diff-lcs (>= 1.2.0, < 2.0)
16
+ rspec-support (~> 3.11.0)
17
+ rspec-support (3.11.0)
18
+
19
+ PLATFORMS
20
+ x86_64-darwin-18
21
+ x86_64-darwin-21
22
+
23
+ DEPENDENCIES
24
+ rspec
25
+
26
+ BUNDLED WITH
27
+ 2.3.7
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 V.G.
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,230 @@
1
+ # ENV variables contract
2
+
3
+ > Keywords: #p20220707a #env #variable #contract #ruby #gem
4
+
5
+ Ruby approach in creating contracts for ENV variables.
6
+
7
+ ## Why are contracts necessary?
8
+
9
+ Having a contract for ENV vars gives you a number of new benefits:
10
+
11
+ - You explicitly list all useful variables along with their requirements, so both your developers and devops know exactly which values are acceptable and which are not.
12
+ - You prevent your app from starting if there is something wrong with the ENV variables. E.g., you never misuse a production adapter or database in a staging environment (see [best practices](#best-practices)).
13
+ - You bring out the implicitly used ENV variables, revealing the hidden expectations of third-party gems. (As we often cannot change the logic of third-party gems, we're supposed to put up with inconsistency assigning, say, `"on"`/`"off"` to some ENV variables they require, `"true"`/`"false"` or `"true"`/`nil` to others, which makes working with ENV vars a poorly documented mess unless we have exposed it all in our contract).
14
+ - You explicitly declare unused variables as `:deprecated`, `:irrelevant` or `:ignore`, leaving developers no question about the applicability of a particular variable.
15
+
16
+ The larger your application, the more useful the ENV contract gets.
17
+
18
+ ## How to use
19
+
20
+ After [installing](#how-to-install) this gem, define your contract:
21
+
22
+ ```ruby
23
+ EnvControl.configuration.contract = {
24
+ MY_UUID_VAR: :uuid,
25
+ ...
26
+ }
27
+ ```
28
+
29
+ Then validate the ENV variables only *after* they are all set (manually or by [dotenv](https://github.com/bkeepers/dotenv) / [Figaro](https://github.com/laserlemon/figaro)):
30
+
31
+ ```ruby
32
+ EnvControl.validate(ENV)
33
+ ```
34
+
35
+ If the contract has been breached, the `#validate` method raises `EnvControl::BreachOfContractError` exception. This behavior can be [customized](#custom-validation-error-handler) to suit your needs.
36
+
37
+
38
+ ## Contract format explained
39
+
40
+ Consider the following example:
41
+
42
+ ```ruby
43
+ EnvControl.configuration.contract = {
44
+ ADMIN_EMAIL: :email,
45
+ DEBUG: ["true", nil],
46
+ LC_CTYPE: "UTF-8",
47
+ MY_VAR1: :string,
48
+ MY_VAR2: :bool,
49
+ MYSQL_DEBUG: :not_set, # same as nil
50
+ MYSQL_PWD: :deprecated,
51
+ RAILS_ENV: ["production", "development", "test"],
52
+ TMPDIR: :existing_file_path,
53
+ }
54
+ ```
55
+
56
+ The contract is a list of ENV variables and validators you have attached to them.
57
+
58
+ Validators can be:
59
+ - Symbols, that are essentially names of [built-in validators](#built-in-validators).
60
+ - String literals that are exact values to compare the value with.
61
+ - `nil`, which literally means "we expect this variable to be unset".
62
+ - Custom callables (procs, lambdas, any objects that respond to `#call` method)
63
+ - a combination of the above as an Array. In this case, the contract will be considered satisfied if *at least one* of the listed validators is satisfied.
64
+ - [environment-specific](#environment-specific-contracts) contracts.
65
+
66
+
67
+ It is allowed to mix validators of different types:
68
+
69
+ ```ruby
70
+ EnvControl.configuration.contract = {
71
+ # Allowed values: "true" OR "false" OR "weekly" OR "daily" OR "hourly" OR nil
72
+ MY_RETRY: [:bool, "weekly", "daily", "hourly", nil],
73
+ }
74
+ ```
75
+
76
+ ## Built-in validators
77
+
78
+ The EnvControl gem contains several built-in validators that you can use in your contracts.
79
+
80
+ Built-in validators are simply method names specified as symbols, e.g. `:string`, `:uuid`, `:email` etc.
81
+
82
+ These methods take ENV variable as input argument and return 'true' or 'false' depending on its value.
83
+
84
+
85
+ List of built-in validators:
86
+
87
+ | Validator | Acceptable values | Comments |
88
+ |-------------------------|-----------------------|--------------------------|
89
+ | `:bool` | `"true"`, `"false"` | |
90
+ | `:string` | any non-empty string | `" "` considered empty |
91
+ | `:email` | any e-mail address | |
92
+ | `:integer` | any integer string | |
93
+ | `:hex` | hexadecimal numbers | |
94
+ | `:ignore` | `nil` / any string | Allows empty `""` value |
95
+ | `:empty` | `nil` or empty string | Same as `[:not_set, ""]` |
96
+ | `:irrelevant` | `nil` / any string | Synonym for `:ignore` |
97
+ | `:deprecated` | `nil` (not set) | Synonym for `nil` |
98
+ | `:not_set` | `nil` (not set) | Synonym for `nil` |
99
+ | `:uri` | any uri | |
100
+ | `:https_uri` | any secure http uri | |
101
+ | `:postgres_uri` | any postgres uri | |
102
+ | `:uuid` | UUID string | |
103
+ | `:existing_path` | file or folder path | Both files and dirs |
104
+ | `:existing_file_path` | full file path | |
105
+ | `:existing_folder_path` | full folder path | |
106
+
107
+ You can [create your own](#custom-validators) validators if needed.
108
+
109
+ **Important:** Validators only work with non-nil ENV variables. If the variable is not set (nil), the validator won't be called.
110
+
111
+ ## Environment-specific contracts
112
+
113
+ TODO
114
+ ## Custom validators
115
+
116
+ You can create your own validators. There are two approaches available.
117
+
118
+ 1. Callable objects. Validators of this kind must respond to the `#call` method, so they can be `Proc`s, `Lambda`s or custom objects.
119
+
120
+ ```ruby
121
+ class StrongPasswordValidator
122
+ def self.call(string)
123
+ string.match? A_STRONG_PASSWORD_REGEX
124
+ end
125
+ end
126
+
127
+ EnvControl.configuration.contract = {
128
+ DB_PASSWORD: [StrongPasswordValidator, :not_set],
129
+ }
130
+ ```
131
+
132
+ 2. Custom methods to extend `EnvControl::Validators` module. These methods can reuse existing validators, making "AND" logic available to you:
133
+
134
+ ```ruby
135
+ module MyContractValidators
136
+ def irc_uri(string)
137
+ uri(string) && URI(string).scheme.eql?("irc")
138
+ end
139
+ end
140
+
141
+ EnvControl::Validators.extend(MyContractValidators)
142
+
143
+ EnvControl.configuration.contract = {
144
+ IRC_CHANNEL: :irc_uri,
145
+ ...
146
+ }
147
+ ```
148
+
149
+ ## How to install
150
+
151
+ ```bash
152
+ gem install env_control
153
+ ```
154
+
155
+ or add the gem to your Gemfile and then run `bundle install`:
156
+
157
+ ```ruby
158
+ # Gemfile
159
+ gem "env_control"
160
+ ```
161
+
162
+ ## Configuration
163
+
164
+ `EnvControl.configuration` is a configuration object that contains the default settings. You can set its attributes directly or within a block:
165
+
166
+ ```ruby
167
+ require "env_control"
168
+
169
+ EnvControl.configuration do |config|
170
+ config.environment_name = ...
171
+ config.contract = {...}
172
+ config.on_validation_error = MyContractErrorHander.new
173
+ end
174
+
175
+ EnvControl.validate(ENV)
176
+ ```
177
+
178
+ Alternatively, you can provide/override contract using keyword attributes in `#validate` method:
179
+
180
+ ```ruby
181
+ EnvControl.validate(
182
+ ENV,
183
+ environment_name: "review",
184
+ contract: contract,
185
+ on_validation_error: MyContractErrorHander.new,
186
+ )
187
+ ```
188
+
189
+ ### Custom validation error handler
190
+ TODO
191
+
192
+ ### environment_name
193
+ TODO
194
+ ### contract
195
+ TODO
196
+ ### validators_allowing_nil
197
+ TODO
198
+ ### on_validation_error
199
+ TODO
200
+ ## Best practices
201
+
202
+ 1. Maintain the ENV contract up to date so that other developers can use it as a source of truth about the ENV variables requirements. Feel free to add comments to the contract.
203
+ 2. Keep the contract keys alphabetically sorted or group the keys by sub-systems of your application.
204
+ 3. Keep the contract as permissive as you can. Avoid putting sensitive string literals.
205
+ 4. Some validators like `:deprecated` are effectively equivalent to `nil`. Give them preference when you need to accompany a requirement to have a variable unset with an appropriate reason.
206
+ 5. Add `:deprecated` to existing validators before proceeding to remove code that uses the variable., e.g.:
207
+ ```ruby
208
+ MY_VAR: [:deprecated, :string]
209
+ ```
210
+
211
+ 6. Consider defining "virtual" environments via `environment_name=` without introducing them to the application. This may be useful if you, say, need to run your review app in "production" environment but with a more restricted ENV contract:
212
+
213
+ ```ruby
214
+ EnvControl.configuration do |config|
215
+ config.environment_name = \
216
+ if [ENV['RAILS_ENV'], ENV['REVIEW']] == ['production', 'true']
217
+ 'review' # virtual production-like environment
218
+ else
219
+ ENV['RAILS_ENV']
220
+ end
221
+
222
+ config.contract = {
223
+ S3_BUCKET: {
224
+ "production" => :string,
225
+ "review" => "qa_bucket", # safe bucket
226
+ "default" => :not_set
227
+ }
228
+ }
229
+ end
230
+ ````
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/env_control/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "env_control"
7
+ spec.version = EnvControl::VERSION
8
+ spec.authors = ["Vladimir Gorodulin"]
9
+ spec.email = ["ru.hostmaster@gmail.com"]
10
+ spec.description = %q{Contract for ENV variables}
11
+ spec.summary = %q{Contract for ENV variables}
12
+ spec.homepage = "https://github.com/gorodulin/env_control"
13
+ spec.license = "MIT"
14
+
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
16
+
17
+ spec.metadata = {
18
+ "changelog_uri" => "https://github.com/gorodulin/env_control/CHANGELOG.md",
19
+ "homepage_uri" => spec.homepage,
20
+ "source_code_uri" => "https://github.com/gorodulin/env_control",
21
+ }
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
26
+ `git ls-files -z`.split("\x0").reject { _1.match(%r{^(bin/|spec/|config/|\.)}) }
27
+ end
28
+
29
+ spec.require_paths = ["lib"]
30
+ spec.add_development_dependency "rspec", "~> 3.2"
31
+ spec.add_development_dependency "pry"
32
+ spec.add_development_dependency "guard-rspec", "~> 4.7"
33
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module EnvControl
6
+ class Configuration
7
+ include Singleton
8
+
9
+ attr_accessor :on_validation_error, :validators_allowing_nil
10
+ attr_reader :contract, :environment_name
11
+
12
+ DEFAULT_BREACH_HANDLER = lambda do |report|
13
+ fail BreachOfContractError.new(context: { report: report })
14
+ end
15
+
16
+ def initialize
17
+ @contract = {}
18
+ @on_validation_error = DEFAULT_BREACH_HANDLER
19
+ @validators_allowing_nil = [:deprecated, :empty, :ignore, :irrelevant, :not_set]
20
+ end
21
+
22
+ def contract=(hash)
23
+ unless hash.respond_to?(:each_pair)
24
+ raise ArgumentError, "Argument (#{hash.inspect}) must respond to #each_pair"
25
+ end
26
+ @contract = hash
27
+ end
28
+
29
+ def environment_name=(name)
30
+ @environment_name = name.respond_to?(:call) ? name.call&.to_s : name&.to_s
31
+
32
+ raise ArgumentError, "Non-empty string or nil expected" if @environment_name == ""
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,64 @@
1
+
2
+ module EnvControl
3
+
4
+ class Error < StandardError
5
+ def initialize(msg = nil, context:)
6
+ super(details(context))
7
+ end
8
+ end
9
+
10
+ class BreachOfContractError < Error
11
+ MSG = "Some ENV variables breach the contract: %s"
12
+
13
+ def details(context)
14
+ MSG % [context[:report].inspect]
15
+ end
16
+ end
17
+
18
+ class EnvironmentNameNotConfiguredError < Error
19
+ MSG = "Can't pick environment-specific contract for %s variable. " +
20
+ "EnvControl.configuration.environment_name is not set."
21
+
22
+ def details(context)
23
+ MSG % [context[:env_var]]
24
+ end
25
+ end
26
+
27
+ class NonStringEnvironmentNameError < Error
28
+ MSG = "Not a String key %s (%s) for %s contract"
29
+
30
+ def details(context)
31
+ MSG % [
32
+ context[:environment_name].inspect,
33
+ context[:environment_name].class.inspect,
34
+ context[:env_var],
35
+ ]
36
+ end
37
+ end
38
+
39
+ class WrongValueError < Error
40
+ MSG = "Wrong value: %s (%s) for %s contract (%s environment)"
41
+
42
+ def details(context)
43
+ MSG % [
44
+ context[:value].inspect,
45
+ context[:value].class.inspect,
46
+ context[:env_var],
47
+ context[:environment_name] || "any"
48
+ ]
49
+ end
50
+ end
51
+
52
+ class NonSymbolicKeyError < Error
53
+ MSG = "Not a Symbol key %s (%s) for %s contract"
54
+
55
+ def details(context)
56
+ MSG % [
57
+ context[:key].inspect,
58
+ context[:key].class.inspect,
59
+ context[:env_var],
60
+ ]
61
+ end
62
+ end
63
+
64
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EnvControl
4
+ class GetEnvironmentSpecificContract
5
+
6
+ def call(env_var:, contracts:, environment_name:)
7
+ unless environment_name
8
+ raise EnvironmentNameNotConfiguredError.new(context: { env_var: env_var })
9
+ end
10
+
11
+ @contracts = contracts
12
+
13
+ contract_for(environment_name) || contract_for("default")
14
+ end
15
+
16
+ private
17
+
18
+ def contract_for(environment_name)
19
+ return nil unless @contracts.has_key?(environment_name)
20
+
21
+ contract = @contracts[environment_name]
22
+ contract.is_a?(Array) ? contract : [contract]
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EnvControl
4
+ class ValidateEnvContract
5
+
6
+ def call(contract:)
7
+ contract.each_pair do |env_var_name, env_var_contract|
8
+ @env_var = env_var_name
9
+ validate_key!(env_var_name)
10
+ if env_var_contract.is_a?(Hash)
11
+ validate_environment_specific_contract!(env_var_contract)
12
+ else
13
+ validate_contract!(env_var_contract)
14
+ end
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :env_var, :environment_name
21
+
22
+ def validate_environment_specific_contract!(hash)
23
+ raise_wrong_value!(hash) if hash.empty?
24
+ hash.each_pair do |environment_name, contract|
25
+ unless environment_name.is_a?(String)
26
+ raise NonStringEnvironmentNameError.new(context: {environment_name: environment_name, env_var: env_var})
27
+ end
28
+ @environment_name = environment_name
29
+ validate_contract!(contract)
30
+ end
31
+ @environment = nil
32
+ end
33
+
34
+ def validate_contract!(value)
35
+ if value.is_a?(Array)
36
+ raise_wrong_value!(value) if value.empty?
37
+ value.each { |subvalue| validate_value!(subvalue) }
38
+ else
39
+ validate_value!(value)
40
+ end
41
+ end
42
+
43
+ def validate_key!(key)
44
+ return if key.is_a?(Symbol)
45
+
46
+ raise NonSymbolicKeyError.new(context: { key: key, env_var: env_var })
47
+ end
48
+
49
+ def validate_value!(value)
50
+ if [Symbol, String, NilClass].include?(value.class) || value.respond_to?(:call)
51
+ true
52
+ else
53
+ raise_wrong_value!(value)
54
+ end
55
+ end
56
+
57
+ def raise_wrong_value!(value)
58
+ raise WrongValueError.new context: { value: value, env_var: env_var, environment_name: environment_name }
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EnvControl
4
+ class ValidateEnvVariable
5
+
6
+ def call(name, value, contract)
7
+ raise ArgumentError unless [String, NilClass].include?(value.class)
8
+
9
+ [contract].flatten.each do |validator|
10
+ return true if satisfies?(name, value, validator)
11
+ end
12
+ false
13
+ end
14
+
15
+ private
16
+
17
+ def satisfies?(name, value, validator)
18
+ case validator
19
+ when NilClass
20
+ value.nil?
21
+ when String
22
+ value == validator
23
+ when Symbol
24
+ run_validator(validator, name, value)
25
+ else
26
+ raise "unknown validator type: #{validator.inspect}" unless validator.respond_to?(:call)
27
+ run_callable_validator(validator, name, value)
28
+ end
29
+ end
30
+
31
+ def run_callable_validator(validator, name, value)
32
+ return false if value.nil?
33
+
34
+ validator.call(value)
35
+ end
36
+
37
+ def run_validator(validator, name, value)
38
+ unless library.respond_to?(validator)
39
+ raise "unknown validator #{validator.inspect} for #{name} variable"
40
+ end
41
+
42
+ if value.nil?
43
+ return EnvControl.configuration.validators_allowing_nil.include?(validator) ? true : false
44
+ end
45
+
46
+ library.send(validator, value)
47
+ end
48
+
49
+ def library
50
+ @library ||= EnvControl::Validators
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EnvControl
4
+ class ValidateEnvVariables
5
+
6
+ def call(contract:, env:, environment_name:)
7
+ failures = {}
8
+ contract.each do |env_var, var_contracts|
9
+ if environment_specific?(var_contracts)
10
+ var_contract = GetEnvironmentSpecificContract.new.call(env_var: env_var, contracts: var_contracts, environment_name: environment_name)
11
+ else
12
+ var_contract = as_array(var_contracts)
13
+ end
14
+ next unless var_contract # No environment-specific contract found
15
+ var_value = env.fetch(env_var.to_s, nil)
16
+ next if contract_honoured?(env_var, var_value, var_contract)
17
+
18
+ failures[env_var] = var_contract
19
+ end
20
+ failures
21
+ end
22
+
23
+ private
24
+
25
+ def contract_honoured?(env_var, var_value, var_contract)
26
+ ValidateEnvVariable.new.call(env_var, var_value, var_contract)
27
+ end
28
+
29
+ def as_array(value)
30
+ value.is_a?(Array) ? value : [value] # Wraps nil (in contrast to Array.wrap)
31
+ end
32
+
33
+ def environment_specific?(var_contract)
34
+ var_contract.respond_to?(:has_key?)
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module EnvControl
6
+ module Validators
7
+ class << self
8
+
9
+ def bool(val)
10
+ ["true", "false"].include?(val)
11
+ end
12
+
13
+ def email(string)
14
+ string.match?(URI::MailTo::EMAIL_REGEXP)
15
+ end
16
+
17
+ def empty(string)
18
+ string == ""
19
+ end
20
+
21
+ def existing_file_path(path)
22
+ File.file?(path)
23
+ end
24
+
25
+ def existing_folder_path(path)
26
+ Dir.exists?(path)
27
+ end
28
+
29
+ def existing_path(path)
30
+ File.exists?(path)
31
+ end
32
+
33
+ def https_uri(string)
34
+ uri(string) && URI(string).scheme.eql?("https")
35
+ end
36
+
37
+ def ignore(string)
38
+ true
39
+ end
40
+
41
+ def integer(string)
42
+ string.match?(/\A[-]{,1}\d+\z/)
43
+ end
44
+
45
+ def irrelevant(string)
46
+ true
47
+ end
48
+
49
+ def not_set(value)
50
+ false
51
+ end
52
+
53
+ alias_method :deprecated, :not_set
54
+
55
+ def postgres_uri(string)
56
+ uri(string) && URI(string).scheme.eql?("postgres")
57
+ end
58
+
59
+ def string(val)
60
+ val.strip.size > 0
61
+ end
62
+
63
+ def uri(string)
64
+ string.match?(/\A#{URI::regexp}\z/)
65
+ end
66
+
67
+ def uuid(string)
68
+ string.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}\z/i)
69
+ end
70
+
71
+ def hex(string)
72
+ string.match?(/\A[a-f0-9]+\z/i)
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EnvControl
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module EnvControl
6
+
7
+ autoload(:BreachOfContractError, "env_control/errors.rb")
8
+ autoload(:Configuration, "env_control/configuration.rb")
9
+ autoload(:EnvironmentNameNotConfiguredError, "env_control/errors.rb")
10
+ autoload(:GetEnvironmentSpecificContract, "env_control/get_environment_specific_contract.rb")
11
+ autoload(:NonStringEnvironmentNameError, "env_control/errors.rb")
12
+ autoload(:NonSymbolicKeyError, "env_control/errors.rb")
13
+ autoload(:ValidateEnvContract, "env_control/validate_env_contract.rb")
14
+ autoload(:ValidateEnvVariable, "env_control/validate_env_variable.rb")
15
+ autoload(:ValidateEnvVariables, "env_control/validate_env_variables.rb")
16
+ autoload(:Validators, "env_control/validators.rb")
17
+ autoload(:VERSION, "env_control/version.rb")
18
+ autoload(:WrongValueError, "env_control/errors.rb")
19
+
20
+ def self.configuration
21
+ yield Configuration.instance if block_given?
22
+ Configuration.instance
23
+ end
24
+
25
+ def self.validate(
26
+ env,
27
+ contract: configuration.contract,
28
+ environment_name: configuration.environment_name,
29
+ on_error: configuration.on_validation_error
30
+ )
31
+ ValidateEnvContract.new.call(contract: contract)
32
+ ValidateEnvVariables.new.call(env: env, contract: contract, environment_name: environment_name).tap do |report|
33
+ return on_error.call(report) if on_error && report.any?
34
+ end
35
+ end
36
+
37
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: env_control
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Vladimir Gorodulin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-08-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: guard-rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.7'
55
+ description: Contract for ENV variables
56
+ email:
57
+ - ru.hostmaster@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - Gemfile
63
+ - Gemfile.lock
64
+ - LICENSE
65
+ - README.md
66
+ - envcontrol.gemspec
67
+ - lib/env_control.rb
68
+ - lib/env_control/configuration.rb
69
+ - lib/env_control/errors.rb
70
+ - lib/env_control/get_environment_specific_contract.rb
71
+ - lib/env_control/validate_env_contract.rb
72
+ - lib/env_control/validate_env_variable.rb
73
+ - lib/env_control/validate_env_variables.rb
74
+ - lib/env_control/validators.rb
75
+ - lib/env_control/version.rb
76
+ homepage: https://github.com/gorodulin/env_control
77
+ licenses:
78
+ - MIT
79
+ metadata:
80
+ changelog_uri: https://github.com/gorodulin/env_control/CHANGELOG.md
81
+ homepage_uri: https://github.com/gorodulin/env_control
82
+ source_code_uri: https://github.com/gorodulin/env_control
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 2.7.0
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubygems_version: 3.3.7
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: Contract for ENV variables
102
+ test_files: []