confset 1.0.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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +44 -0
  3. data/docs/CHANGELOG.md +15 -0
  4. data/docs/LICENSE.md +29 -0
  5. data/lib/confset/configuration.rb +37 -0
  6. data/lib/confset/integrations/heroku.rb +61 -0
  7. data/lib/confset/integrations/rails/railtie.rb +40 -0
  8. data/lib/confset/integrations/sinatra.rb +27 -0
  9. data/lib/confset/options.rb +187 -0
  10. data/lib/confset/rack/reloader.rb +17 -0
  11. data/lib/confset/sources/env_source.rb +74 -0
  12. data/lib/confset/sources/hash_source.rb +18 -0
  13. data/lib/confset/sources/yaml_source.rb +34 -0
  14. data/lib/confset/tasks/heroku.rake +9 -0
  15. data/lib/confset/validation/error.rb +13 -0
  16. data/lib/confset/validation/schema.rb +22 -0
  17. data/lib/confset/validation/validate.rb +26 -0
  18. data/lib/confset/version.rb +5 -0
  19. data/lib/confset.rb +89 -0
  20. data/lib/generators/confset/install_generator.rb +34 -0
  21. data/lib/generators/confset/templates/confset.rb +58 -0
  22. data/lib/generators/confset/templates/settings/development.yml +0 -0
  23. data/lib/generators/confset/templates/settings/production.yml +0 -0
  24. data/lib/generators/confset/templates/settings/test.yml +0 -0
  25. data/lib/generators/confset/templates/settings.local.yml +0 -0
  26. data/lib/generators/confset/templates/settings.yml +0 -0
  27. data/spec/config_env_spec.rb +238 -0
  28. data/spec/config_spec.rb +468 -0
  29. data/spec/configuration_spec.rb +16 -0
  30. data/spec/fixtures/bool_override/config1.yml +2 -0
  31. data/spec/fixtures/bool_override/config2.yml +2 -0
  32. data/spec/fixtures/custom_types/hash.yml +7 -0
  33. data/spec/fixtures/deep_merge/config1.yml +31 -0
  34. data/spec/fixtures/deep_merge/config2.yml +28 -0
  35. data/spec/fixtures/deep_merge2/config1.yml +3 -0
  36. data/spec/fixtures/deep_merge2/config2.yml +2 -0
  37. data/spec/fixtures/deep_merge3/config1.yml +1 -0
  38. data/spec/fixtures/deep_merge3/config2.yml +1 -0
  39. data/spec/fixtures/development.yml +4 -0
  40. data/spec/fixtures/empty1.yml +0 -0
  41. data/spec/fixtures/empty2.yml +0 -0
  42. data/spec/fixtures/knockout_prefix/config1.yml +40 -0
  43. data/spec/fixtures/knockout_prefix/config2.yml +28 -0
  44. data/spec/fixtures/knockout_prefix/config3.yml +14 -0
  45. data/spec/fixtures/malformed.yml +6 -0
  46. data/spec/fixtures/multilevel.yml +10 -0
  47. data/spec/fixtures/overwrite_arrays/config1.yml +17 -0
  48. data/spec/fixtures/overwrite_arrays/config2.yml +12 -0
  49. data/spec/fixtures/overwrite_arrays/config3.yml +10 -0
  50. data/spec/fixtures/reserved_keywords.yml +7 -0
  51. data/spec/fixtures/settings.yml +22 -0
  52. data/spec/fixtures/settings2.yml +1 -0
  53. data/spec/fixtures/unsafe_load.yml +12 -0
  54. data/spec/fixtures/validation/confset.yml +3 -0
  55. data/spec/fixtures/with_erb.yml +4 -0
  56. data/spec/fixtures/with_malformed_erb.yml +1 -0
  57. data/spec/options_spec.rb +255 -0
  58. data/spec/sources/env_source_spec.rb +81 -0
  59. data/spec/sources/hash_source_spec.rb +49 -0
  60. data/spec/sources/yaml_source_spec.rb +145 -0
  61. data/spec/spec_helper.rb +28 -0
  62. data/spec/support/fixture_helper.rb +14 -0
  63. data/spec/support/rails_helper.rb +24 -0
  64. data/spec/support/shared_contexts/rake.rb +8 -0
  65. data/spec/validation_spec.rb +84 -0
  66. metadata +295 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 78d6fd35c87f305efd074652b7382687bee16e33a696e1ae63ec605cc31d2ff6
4
+ data.tar.gz: c823bdae1fc505a4683a1a5ff2a798d1af65d4abd1f13f40d33594209df99434
5
+ SHA512:
6
+ metadata.gz: c0aff665228c8381d782965c7342b843a3bdf0936917b26085258c1fa86ae8d8069b367fefea9237b1e73c950bb64ba89ff5bcb73d0b7f2fdb3719c749148d9c
7
+ data.tar.gz: 7a8e6957095c1f3dc0f21488c8df1e2a1109db6041579def3ed335584a34a0a3da731baa4eba634fb37d41619fca4046ce429fb3b0e866678ae13589aff4df4c
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env rake
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "bundler/setup"
6
+ rescue LoadError
7
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
8
+ end
9
+
10
+ Bundler::GemHelper.install_tasks
11
+
12
+ begin
13
+ require "rake/testtask"
14
+ require "rubocop/rake_task"
15
+
16
+ RuboCop::RakeTask.new(:rubocop) do |t|
17
+ t.options = ["--display-cop-names"]
18
+ end
19
+ rescue LoadError
20
+ # no rspec available
21
+ end
22
+
23
+ begin
24
+ require "rspec/core/rake_task"
25
+
26
+ RSpec::Core::RakeTask.new(:spec)
27
+ rescue LoadError
28
+ # no rspec available
29
+ end
30
+
31
+ # Documentation
32
+ require "rdoc/task"
33
+
34
+ RDoc::Task.new(:rdoc) do |rdoc|
35
+ rdoc.rdoc_dir = "rdoc"
36
+ rdoc.title = "Confset #{Confset::VERSION}"
37
+ rdoc.options << "--line-numbers"
38
+ rdoc.rdoc_files.include("README.*")
39
+ rdoc.rdoc_files.include("CHANGELOG.*")
40
+ rdoc.rdoc_files.include("LICENSE.*")
41
+ rdoc.rdoc_files.include("lib/**/*.rb")
42
+ end
43
+
44
+ task default: %i[rubocop spec]
data/docs/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## v1.0.0-20220527 - [Danilo Carolino](@danilogco)
4
+
5
+ Initial release
6
+
7
+ Build
8
+
9
+ Refac the project focusing on the newer versions of the Ruby
10
+ language and Ruby on Rails.
11
+
12
+ ---------------------
13
+
14
+ You can find the full changelog of the original "config" gem at the link
15
+ <https://github.com/rubyconfig/config/blob/master/CHANGELOG.md>.
data/docs/LICENSE.md ADDED
@@ -0,0 +1,29 @@
1
+ # The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 DCO Tecnologia
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
13
+ all 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
21
+ THE SOFTWARE.
22
+
23
+ Third-party materials and licenses:
24
+
25
+ * Confset contains Config (<https://github.com/rubyconfig/config>) which is governed by the MIT license
26
+ Copyright (C) 2010-2014 Jacques Crocker, Fred Wu, Piotr Kuczynski
27
+
28
+ * Confset contains Deep Merge (deep_merge.rb) which is governed by the MIT license
29
+ Copyright (C) 2008 Steve Midgley
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Confset
4
+ # The main configuration backbone
5
+ class Configuration < Module
6
+ # Accepts configuration options,
7
+ # initializing a module that can be used to extend
8
+ # the necessary class with the provided config methods
9
+ def initialize(**attributes)
10
+ attributes.each do |name, default|
11
+ define_reader(name, default)
12
+ define_writer(name)
13
+ end
14
+ end
15
+
16
+ private
17
+ def define_reader(name, default)
18
+ variable = :"@#{name}"
19
+
20
+ define_method(name) do
21
+ if instance_variable_defined?(variable)
22
+ instance_variable_get(variable)
23
+ else
24
+ default
25
+ end
26
+ end
27
+ end
28
+
29
+ def define_writer(name)
30
+ variable = :"@#{name}"
31
+
32
+ define_method("#{name}=") do |value|
33
+ instance_variable_set(variable, value)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
4
+
5
+ module Confset
6
+ module Integrations
7
+ class Heroku < Struct.new(:app)
8
+ def invoke
9
+ puts "Setting vars..."
10
+ heroku_command = "config:set #{vars}"
11
+ heroku(heroku_command)
12
+ puts "Vars set:"
13
+ puts heroku_command
14
+ end
15
+
16
+ def vars
17
+ # Load only local options to Heroku
18
+ Confset.load_and_set_settings(
19
+ Rails.root.join("config", "settings.local.yml").to_s,
20
+ Rails.root.join("config", "settings", "#{environment}.local.yml").to_s,
21
+ Rails.root.join("config", "environments", "#{environment}.local.yml").to_s
22
+ )
23
+
24
+ out = ""
25
+ dotted_hash = to_dotted_hash Kernel.const_get(Confset.const_name).to_hash, {}, Confset.const_name
26
+ dotted_hash.each { |key, value| out += " #{key}=#{value} " }
27
+ out
28
+ end
29
+
30
+ def environment
31
+ heroku("run 'echo $RAILS_ENV'").chomp[/(\w+)\z/]
32
+ end
33
+
34
+ def heroku(command)
35
+ with_app = app ? " --app #{app}" : ""
36
+ `heroku #{command}#{with_app}`
37
+ end
38
+
39
+ def `(command)
40
+ Bundler.with_clean_env { super }
41
+ end
42
+
43
+ def to_dotted_hash(source, target = {}, namespace = nil)
44
+ prefix = "#{namespace}." if namespace
45
+ case source
46
+ when Hash
47
+ source.each do |key, value|
48
+ to_dotted_hash(value, target, "#{prefix}#{key}")
49
+ end
50
+ when Array
51
+ source.each_with_index do |value, index|
52
+ to_dotted_hash(value, target, "#{prefix}#{index}")
53
+ end
54
+ else
55
+ target[namespace] = source
56
+ end
57
+ target
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Confset
4
+ module Integrations
5
+ module Rails
6
+ class Railtie < ::Rails::Railtie
7
+ def preload
8
+ # Manually load the custom initializer before everything else
9
+ initializer = ::Rails.root.join("config", "initializers", "confset.rb")
10
+ require initializer if File.exist?(initializer)
11
+
12
+ # Parse the settings before any of the initializers
13
+ Confset.load_and_set_settings(
14
+ Confset.setting_files(::Rails.root.join("config"), ::Rails.env)
15
+ )
16
+ end
17
+
18
+ # Load rake tasks (eg. Heroku)
19
+ rake_tasks do
20
+ Dir[File.join(File.dirname(__FILE__), "../tasks/*.rake")].each { |f| load f }
21
+ end
22
+
23
+ config.before_configuration { preload }
24
+
25
+ # Development environment should reload settings on every request
26
+ if ::Rails.env.development?
27
+ initializer :config_reload_on_development do
28
+ ActiveSupport.on_load :action_controller_base do
29
+ if ::Rails::VERSION::MAJOR >= 4
30
+ prepend_before_action { ::Confset.reload! }
31
+ else
32
+ prepend_before_filter { ::Confset.reload! }
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "confset/rack/reloader"
4
+
5
+ module Confset
6
+ # provide helper to register within your Sinatra app
7
+ #
8
+ # set :root, File.dirname(__FILE__)
9
+ # register Confset
10
+ #
11
+ def self.registered(app)
12
+ app.configure do |inner_app|
13
+ env = inner_app.environment || ENV["RACK_ENV"]
14
+ root = inner_app.root
15
+
16
+ # use Padrino settings if applicable
17
+ if defined?(Padrino)
18
+ env = Padrino.env if Padrino.respond_to?(:env)
19
+ root = Padrino.root if Padrino.respond_to?(:root)
20
+ end
21
+
22
+ Confset.load_and_set_settings(Confset.setting_files(File.join(root, "config"), env))
23
+
24
+ inner_app.use(::Confset::Rack::Reloader) if inner_app.development?
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ostruct"
4
+ require "pathname"
5
+ require "confset/validation/validate"
6
+
7
+ module Confset
8
+ class Options < OpenStruct
9
+ include Enumerable
10
+ include Validation::Validate
11
+
12
+ def keys
13
+ marshal_dump.keys
14
+ end
15
+
16
+ def empty?
17
+ marshal_dump.empty?
18
+ end
19
+
20
+ def add_source!(source)
21
+ # handle yaml file paths
22
+ source = (Sources::YAMLSource.new(source)) if source.is_a?(String) || source.is_a?(::Pathname)
23
+ source = (Sources::HashSource.new(source)) if source.is_a?(Hash)
24
+
25
+ @config_sources ||= []
26
+ @config_sources << source
27
+ end
28
+
29
+ def prepend_source!(source)
30
+ source = (Sources::YAMLSource.new(source)) if source.is_a?(String) || source.is_a?(::Pathname)
31
+ source = (Sources::HashSource.new(source)) if source.is_a?(Hash)
32
+
33
+ @config_sources ||= []
34
+ @config_sources.unshift(source)
35
+ end
36
+
37
+ # look through all our sources and rebuild the configuration
38
+ def reload!
39
+ conf = {}
40
+ @config_sources.each do |source|
41
+ source_conf = source.load
42
+
43
+ if conf.empty?
44
+ conf = source_conf
45
+ else
46
+ DeepMerge.deep_merge!(
47
+ source_conf,
48
+ conf,
49
+ preserve_unmergeables: false,
50
+ knockout_prefix: Confset.knockout_prefix,
51
+ overwrite_arrays: Confset.overwrite_arrays,
52
+ merge_nil_values: Confset.merge_nil_values,
53
+ merge_hash_arrays: Confset.merge_hash_arrays
54
+ )
55
+ end
56
+ end
57
+
58
+ # swap out the contents of the OStruct with a hash (need to recursively convert)
59
+ marshal_load(__convert(conf).marshal_dump)
60
+
61
+ validate!
62
+
63
+ self
64
+ end
65
+
66
+ alias :load! :reload!
67
+
68
+ def reload_from_files(*files)
69
+ Confset.load_and_set_settings(files)
70
+ reload!
71
+ end
72
+
73
+ def to_hash
74
+ result = {}
75
+ marshal_dump.each do |k, v|
76
+ if v.instance_of? Confset::Options
77
+ result[k] = v.to_hash
78
+ elsif v.instance_of? Array
79
+ result[k] = descend_array(v)
80
+ else
81
+ result[k] = v
82
+ end
83
+ end
84
+ result
85
+ end
86
+
87
+ alias :to_h :to_hash
88
+
89
+ def each(*args, &block)
90
+ marshal_dump.each(*args, &block)
91
+ end
92
+
93
+ def to_json(*args)
94
+ require "json" unless defined?(JSON)
95
+ to_hash.to_json(*args)
96
+ end
97
+
98
+ def as_json(options = nil)
99
+ to_hash.as_json(options)
100
+ end
101
+
102
+ def merge!(hash)
103
+ current = to_hash
104
+ DeepMerge.deep_merge!(
105
+ hash.dup,
106
+ current,
107
+ preserve_unmergeables: false,
108
+ knockout_prefix: Confset.knockout_prefix,
109
+ overwrite_arrays: Confset.overwrite_arrays,
110
+ merge_nil_values: Confset.merge_nil_values,
111
+ merge_hash_arrays: Confset.merge_hash_arrays
112
+ )
113
+ marshal_load(__convert(current).marshal_dump)
114
+ self
115
+ end
116
+
117
+ # Some keywords that don't play nicely with OpenStruct
118
+ SETTINGS_RESERVED_NAMES = %w[select collect test count zip min max exit!].freeze
119
+
120
+ # An alternative mechanism for property access.
121
+ # This let's you do foo['bar'] along with foo.bar.
122
+ def [](param)
123
+ return super if SETTINGS_RESERVED_NAMES.include?(param)
124
+ send("#{param}")
125
+ end
126
+
127
+ def []=(param, value)
128
+ send("#{param}=", value)
129
+ end
130
+
131
+ SETTINGS_RESERVED_NAMES.each do |name|
132
+ define_method name do
133
+ self[name]
134
+ end
135
+ end
136
+
137
+ def key?(key)
138
+ table.key?(key)
139
+ end
140
+
141
+ def has_key?(key)
142
+ table.has_key?(key)
143
+ end
144
+
145
+ def method_missing(method_name, *args)
146
+ if Confset.fail_on_missing && method_name !~ /.*(?==\z)/m
147
+ raise KeyError, "key not found: #{method_name.inspect}" unless key?(method_name)
148
+ end
149
+ super
150
+ end
151
+
152
+ def respond_to_missing?(*args)
153
+ super
154
+ end
155
+
156
+ protected
157
+ def descend_array(array)
158
+ array.map do |value|
159
+ if value.instance_of? Confset::Options
160
+ value.to_hash
161
+ elsif value.instance_of? Array
162
+ descend_array(value)
163
+ else
164
+ value
165
+ end
166
+ end
167
+ end
168
+
169
+ # Recursively converts Hashes to Options (including Hashes inside Arrays)
170
+ def __convert(h) # :nodoc:
171
+ s = self.class.new
172
+
173
+ h.each do |k, v|
174
+ k = k.to_s if !k.respond_to?(:to_sym) && k.respond_to?(:to_s)
175
+
176
+ if v.is_a?(Hash)
177
+ v = v["type"] == "hash" ? v["contents"] : __convert(v)
178
+ elsif v.is_a?(Array)
179
+ v = v.collect { |e| e.instance_of?(Hash) ? __convert(e) : e }
180
+ end
181
+
182
+ s[k] = v
183
+ end
184
+ s
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Confset
4
+ module Rack
5
+ # Rack middleware the reloads Confset on every request (only use in dev mode)
6
+ class Reloader
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ Confset.reload!
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Confset
4
+ module Sources
5
+ # Allows settings to be loaded from a "flat" hash with string keys, like ENV.
6
+ class EnvSource
7
+ attr_reader :prefix
8
+ attr_reader :separator
9
+ attr_reader :converter
10
+ attr_reader :parse_values
11
+
12
+ def initialize(env,
13
+ prefix: Confset.env_prefix || Confset.const_name,
14
+ separator: Confset.env_separator,
15
+ converter: Confset.env_converter,
16
+ parse_values: Confset.env_parse_values)
17
+ @env = env
18
+ @prefix = prefix.to_s.split(separator)
19
+ @separator = separator
20
+ @converter = converter
21
+ @parse_values = parse_values
22
+ end
23
+
24
+ def load
25
+ return {} if @env.nil? || @env.empty?
26
+
27
+ hash = Hash.new
28
+
29
+ @env.each do |variable, value|
30
+ keys = variable.to_s.split(separator)
31
+
32
+ next if keys.shift(prefix.size) != prefix
33
+
34
+ keys.map! { |key|
35
+ case converter
36
+ when :downcase then
37
+ key.downcase
38
+ when nil then
39
+ key
40
+ else
41
+ raise "Invalid ENV variables name converter: #{converter}"
42
+ end
43
+ }
44
+
45
+ leaf = keys[0...-1].inject(hash) { |h, key|
46
+ h[key] ||= {}
47
+ }
48
+
49
+ unless leaf.is_a?(Hash)
50
+ conflicting_key = (prefix + keys[0...-1]).join(separator)
51
+ raise "Environment variable #{variable} conflicts with variable #{conflicting_key}"
52
+ end
53
+
54
+ leaf[keys.last] = parse_values ? __value(value) : value
55
+ end
56
+
57
+ hash
58
+ end
59
+
60
+ private
61
+ # Try to convert string to a correct type
62
+ def __value(v)
63
+ case v
64
+ when "false"
65
+ false
66
+ when "true"
67
+ true
68
+ else
69
+ Integer(v) rescue Float(v) rescue v
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Confset
4
+ module Sources
5
+ class HashSource
6
+ attr_accessor :hash
7
+
8
+ def initialize(hash)
9
+ @hash = hash
10
+ end
11
+
12
+ # returns hash that was passed in to initialize
13
+ def load
14
+ hash.is_a?(Hash) ? hash : {}
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "erb"
5
+
6
+ module Confset
7
+ module Sources
8
+ class YAMLSource
9
+ attr_accessor :path
10
+ attr_reader :evaluate_erb
11
+
12
+ def initialize(path, evaluate_erb: Confset.evaluate_erb_in_yaml)
13
+ @path = path.to_s
14
+ @evaluate_erb = !!evaluate_erb
15
+ end
16
+
17
+ # returns a config hash from the YML file
18
+ def load
19
+ if @path && File.exist?(@path)
20
+ file_contents = IO.read(@path)
21
+ file_contents = ERB.new(file_contents).result if evaluate_erb
22
+ result = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(file_contents) : YAML.load(file_contents)
23
+ end
24
+
25
+ result || {}
26
+
27
+ rescue Psych::SyntaxError => e
28
+ raise "YAML syntax error occurred while parsing #{@path}. " \
29
+ "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
30
+ "Error: #{e.message}"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "confset/integrations/heroku"
4
+
5
+ namespace "confset" do
6
+ task :heroku, [:app] => :environment do |_, args|
7
+ Confset::Integrations::Heroku.new(args[:app]).invoke
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Confset
4
+ module Validation
5
+ class Error < StandardError
6
+ def self.format(v_res)
7
+ v_res.errors.group_by(&:path).map do |path, messages|
8
+ "#{' ' * 2}#{path.join('.')}: #{messages.map(&:text).join('; ')}"
9
+ end.join("\n")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Confset
4
+ module Validation
5
+ module Schema
6
+ # Assigns schema configuration option
7
+ def schema=(value)
8
+ @schema = value
9
+ end
10
+
11
+ def schema(&block)
12
+ if block_given?
13
+ # Delay require until optional schema validation is requested
14
+ require "dry-validation"
15
+ @schema = Dry::Schema.define(&block)
16
+ else
17
+ @schema
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "confset/validation/error"
4
+
5
+ module Confset
6
+ module Validation
7
+ module Validate
8
+ def validate!
9
+ validate_using!(Confset.validation_contract)
10
+ validate_using!(Confset.schema)
11
+ end
12
+
13
+ private
14
+ def validate_using!(validator)
15
+ if validator
16
+ result = validator.call(to_hash)
17
+
18
+ return if result.success?
19
+
20
+ error = Confset::Validation::Error.format(result)
21
+ raise Confset::Validation::Error, "Confset validation failed:\n\n#{error}"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Confset
4
+ VERSION = "1.0.0"
5
+ end