qonfig 0.24.0 → 0.27.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +62 -0
  3. data/.gitignore +0 -2
  4. data/.rubocop.yml +19 -1
  5. data/CHANGELOG.md +53 -0
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +129 -0
  8. data/LICENSE.txt +1 -1
  9. data/README.md +115 -5
  10. data/Rakefile +0 -1
  11. data/gemfiles/with_external_deps.gemfile +2 -0
  12. data/gemfiles/with_external_deps.gemfile.lock +123 -0
  13. data/gemfiles/without_external_deps.gemfile.lock +113 -0
  14. data/lib/qonfig/commands/definition/load_from_env/value_converter.rb +2 -0
  15. data/lib/qonfig/commands/definition/load_from_env.rb +2 -0
  16. data/lib/qonfig/data_set.rb +3 -0
  17. data/lib/qonfig/imports/direct_key.rb +8 -2
  18. data/lib/qonfig/imports/mappings.rb +8 -2
  19. data/lib/qonfig/loaders/basic.rb +2 -0
  20. data/lib/qonfig/loaders/json.rb +2 -1
  21. data/lib/qonfig/loaders/yaml.rb +3 -1
  22. data/lib/qonfig/plugins/pretty_print/requirements.rb +3 -0
  23. data/lib/qonfig/plugins/pretty_print.rb +1 -0
  24. data/lib/qonfig/plugins/toml/commands/definition/expose_toml.rb +3 -3
  25. data/lib/qonfig/plugins/vault/commands/definition/expose_vault.rb +142 -0
  26. data/lib/qonfig/plugins/vault/commands/definition/load_from_vault.rb +53 -0
  27. data/lib/qonfig/plugins/vault/dsl.rb +35 -0
  28. data/lib/qonfig/plugins/vault/errors.rb +9 -0
  29. data/lib/qonfig/plugins/vault/loaders/vault.rb +74 -0
  30. data/lib/qonfig/plugins/vault.rb +24 -0
  31. data/lib/qonfig/plugins.rb +1 -0
  32. data/lib/qonfig/settings/key_matcher.rb +3 -1
  33. data/lib/qonfig/settings.rb +83 -22
  34. data/lib/qonfig/uploaders/base.rb +2 -0
  35. data/lib/qonfig/uploaders/yaml.rb +2 -0
  36. data/lib/qonfig/version.rb +1 -1
  37. data/lib/qonfig.rb +4 -1
  38. data/qonfig.gemspec +9 -8
  39. data/sig/.keep +0 -0
  40. data/spec/artifacts/.keep +0 -0
  41. data/spec/features/clear_options_spec.rb +92 -0
  42. data/spec/features/compacted_config_spec.rb +308 -0
  43. data/spec/features/composition_spec.rb +207 -0
  44. data/spec/features/config_definition_and_representation_spec.rb +535 -0
  45. data/spec/features/definition_order_spec.rb +69 -0
  46. data/spec/features/dig_functionality_spec.rb +47 -0
  47. data/spec/features/dot_notation_spec.rb +159 -0
  48. data/spec/features/export_settings_spec.rb +138 -0
  49. data/spec/features/expose_json_spec.rb +281 -0
  50. data/spec/features/expose_self/format_option_dynamic_spec.rb +69 -0
  51. data/spec/features/expose_self/format_option_json_spec.rb +74 -0
  52. data/spec/features/expose_self/format_option_unsupported_spec.rb +27 -0
  53. data/spec/features/expose_self/format_option_yaml_spec.rb +77 -0
  54. data/spec/features/expose_self_spec.rb +97 -0
  55. data/spec/features/expose_yaml_spec.rb +263 -0
  56. data/spec/features/freeze_state_spec.rb +122 -0
  57. data/spec/features/get_config_keys_spec.rb +62 -0
  58. data/spec/features/get_config_values_spec.rb +41 -0
  59. data/spec/features/has_a_key_spec.rb +48 -0
  60. data/spec/features/import_settings_spec.rb +323 -0
  61. data/spec/features/indifferent_access_spec.rb +57 -0
  62. data/spec/features/inheritance_spec.rb +110 -0
  63. data/spec/features/instantiation_without_class_definition_spec.rb +59 -0
  64. data/spec/features/iteration_over_setting_keys_spec.rb +48 -0
  65. data/spec/features/load_from_env_spec.rb +240 -0
  66. data/spec/features/load_from_json_spec.rb +97 -0
  67. data/spec/features/load_from_self/format_option_json_spec.rb +31 -0
  68. data/spec/features/load_from_self/format_option_unsupported_spec.rb +27 -0
  69. data/spec/features/load_from_self/format_option_yaml_spec.rb +49 -0
  70. data/spec/features/load_from_self/with_erb_instructions_spec.rb +33 -0
  71. data/spec/features/load_from_self/with_hash_like_data_representation_spec.rb +66 -0
  72. data/spec/features/load_from_self/with_non_hash_like_data_representation_spec.rb +19 -0
  73. data/spec/features/load_from_self/without_end_data_spec.rb +11 -0
  74. data/spec/features/load_from_yaml_spec.rb +110 -0
  75. data/spec/features/load_setting_values_from_file/by_instance_method_examples.rb +171 -0
  76. data/spec/features/load_setting_values_from_file/by_macros_examples.rb +165 -0
  77. data/spec/features/load_setting_values_from_file/load_from_json_spec.rb +21 -0
  78. data/spec/features/load_setting_values_from_file/load_from_self/json_format/end_data_with_env_spec.rb +100 -0
  79. data/spec/features/load_setting_values_from_file/load_from_self/json_format/with_end_data_spec.rb +129 -0
  80. data/spec/features/load_setting_values_from_file/load_from_self/json_format/with_incorrect_end_data_spec.rb +34 -0
  81. data/spec/features/load_setting_values_from_file/load_from_self/json_format/without_end_data_spec.rb +65 -0
  82. data/spec/features/load_setting_values_from_file/load_from_self/yaml_format/end_data_with_env_spec.rb +94 -0
  83. data/spec/features/load_setting_values_from_file/load_from_self/yaml_format/with_end_data_spec.rb +126 -0
  84. data/spec/features/load_setting_values_from_file/load_from_self/yaml_format/with_incorrect_end_data_spec.rb +32 -0
  85. data/spec/features/load_setting_values_from_file/load_from_self/yaml_format/without_end_data_spec.rb +65 -0
  86. data/spec/features/load_setting_values_from_file/load_from_yaml_spec.rb +21 -0
  87. data/spec/features/load_setting_values_from_file/shared_behavior_spec.rb +33 -0
  88. data/spec/features/mixin_spec.rb +387 -0
  89. data/spec/features/non_redefineable_core_methods_spec.rb +29 -0
  90. data/spec/features/plugins/pretty_print_spec.rb +86 -0
  91. data/spec/features/plugins/toml/expose_self/format_option_toml_spec.rb +71 -0
  92. data/spec/features/plugins/toml/expose_toml_spec.rb +221 -0
  93. data/spec/features/plugins/toml/load_from_self/format_option_toml_spec.rb +27 -0
  94. data/spec/features/plugins/toml/load_from_toml_spec.rb +109 -0
  95. data/spec/features/plugins/toml/load_setting_values_from_file/load_from_toml_spec.rb +27 -0
  96. data/spec/features/plugins/toml/load_setting_values_from_file/load_toml_from_self/end_data_with_env_spec.rb +95 -0
  97. data/spec/features/plugins/toml/load_setting_values_from_file/load_toml_from_self/with_end_data_spec.rb +125 -0
  98. data/spec/features/plugins/toml/load_setting_values_from_file/load_toml_from_self/with_incorrect_end_data_spec.rb +34 -0
  99. data/spec/features/plugins/toml/load_setting_values_from_file/load_toml_from_self/without_end_data_spec.rb +65 -0
  100. data/spec/features/plugins/toml/load_setting_values_from_file/shared_behavior_spec.rb +34 -0
  101. data/spec/features/plugins/toml/save_to_toml_spec.rb +149 -0
  102. data/spec/features/plugins/vault/expose_vault_spec.rb +117 -0
  103. data/spec/features/plugins/vault/load_from_vault_spec.rb +80 -0
  104. data/spec/features/plugins_spec.rb +89 -0
  105. data/spec/features/reload_spec.rb +75 -0
  106. data/spec/features/run_code_with_temporary_settings_spec.rb +104 -0
  107. data/spec/features/save_to_file/save_to_json_spec.rb +157 -0
  108. data/spec/features/save_to_file/save_to_yaml_spec.rb +189 -0
  109. data/spec/features/settings_as_predicates_spec.rb +47 -0
  110. data/spec/features/settings_redefinition_spec.rb +30 -0
  111. data/spec/features/slice_functionality_spec.rb +69 -0
  112. data/spec/features/subset_functionality_spec.rb +49 -0
  113. data/spec/features/validation_spec.rb +919 -0
  114. data/spec/fixtures/array_settings.yml +3 -0
  115. data/spec/fixtures/expose_json/incompatible_root_structure.json +6 -0
  116. data/spec/fixtures/expose_json/incompatible_structure.json +4 -0
  117. data/spec/fixtures/expose_json/project.development.json +7 -0
  118. data/spec/fixtures/expose_json/project.json +30 -0
  119. data/spec/fixtures/expose_json/project.production.json +7 -0
  120. data/spec/fixtures/expose_json/project.staging.json +7 -0
  121. data/spec/fixtures/expose_json/project.test.json +7 -0
  122. data/spec/fixtures/expose_yaml/incompatible_structure.yml +2 -0
  123. data/spec/fixtures/expose_yaml/project.development.yml +4 -0
  124. data/spec/fixtures/expose_yaml/project.production.yml +4 -0
  125. data/spec/fixtures/expose_yaml/project.staging.yml +4 -0
  126. data/spec/fixtures/expose_yaml/project.test.yml +4 -0
  127. data/spec/fixtures/expose_yaml/project.yml +25 -0
  128. data/spec/fixtures/json_array_sample.json +14 -0
  129. data/spec/fixtures/json_object_sample.json +9 -0
  130. data/spec/fixtures/json_with_empty_object.json +6 -0
  131. data/spec/fixtures/json_with_erb.json +6 -0
  132. data/spec/fixtures/plugins/toml/expose_toml/project.development.toml +4 -0
  133. data/spec/fixtures/plugins/toml/expose_toml/project.production.toml +4 -0
  134. data/spec/fixtures/plugins/toml/expose_toml/project.staging.toml +4 -0
  135. data/spec/fixtures/plugins/toml/expose_toml/project.test.toml +4 -0
  136. data/spec/fixtures/plugins/toml/expose_toml/project.toml +27 -0
  137. data/spec/fixtures/plugins/toml/mini_file.toml +6 -0
  138. data/spec/fixtures/plugins/toml/toml_sample_with_all_types.toml +72 -0
  139. data/spec/fixtures/plugins/toml/values_file/with_env.toml +13 -0
  140. data/spec/fixtures/plugins/toml/values_file/without_env.toml +6 -0
  141. data/spec/fixtures/rubocop_settings.yml +12 -0
  142. data/spec/fixtures/shared_settings_with_aliases.yml +10 -0
  143. data/spec/fixtures/travis_settings.yml +6 -0
  144. data/spec/fixtures/values_file/with_env.json +19 -0
  145. data/spec/fixtures/values_file/with_env.yml +17 -0
  146. data/spec/fixtures/values_file/without_env.json +8 -0
  147. data/spec/fixtures/values_file/without_env.yml +5 -0
  148. data/spec/fixtures/with_empty_hash.yml +4 -0
  149. data/spec/fixtures/with_erb_instructions.yml +3 -0
  150. data/spec/spec_helper.rb +46 -0
  151. data/spec/support/meta_scopes.rb +20 -0
  152. data/spec/support/spec_support.rb +42 -0
  153. metadata +181 -15
  154. data/.travis.yml +0 -47
@@ -0,0 +1,919 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe 'Validation' do
4
+ describe 'DSL' do
5
+ specify 'fails when validation method isnt chosen' do
6
+ expect do
7
+ # NOTE: no validation block, no dataset method
8
+ Class.new(Qonfig::DataSet) { validate }
9
+ end.to raise_error(Qonfig::ValidatorArgumentError)
10
+ end
11
+
12
+ specify 'fails when you try to use both block validation and method validation' do
13
+ expect do
14
+ Class.new(Qonfig::DataSet) do
15
+ validate '*', by: :test do # NOTE: dataset mtehod
16
+ true
17
+ end # NOTE: block
18
+ end
19
+ end.to raise_error(Qonfig::ValidatorArgumentError)
20
+ end
21
+
22
+ specify 'fails when you try to use both predefined validator and any validation method' do
23
+ expect do
24
+ Class.new(Qonfig::DataSet) do
25
+ validate '*', :integer do
26
+ true
27
+ end
28
+ end
29
+ end.to raise_error(Qonfig::ValidatorArgumentError)
30
+
31
+ expect do
32
+ Class.new(Qonfig::DataSet) do
33
+ validate '*', :integer, by: :check
34
+ end
35
+ end.to raise_error(Qonfig::ValidatorArgumentError)
36
+
37
+ expect do
38
+ Class.new(Qonfig::DataSet) do
39
+ validate '*', :integer, by: :check do
40
+ true
41
+ end
42
+ end
43
+ end.to raise_error(Qonfig::ValidatorArgumentError)
44
+ end
45
+
46
+ specify 'fails when required predefined validator does not exist' do
47
+ # NOTE: incorrect name => error
48
+ expect do
49
+ Class.new(Qonfig::DataSet) do
50
+ validate '*', 1923923
51
+ end
52
+ end.to raise_error(Qonfig::ValidatorArgumentError)
53
+
54
+ # NOTE: nonexistent predefined validator => error
55
+ expect do
56
+ Class.new(Qonfig::DataSet) do
57
+ validate '*', :abracadabra
58
+ end
59
+ end.to raise_error(Qonfig::ValidatorArgumentError)
60
+
61
+ # NOTE: existent predefined validator => ok
62
+ expect do
63
+ Class.new(Qonfig::DataSet) do
64
+ validate '*', :integer
65
+ validate '*', :float
66
+ validate '*', :string
67
+ validate '*', :symbol
68
+ validate '*', :numeric
69
+ validate '*', :hash, strict: true
70
+ validate '*', :array, strict: true
71
+ validate '*', :big_decimal, strict: true
72
+ validate '*', :boolean, strict: true
73
+ end
74
+ end.not_to raise_error
75
+ end
76
+
77
+ specify 'predefned validatiors can be selected by string and symbols' do
78
+ expect do
79
+ Class.new(Qonfig::DataSet) do
80
+ validate 'a', :integer
81
+ validate 'b', 'integer', strict: true
82
+ end
83
+ end.not_to raise_error
84
+ end
85
+
86
+ specify 'dataset method validation (by:): fails on incorrect method name' do
87
+ expect do
88
+ # NOTE: only strings and symbols are supported
89
+ Class.new(Qonfig::DataSet) { validate by: 123 }
90
+ end.to raise_error(Qonfig::ValidatorArgumentError)
91
+
92
+ expect do
93
+ # NOTE: only strings and symbols are supported
94
+ Class.new(Qonfig::DataSet) { validate by: '123' }
95
+ end.not_to raise_error
96
+
97
+ expect do
98
+ # NOTE: only strings and symbols are supported
99
+ Class.new(Qonfig::DataSet) { validate by: :my_method }
100
+ end.not_to raise_error
101
+ end
102
+
103
+ specify 'you can set validation method that is not defined yet' do
104
+ expect do
105
+ Class.new(Qonfig::DataSet) do
106
+ validate by: :my_method # NOTE: not defined method
107
+ validate by: 'another_method' # NOTE: not defined method
108
+ end
109
+ end.not_to raise_error
110
+ end
111
+
112
+ specify 'correct setting key patterns' do
113
+ expect do
114
+ Class.new(Qonfig::DataSet) do
115
+ validate :db do # NOTE: symbol => correct
116
+ end
117
+
118
+ validate 'db.creds' do # NOTE: string => correct
119
+ end
120
+
121
+ validate :user, by: :my_method # NOTE: symbol => correct
122
+ validate 'password', by: :my_method # NOTE: string => correct
123
+ end
124
+ end.not_to raise_error
125
+ end
126
+
127
+ specify 'incorrect setting key patterns' do
128
+ expect do
129
+ Class.new(Qonfig::DataSet) do
130
+ validate 123, by: :my_method
131
+ end
132
+ end.to raise_error(Qonfig::ValidatorArgumentError)
133
+
134
+ expect do
135
+ Class.new(Qonfig::DataSet) do
136
+ validate 123 do
137
+ true
138
+ end
139
+ end
140
+ end.to raise_error(Qonfig::ValidatorArgumentError)
141
+ end
142
+
143
+ specify ':strict and non-strict both can be used' do
144
+ expect do
145
+ Class.new(Qonfig::DataSet) do
146
+ validate(:kek, strict: true) {}
147
+ validate(:pek, strict: false) {}
148
+ end
149
+ end.not_to raise_error
150
+ end
151
+
152
+ specify 'fails with error on non-boolean values of :strict attribute' do
153
+ expect do
154
+ Class.new(Qonfig::DataSet) do
155
+ validate(:check, strict: Object.new) {}
156
+ end
157
+ end.to raise_error(Qonfig::ValidatorArgumentError)
158
+
159
+ expect do
160
+ Class.new(Qonfig::DataSet) do
161
+ validate(:check, strict: nil) {}
162
+ end
163
+ end.to raise_error(Qonfig::ValidatorArgumentError)
164
+ end
165
+ end
166
+
167
+ describe 'validations' do
168
+ let(:config_klass) do
169
+ Class.new(Qonfig::DataSet) do
170
+ setting :telegraf_url, 'test' # NOTE: all right
171
+
172
+ # NOTE: check that telegraf_url is a string value
173
+ validate('telegraf_url', strict: true) do |value|
174
+ value.is_a?(String)
175
+ end
176
+ end
177
+ end
178
+
179
+ specify 'validates invalid settings on instnation' do
180
+ config_klass = Class.new(Qonfig::DataSet) do
181
+ setting :telegraf_url, 12345 # NOTE: should be a string
182
+ validate 'telegraf_url' do |value|
183
+ value.is_a?(String)
184
+ end
185
+ end
186
+
187
+ expect { config_klass.new }.to raise_error(Qonfig::ValidationError)
188
+ end
189
+
190
+ specify 'validates settings defined by configuration options on instantiation' do
191
+ expect { config_klass.new(telegraf_url: '123') }.not_to raise_error
192
+ expect { config_klass.new(telegraf_url: 123) }.to raise_error(Qonfig::ValidationError)
193
+ end
194
+
195
+ specify 'validates settings defined by configuration block' do
196
+ expect do
197
+ config_klass.new.configure do |config|
198
+ config.telegraf_url = 123 # NOTE: should be a string
199
+ end
200
+ end.to raise_error(Qonfig::ValidationError)
201
+ end
202
+
203
+ specify 'validates settings setted in runtime' do
204
+ config = config_klass.new
205
+
206
+ expect { config.settings.telegraf_url = 1234 }.to raise_error(Qonfig::ValidationError)
207
+ expect { config.settings.telegraf_url = '55' }.not_to raise_error
208
+ end
209
+
210
+ specify 'invokes validations on config reload' do
211
+ # reload with hash
212
+ expect do
213
+ config_klass.new.reload!(telegraf_url: 123) # NOTE: should be a string
214
+ end.to raise_error(Qonfig::ValidationError)
215
+
216
+ # reload with configuration block
217
+ expect do
218
+ config_klass.new.reload! do |conf|
219
+ conf.telegraf_url = 123 # NOTE: should be a string
220
+ end
221
+ end.to raise_error(Qonfig::ValidationError)
222
+ end
223
+
224
+ specify 'invokes validations on config clearing' do
225
+ # NOTE:
226
+ # before #clear!: telegraf_config => 'test' (correct value)
227
+ # after #clear!: telegraf_config => nil (incorrect value)
228
+
229
+ expect { config_klass.new.clear! }.to raise_error(Qonfig::ValidationError)
230
+ end
231
+
232
+ specify 'config state after exception interception (#valid? / #validate!)' do
233
+ config = config_klass.new
234
+ expect(config.valid?).to eq(true)
235
+
236
+ begin
237
+ config.clear! # NOTE: set telegraf_url to nil (to incorrect value)
238
+ rescue Qonfig::ValidationError
239
+ # NOOP
240
+ end
241
+
242
+ expect(config.valid?).to eq(false)
243
+ end
244
+
245
+ specify 'deeply nested settings validation' do
246
+ deep_config_klass = Class.new(Qonfig::DataSet) do
247
+ setting :db do
248
+ setting :user, 'D@iVeR'
249
+ setting :password, 'test123'
250
+ end
251
+
252
+ # NOTE: setting key pattern :)
253
+ validate('db.user') { |value| value.is_a?(String) }
254
+ end
255
+
256
+ # NOTE: all right (originally)
257
+ expect { deep_config_klass.new }.not_to raise_error
258
+
259
+ # NOTE: change validated setting to incorrect value
260
+ # rubocop:disable Layout/LineLength
261
+ expect { deep_config_klass.new(db: { user: 123 }) }.to raise_error(Qonfig::ValidationError)
262
+ expect { deep_config_klass.new.settings.db.user = 123 }.to raise_error(Qonfig::ValidationError)
263
+ # rubocop:enable Layout/LineLength
264
+
265
+ # NOTE: change non-validated setting to any value
266
+ expect { deep_config_klass.new(db: { password: 123 }) }.not_to raise_error
267
+ expect { deep_config_klass.new.settings.db.password = 555 }.not_to raise_error
268
+ end
269
+
270
+ specify 'child class inherits the base class validations' do
271
+ base_config_klass = Class.new(Qonfig::DataSet) do
272
+ setting :adapter, 'sidekiq'
273
+ validate(:adapter, strict: true) { |value| value.is_a?(String) }
274
+ end
275
+
276
+ child_config_klass = Class.new(base_config_klass) do
277
+ setting :enabled, false
278
+ validate('enabled') { |value| value.is_a?(TrueClass) || value.is_a?(FalseClass) }
279
+
280
+ # NOTE: should inherit :adapter valdations
281
+ end
282
+
283
+ # NOTE: all right (originally)
284
+ expect { child_config_klass.new }.not_to raise_error
285
+ expect do
286
+ config = child_config_klass.new
287
+ config.settings.adapter = 'resque'
288
+ config.settings.enabled = true
289
+ end.not_to raise_error
290
+
291
+ # NOTE: inherited validations
292
+ # rubocop:disable Layout/LineLength
293
+ expect { child_config_klass.new(adapter: 123) }.to raise_error(Qonfig::ValidationError)
294
+ expect { child_config_klass.new.settings.adapter = 123 }.to raise_error(Qonfig::ValidationError)
295
+ expect { child_config_klass.new { |conf| conf.adapter = 123 } }.to raise_error(Qonfig::ValidationError)
296
+ expect { child_config_klass.new.reload!(adapter: 123) }.to raise_error(Qonfig::ValidationError)
297
+ # rubocop:enable Layout/LineLength
298
+
299
+ config = child_config_klass.new
300
+ expect(config.valid?).to eq(true)
301
+ begin
302
+ config.settings.adapter = 123
303
+ rescue Qonfig::ValidationError
304
+ # NOOP
305
+ end
306
+ expect(config.valid?).to eq(false)
307
+ expect(config.settings.adapter).to eq(123)
308
+
309
+ # NOTE: own validations
310
+ # rubocop:disable Layout/LineLength
311
+ expect { child_config_klass.new(enabled: '123') }.to raise_error(Qonfig::ValidationError)
312
+ expect { child_config_klass.new.settings.enabled = '123' }.to raise_error(Qonfig::ValidationError)
313
+ expect { child_config_klass.new { |conf| conf.enabled = '123' } }.to raise_error(Qonfig::ValidationError)
314
+ expect { child_config_klass.new.reload!(enabled: '123') }.to raise_error(Qonfig::ValidationError)
315
+ expect { child_config_klass.new.clear! }.to raise_error(Qonfig::ValidationError)
316
+ # rubocop:enable Layout/LineLength
317
+
318
+ config = child_config_klass.new
319
+ expect(config.valid?).to eq(true)
320
+ begin
321
+ config.settings.enabled = '123'
322
+ rescue Qonfig::ValidationError
323
+ # NOOP
324
+ end
325
+ expect(config.valid?).to eq(false)
326
+ expect(config.settings.enabled).to eq('123')
327
+ end
328
+
329
+ specify 'config composition inherits validators' do
330
+ config_klass = Class.new(Qonfig::DataSet) do
331
+ setting :telegraf_url, 'udp://localhost:9094'
332
+ validate(:telegraf_url) { |value| value.is_a?(String) }
333
+ end
334
+
335
+ composed_config_klass = Class.new(Qonfig::DataSet) do
336
+ # NOTE: root configs
337
+ compose(config_klass)
338
+
339
+ # NOTE: nested configs
340
+ setting(:nested) { compose(config_klass) }
341
+ end
342
+
343
+ # NOTE: all right
344
+ expect { composed_config_klass.new }.not_to raise_error
345
+ expect do
346
+ config = composed_config_klass.new
347
+ config.settings.telegraf_url = '123'
348
+ config.settings.nested.telegraf_url = '123'
349
+ end.not_to raise_error
350
+
351
+ expect do # NOTE: check root config validation
352
+ composed_config_klass.new.settings.telegraf_url = 123 # NOTE: should be a string
353
+ end.to raise_error(Qonfig::ValidationError)
354
+
355
+ expect do # NOTE: check nested config validation
356
+ composed_config_klass.new.settings.nested.telegraf_url = 123
357
+ end.to raise_error(Qonfig::ValidationError)
358
+ end
359
+
360
+ specify 'setting patterns (validation of a set of configs chosen by setting pattern)' do
361
+ config_klass = Class.new(Qonfig::DataSet) do
362
+ setting :db do
363
+ setting :creds do
364
+ setting :user, 'D@iVeR'
365
+ setting :password, 'test123'
366
+ end
367
+ end
368
+
369
+ setting :sidekiq do
370
+ setting :admin do
371
+ setting :user, 'D@iVeR'
372
+ setting :password, '123test'
373
+ end
374
+
375
+ setting :logger_level, 'info'
376
+ end
377
+
378
+ setting :adapter, :resque
379
+ setting :port, 1019
380
+
381
+ # only the root setting key
382
+ # (port)
383
+ validate('port', strict: true) do |value|
384
+ value.is_a?(Numeric)
385
+ end
386
+
387
+ # all .user setting keys
388
+ # (db.creds.user, sidekiq.admin.user)
389
+ validate('#.user', strict: true) do |value|
390
+ value.is_a?(String)
391
+ end
392
+
393
+ # one level inside db AND all password setting keys there
394
+ # (db.creds.password)
395
+ validate('db.*.password', strict: true) do |value|
396
+ value.is_a?(String)
397
+ end
398
+
399
+ # all .adapter setting keys
400
+ # (adapter)
401
+ validate('#.adapter', strict: true) do |value|
402
+ value.is_a?(Symbol)
403
+ end
404
+
405
+ # all keys inside sidekiq setting group
406
+ # (sidekiq.admin.user, sidekiq.admin.password, sidekiq.logger_level)
407
+ validate('sidekiq.#', strict: true) do |value|
408
+ value.is_a?(String)
409
+ end
410
+ end
411
+
412
+ # NOTE: all right (originally)
413
+ expect { config_klass.new }.not_to raise_error
414
+
415
+ # rubocop:disable Layout/LineLength
416
+ expect { config_klass.new.settings.db.creds.user = 123 }.to raise_error(Qonfig::ValidationError)
417
+ expect { config_klass.new.settings.sidekiq.admin.user = 123 }.to raise_error(Qonfig::ValidationError)
418
+ expect { config_klass.new.settings.sidekiq.admin.password = 123 }.to raise_error(Qonfig::ValidationError)
419
+ expect { config_klass.new.settings.sidekiq.logger_level = nil }.to raise_error(Qonfig::ValidationError)
420
+ expect { config_klass.new.settings.db.creds.password = 123 }.to raise_error(Qonfig::ValidationError)
421
+ expect { config_klass.new.settings.adapter = 'que' }.to raise_error(Qonfig::ValidationError)
422
+ expect { config_klass.new.settings.port = '555' }.to raise_error(Qonfig::ValidationError)
423
+ # rubocop:enable Layout/LineLength
424
+
425
+ expect { config_klass.new.settings.db.creds.user = '123' }.not_to raise_error
426
+ expect { config_klass.new.settings.sidekiq.admin.user = '123' }.not_to raise_error
427
+ expect { config_klass.new.settings.sidekiq.admin.password = '123' }.not_to raise_error
428
+ expect { config_klass.new.settings.sidekiq.logger_level = 'warn' }.not_to raise_error
429
+ expect { config_klass.new.settings.db.creds.password = '123' }.not_to raise_error
430
+ expect { config_klass.new.settings.adapter = :que }.not_to raise_error
431
+ expect { config_klass.new.settings.port = 5599 }.not_to raise_error
432
+ end
433
+
434
+ specify 'proc-based validation wokrs inside dataset instance context' do
435
+ config_klass = Class.new(Qonfig::DataSet) do
436
+ setting :some_key, 555
437
+
438
+ validate 'some_key' do |value|
439
+ value > some_method
440
+ end
441
+
442
+ def some_method
443
+ 123
444
+ end
445
+ end
446
+
447
+ # NOTE: all right (originally)
448
+ expect { config_klass.new }.not_to raise_error
449
+
450
+ # NOTE: invalid value
451
+ expect { config_klass.new(some_key: 122) }.to raise_error(Qonfig::ValidationError)
452
+ # NOTE: valid value
453
+ expect { config_klass.new(some_key: 1234) }.not_to raise_error
454
+ end
455
+
456
+ specify 'setting key validation by custom method defined directly on dataset' do
457
+ config_klass = Class.new(Qonfig::DataSet) do
458
+ setting :db do
459
+ setting :enabled, false
460
+ end
461
+
462
+ # NOTE: validate setting keys by custom dataset method
463
+ validate 'db.#', by: :check_credentials
464
+
465
+ def check_credentials(setting_key_value)
466
+ setting_key_value.is_a?(TrueClass) || setting_key_value.is_a?(FalseClass)
467
+ end
468
+ end
469
+
470
+ # NOTE: all right (orginally)
471
+ expect { config_klass.new }.not_to raise_error
472
+
473
+ # NOTE: invalid values
474
+ expect { config_klass.new.settings.db.enabled = 123 }.to raise_error(Qonfig::ValidationError)
475
+ expect { config_klass.new(db: { enabled: 123 }) }.to raise_error(Qonfig::ValidationError)
476
+
477
+ # NOTE: valid values
478
+ expect { config_klass.new.settings.db.enabled = true }.not_to raise_error
479
+ expect { config_klass.new(db: { enabled: true }) }.not_to raise_error
480
+ end
481
+
482
+ specify 'full data set object validation via #validate' do
483
+ # валидация всего сеттингса скопом (синтаксис "validate { |settings| } / validate by:")
484
+ config_klass = Class.new(Qonfig::DataSet) do
485
+ setting :namespace do
486
+ setting :enabled, :true
487
+ end
488
+
489
+ setting :go_for_cybersport, 'NO'
490
+
491
+ # NOTE: no setting key pattern => full dataset object validation
492
+ validate { settings.namespace.enabled.is_a?(Symbol) }
493
+
494
+ # NOTE: no setting key pattern => full dataset object validation
495
+ validate by: :check_all
496
+
497
+ def check_all
498
+ settings.go_for_cybersport == 'NO'
499
+ end
500
+ end
501
+
502
+ # NOTE: all right (originally)
503
+ expect { config_klass.new }.not_to raise_error
504
+
505
+ # NOTE: invalid values
506
+ # (namespace.enabled should be a symbol)
507
+ expect do
508
+ config_klass.new.settings.namespace.enabled = 123
509
+ end.to raise_error(Qonfig::ValidationError)
510
+ # (go_for_cybersport should have the 'NO' string value)
511
+ expect do
512
+ config_klass.new.settings.go_for_cybersport = 'YES'
513
+ end.to raise_error(Qonfig::ValidationError)
514
+
515
+ # NOTE: valid values
516
+ expect { config_klass.new.settings.namespace.enabled = :false }.not_to raise_error
517
+ expect { config_klass.new.settings.go_for_cybersport = 'NO' }.not_to raise_error
518
+ end
519
+ end
520
+
521
+ describe 'strict behaviour' do
522
+ specify 'non-strict by default (validation ignores nil values)' do
523
+ config = Qonfig::DataSet.build do
524
+ setting :login, 'D@iVeR'
525
+ setting :password, 'atata123'
526
+ setting :enabled, true
527
+
528
+ validate :login, :string
529
+ validate(:password) { |value| value.is_a?(String) }
530
+ validate :enabled, by: :check_enabled_setting
531
+
532
+ def check_enabled_setting(value)
533
+ value.is_a?(FalseClass) || value.is_a?(TrueClass)
534
+ end
535
+ end
536
+
537
+ expect(config.valid?).to eq(true)
538
+ expect { config.settings.login = nil }.not_to raise_error
539
+ expect { config.settings.password = nil }.not_to raise_error
540
+ expect { config.settings.enabled = nil }.not_to raise_error
541
+ expect(config.valid?).to eq(true)
542
+ end
543
+
544
+ specify 'strict validation does not ignore nil value' do
545
+ config = Qonfig::DataSet.build do
546
+ setting :login, '0exp'
547
+ setting :password, 'test123'
548
+ setting :enabled, false
549
+
550
+ validate :login, :string, strict: true
551
+ validate(:password, strict: true) { |value| value.is_a?(String) }
552
+ validate :enabled, by: :check_enabled_setting, strict: true
553
+
554
+ def check_enabled_setting(value)
555
+ value.is_a?(FalseClass) || value.is_a?(TrueClass)
556
+ end
557
+ end
558
+
559
+ expect(config.valid?).to eq(true)
560
+ expect { config.settings.login = nil }.to raise_error(Qonfig::ValidationError)
561
+ expect { config.settings.password = nil }.to raise_error(Qonfig::ValidationError)
562
+ expect { config.settings.enabled = nil }.to raise_error(Qonfig::ValidationError)
563
+ expect(config.valid?).to eq(false)
564
+ end
565
+ end
566
+
567
+ describe 'predefined validators' do
568
+ specify 'common behaviour (strict)' do
569
+ config_klass = Class.new(Qonfig::DataSet) do
570
+ setting :enabled, false
571
+ setting :count, 123
572
+ setting :amount, 23.55
573
+ setting :adapter, 'sidekiq'
574
+ setting :switcher, :on
575
+ setting :data, [1, 2, 3]
576
+ setting :mappings, a: 1, b: 2
577
+ setting :age, 20
578
+
579
+ validate :enabled, :boolean, strict: true
580
+ validate :count, :integer, strict: true
581
+ validate :amount, :float, strict: true
582
+ validate :adapter, :string, strict: true
583
+ validate :switcher, :symbol, strict: true
584
+ validate :data, :array, strict: true
585
+ validate :mappings, :hash, strict: true
586
+ validate :age, :numeric, strict: true
587
+ end
588
+
589
+ # NOTE: all right (originally)
590
+ expect { config_klass.new }.not_to raise_error
591
+
592
+ # NOTE: invalid values
593
+ expect { config_klass.new.settings.enabled = nil }.to raise_error(Qonfig::ValidationError)
594
+ expect { config_klass.new.settings.count = '5' }.to raise_error(Qonfig::ValidationError)
595
+ expect { config_klass.new.settings.amount = 22 }.to raise_error(Qonfig::ValidationError)
596
+ expect { config_klass.new.settings.adapter = :resque }.to raise_error(Qonfig::ValidationError)
597
+ expect { config_klass.new.settings.switcher = 'off' }.to raise_error(Qonfig::ValidationError)
598
+ expect { config_klass.new.settings.data = {} }.to raise_error(Qonfig::ValidationError)
599
+ expect { config_klass.new.settings.mappings = [] }.to raise_error(Qonfig::ValidationError)
600
+ expect { config_klass.new.settings.age = nil }.to raise_error(Qonfig::ValidationError)
601
+
602
+ # NOTE: valid values
603
+ expect { config_klass.new.settings.enabled = true }.not_to raise_error
604
+ expect { config_klass.new.settings.count = 5 }.not_to raise_error
605
+ expect { config_klass.new.settings.amount = 22.0 }.not_to raise_error
606
+ expect { config_klass.new.settings.adapter = 'resque' }.not_to raise_error
607
+ expect { config_klass.new.settings.switcher = :off }.not_to raise_error
608
+ expect { config_klass.new.settings.data = [] }.not_to raise_error
609
+ expect { config_klass.new.settings.mappings = {} }.not_to raise_error
610
+ expect { config_klass.new.settings.age = 20.1 }.not_to raise_error
611
+ end
612
+
613
+ specify 'common behaviour (non-strict)' do
614
+ config_klass = Class.new(Qonfig::DataSet) do
615
+ setting :enabled, false
616
+ setting :count, 123
617
+ setting :amount, 23.55
618
+ setting :adapter, 'sidekiq'
619
+ setting :switcher, :on
620
+ setting :data, [1, 2, 3]
621
+ setting :mappings, a: 1, b: 2
622
+ setting :age, 20
623
+
624
+ validate :enabled, :boolean
625
+ validate :count, :integer
626
+ validate :amount, :float
627
+ validate :adapter, :string
628
+ validate :switcher, :symbol
629
+ validate :data, :array
630
+ validate :mappings, :hash
631
+ validate :age, :numeric
632
+ end
633
+
634
+ # NOTE: all right (originally)
635
+ expect { config_klass.new }.not_to raise_error
636
+
637
+ # NOTE: invalid values
638
+ expect { config_klass.new.settings.count = '5' }.to raise_error(Qonfig::ValidationError)
639
+ expect { config_klass.new.settings.amount = 22 }.to raise_error(Qonfig::ValidationError)
640
+ expect { config_klass.new.settings.adapter = :resque }.to raise_error(Qonfig::ValidationError)
641
+ expect { config_klass.new.settings.switcher = 'off' }.to raise_error(Qonfig::ValidationError)
642
+ expect { config_klass.new.settings.data = {} }.to raise_error(Qonfig::ValidationError)
643
+ expect { config_klass.new.settings.mappings = [] }.to raise_error(Qonfig::ValidationError)
644
+
645
+ # NOTE: non-strict values (validation ignores nil values)
646
+ expect { config_klass.new.settings.age = nil }.not_to raise_error
647
+ expect { config_klass.new.settings.enabled = nil }.not_to raise_error
648
+
649
+ # NOTE: valid values
650
+ expect { config_klass.new.settings.enabled = true }.not_to raise_error
651
+ expect { config_klass.new.settings.count = 5 }.not_to raise_error
652
+ expect { config_klass.new.settings.amount = 22.0 }.not_to raise_error
653
+ expect { config_klass.new.settings.adapter = 'resque' }.not_to raise_error
654
+ expect { config_klass.new.settings.switcher = :off }.not_to raise_error
655
+ expect { config_klass.new.settings.data = [] }.not_to raise_error
656
+ expect { config_klass.new.settings.mappings = {} }.not_to raise_error
657
+ expect { config_klass.new.settings.age = 20.1 }.not_to raise_error
658
+ end
659
+
660
+ describe 'valid_with?' do
661
+ specify '(#valid_with?): config object is valid or invalid with new potenital configs' do
662
+ config = Qonfig::DataSet.build do
663
+ setting :enabled, false
664
+ setting :count, 123
665
+ setting :amount, 23.55
666
+ setting :level, 100_500
667
+ setting :sound do
668
+ setting :genre, 'death-metal'
669
+ end
670
+
671
+ validate :enabled, :boolean
672
+ validate :count, :integer, strict: true
673
+
674
+ validate by: :check_level
675
+ validate 'sound.#' do |value|
676
+ value.is_a?(String)
677
+ end
678
+
679
+ def check_level
680
+ settings.level.is_a?(Numeric)
681
+ end
682
+ end
683
+
684
+ expect(config.valid_with?(
685
+ enabled: true,
686
+ count: 444,
687
+ amount: 66.21,
688
+ level: 123.55,
689
+ sound: { genre: 'melodic-death-metal' }
690
+ )).to eq(true)
691
+
692
+ expect(config.valid_with?(enabled: '123')).to eq(false)
693
+ expect(config.valid_with?(sound: { genre: false })).to eq(false)
694
+ expect(config.valid_with?(level: '2')).to eq(false)
695
+ expect(config.valid_with?(count: nil)).to eq(false)
696
+ expect(config.valid_with?(level: '7', count: nil, enabled: 123)).to eq(false)
697
+
698
+ # NOTE: check that original config does not mutated after validation checking
699
+ expect(config.settings.enabled).to eq(false)
700
+ expect(config.settings.count).to eq(123)
701
+ expect(config.settings.amount).to eq(23.55)
702
+ expect(config.settings.level).to eq(100_500)
703
+ expect(config.settings.sound.genre).to eq('death-metal')
704
+ end
705
+ end
706
+
707
+ specify '(.valid_with?/#valid_with?) support for do-config notation :)' do
708
+ config_klass = Class.new(Qonfig::DataSet) do
709
+ setting :enabled, false
710
+ setting(:db) { setting :user, 'D@iVeR' }
711
+ validate :enabled, :boolean, strict: true
712
+ validate 'db.#', :text, strict: true
713
+ end
714
+
715
+ config = config_klass.new
716
+
717
+ # class-level checker
718
+ expect(
719
+ (config_klass.valid_with?(enabled: true) do |conf|
720
+ conf.db.user = '0exp'
721
+ end)
722
+ ).to eq(true)
723
+ expect(
724
+ (config_klass.valid_with?(enabled: false) do |conf|
725
+ conf.db.user = 123
726
+ end)
727
+ ).to eq(false)
728
+ expect(
729
+ (config_klass.valid_with?(enabled: nil) do |conf|
730
+ conf.db.user = 'test'
731
+ end)
732
+ ).to eq(false)
733
+
734
+ # instance-level checker
735
+ expect(
736
+ (config.valid_with?(enabled: true) do |conf|
737
+ conf.db.user = '0exp'
738
+ end)
739
+ ).to eq(true)
740
+ expect(
741
+ (config.valid_with?(enabled: false) do |conf|
742
+ conf.db.user = 123
743
+ end)
744
+ ).to eq(false)
745
+ expect(
746
+ (config.valid_with?(enabled: nil) do |conf|
747
+ conf.db.user = 'test'
748
+ end)
749
+ ).to eq(false)
750
+
751
+ # original instance is not changed
752
+ expect(config.settings.enabled).to eq(false)
753
+ expect(config.settings.db.user).to eq('D@iVeR')
754
+ end
755
+
756
+ specify '(.valid_with?) potential config instances will be valid or invalid with new configs' do
757
+ config_klass = Class.new(Qonfig::DataSet) do
758
+ setting :enabled, false
759
+ setting :count, 123
760
+ setting :amount, 23.55
761
+ setting :level, 100_500
762
+ setting :sound do
763
+ setting :genre, 'death-metal'
764
+ end
765
+
766
+ validate :enabled, :boolean
767
+ validate :count, :integer, strict: true
768
+
769
+ validate by: :check_level
770
+ validate 'sound.#' do |value|
771
+ value.is_a?(String)
772
+ end
773
+
774
+ def check_level
775
+ settings.level.is_a?(Numeric)
776
+ end
777
+ end
778
+
779
+ expect(config_klass.valid_with?(
780
+ enabled: true,
781
+ count: 444,
782
+ amount: 66.21,
783
+ level: 123.55,
784
+ sound: { genre: 'melodic-death-metal' }
785
+ )).to eq(true)
786
+
787
+ expect(config_klass.valid_with?(enabled: '123')).to eq(false)
788
+ expect(config_klass.valid_with?(sound: { genre: false })).to eq(false)
789
+ expect(config_klass.valid_with?(level: '2')).to eq(false)
790
+ expect(config_klass.valid_with?(count: nil)).to eq(false)
791
+ expect(config_klass.valid_with?(level: '7', count: nil, enabled: 123)).to eq(false)
792
+ end
793
+ end
794
+
795
+ describe 'custom validators' do
796
+ specify 'you can define your own global validators and concrete-class-related validators' do
797
+ # custom global validator
798
+ Qonfig::DataSet.define_validator(:globality) do |value|
799
+ value == 'global'
800
+ end
801
+
802
+ config_klass = Class.new(Qonfig::DataSet) do
803
+ # custom class-related validator
804
+ define_validator(:int_or_sym) do |value|
805
+ value.is_a?(Integer) || value.is_a?(Symbol)
806
+ end
807
+
808
+ # custom class-related validator
809
+ define_validator(:queue_adapter) do |value|
810
+ value == :que || value == :sidekiq
811
+ end
812
+
813
+ setting :some_value, 123
814
+ setting :adapter, :sidekiq
815
+ setting :global, 'global'
816
+
817
+ validate :some_value, :int_or_sym # class-related validator
818
+ validate :adapter, :queue_adapter # class-related validator
819
+ validate :global, :globality, strict: true # global validator + srict cheker
820
+ end
821
+
822
+ expect { config_klass.new }.not_to raise_error
823
+
824
+ # invalid :global setting values
825
+ expect { config_klass.new(global: 123) }.to raise_error(Qonfig::ValidationError)
826
+ expect { config_klass.new(global: nil) }.to raise_error(Qonfig::ValidationError)
827
+ # invalid :some_value setting value
828
+ expect { config_klass.new(some_value: 123.456) }.to raise_error(Qonfig::ValidationError)
829
+ # invalid :adaper setting value
830
+ expect { config_klass.new(adapter: :sneakers) }.to raise_error(Qonfig::ValidationError)
831
+ end
832
+
833
+ specify 'fails when validation logic is not provided at definition step' do
834
+ config_klass = Class.new(Qonfig::DataSet)
835
+
836
+ expect do
837
+ config_klass.define_validator(:simple_validator)
838
+ end.to raise_error(Qonfig::ValidatorArgumentError)
839
+
840
+ expect do
841
+ config_klass.define_validator(:simple_validator) {}
842
+ end.not_to raise_error
843
+ end
844
+
845
+ specify 'inheritance works as expeced' do
846
+ Qonfig::DataSet.define_validator(:global_inheritable) do |value|
847
+ value == 0
848
+ end
849
+
850
+ first_klass = Class.new(Qonfig::DataSet) do
851
+ define_validator(:first_validator) { |value| value == 1 }
852
+ setting :a
853
+ end
854
+
855
+ second_klass = Class.new(first_klass) do
856
+ define_validator(:second_validator) { |value| value == 2 }
857
+ setting :b
858
+ end
859
+
860
+ third_klass = Class.new(second_klass) do
861
+ define_validator(:third_validator) { |value| value == 3 }
862
+ setting :c
863
+ setting :d
864
+
865
+ validate :a, :first_validator
866
+ validate :b, :second_validator
867
+ validate :c, :third_validator
868
+ validate :d, :global_inheritable
869
+ end
870
+
871
+ expect { third_klass.new(a: 1, b: 2, c: 3, d: 0) }.not_to raise_error
872
+ expect { third_klass.new }.not_to raise_error
873
+
874
+ expect { third_klass.new(a: '1') }.to raise_error(Qonfig::ValidationError)
875
+ expect { third_klass.new(b: '2') }.to raise_error(Qonfig::ValidationError)
876
+ expect { third_klass.new(c: '3') }.to raise_error(Qonfig::ValidationError)
877
+ expect { third_klass.new(d: '0') }.to raise_error(Qonfig::ValidationError)
878
+ end
879
+
880
+ specify 'predefined validators can be redefined' do
881
+ # custom global predefined validator
882
+ Qonfig::DataSet.define_validator(:global_predefined) do |value|
883
+ value == :predefined
884
+ end
885
+
886
+ config_klass_with_redefinition = Class.new(Qonfig::DataSet) do
887
+ define_validator(:global_predefined) do |value|
888
+ value == :class_level_predefined
889
+ end
890
+
891
+ setting :some_option
892
+ validate :some_option, :global_predefined
893
+ end
894
+
895
+ config_klass_without_redefinition = Class.new(Qonfig::DataSet) do
896
+ setting :some_option
897
+ validate :some_option, :global_predefined
898
+ end
899
+
900
+ # NOTE: config with redefined validator
901
+ expect do
902
+ # check that redefined validator is used
903
+ config_klass_with_redefinition.new(some_option: :class_level_predefined)
904
+ end.not_to raise_error
905
+ expect do
906
+ # check that global validator is not reached
907
+ config_klass_with_redefinition.new(some_option: :predefined)
908
+ end.to raise_error(Qonfig::ValidationError)
909
+
910
+ # NOTE: config without redefined validator
911
+ expect do
912
+ config_klass_without_redefinition.new(some_option: :class_level_predefined)
913
+ end.to raise_error(Qonfig::ValidationError)
914
+ expect do
915
+ config_klass_without_redefinition.new(some_option: :predefined)
916
+ end.not_to raise_error
917
+ end
918
+ end
919
+ end