activesupport 1.2.4 → 8.1.2

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 (309) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +505 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +40 -0
  5. data/lib/active_support/actionable_error.rb +50 -0
  6. data/lib/active_support/all.rb +5 -0
  7. data/lib/active_support/array_inquirer.rb +50 -0
  8. data/lib/active_support/backtrace_cleaner.rb +234 -0
  9. data/lib/active_support/benchmark.rb +21 -0
  10. data/lib/active_support/benchmarkable.rb +53 -0
  11. data/lib/active_support/broadcast_logger.rb +238 -0
  12. data/lib/active_support/builder.rb +8 -0
  13. data/lib/active_support/cache/coder.rb +153 -0
  14. data/lib/active_support/cache/entry.rb +134 -0
  15. data/lib/active_support/cache/file_store.rb +244 -0
  16. data/lib/active_support/cache/mem_cache_store.rb +288 -0
  17. data/lib/active_support/cache/memory_store.rb +264 -0
  18. data/lib/active_support/cache/null_store.rb +62 -0
  19. data/lib/active_support/cache/redis_cache_store.rb +498 -0
  20. data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
  21. data/lib/active_support/cache/strategy/local_cache.rb +246 -0
  22. data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
  23. data/lib/active_support/cache.rb +1170 -0
  24. data/lib/active_support/callbacks.rb +960 -0
  25. data/lib/active_support/class_attribute.rb +33 -0
  26. data/lib/active_support/code_generator.rb +79 -0
  27. data/lib/active_support/concern.rb +217 -0
  28. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
  29. data/lib/active_support/concurrency/null_lock.rb +13 -0
  30. data/lib/active_support/concurrency/share_lock.rb +225 -0
  31. data/lib/active_support/concurrency/thread_monitor.rb +55 -0
  32. data/lib/active_support/configurable.rb +193 -0
  33. data/lib/active_support/configuration_file.rb +60 -0
  34. data/lib/active_support/continuous_integration.rb +145 -0
  35. data/lib/active_support/core_ext/array/access.rb +100 -0
  36. data/lib/active_support/core_ext/array/conversions.rb +209 -26
  37. data/lib/active_support/core_ext/array/extract.rb +21 -0
  38. data/lib/active_support/core_ext/array/extract_options.rb +31 -0
  39. data/lib/active_support/core_ext/array/grouping.rb +109 -0
  40. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  41. data/lib/active_support/core_ext/array/wrap.rb +48 -0
  42. data/lib/active_support/core_ext/array.rb +8 -4
  43. data/lib/active_support/core_ext/benchmark.rb +6 -0
  44. data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
  45. data/lib/active_support/core_ext/big_decimal.rb +3 -0
  46. data/lib/active_support/core_ext/class/attribute.rb +137 -0
  47. data/lib/active_support/core_ext/class/attribute_accessors.rb +6 -0
  48. data/lib/active_support/core_ext/class/subclasses.rb +24 -0
  49. data/lib/active_support/core_ext/class.rb +4 -0
  50. data/lib/active_support/core_ext/date/acts_like.rb +10 -0
  51. data/lib/active_support/core_ext/date/blank.rb +18 -0
  52. data/lib/active_support/core_ext/date/calculations.rb +161 -0
  53. data/lib/active_support/core_ext/date/conversions.rb +95 -28
  54. data/lib/active_support/core_ext/date/zones.rb +8 -0
  55. data/lib/active_support/core_ext/date.rb +6 -5
  56. data/lib/active_support/core_ext/date_and_time/calculations.rb +374 -0
  57. data/lib/active_support/core_ext/date_and_time/compatibility.rb +23 -0
  58. data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
  59. data/lib/active_support/core_ext/date_time/acts_like.rb +16 -0
  60. data/lib/active_support/core_ext/date_time/blank.rb +18 -0
  61. data/lib/active_support/core_ext/date_time/calculations.rb +215 -0
  62. data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
  63. data/lib/active_support/core_ext/date_time/conversions.rb +108 -0
  64. data/lib/active_support/core_ext/date_time.rb +7 -0
  65. data/lib/active_support/core_ext/digest/uuid.rb +76 -0
  66. data/lib/active_support/core_ext/digest.rb +3 -0
  67. data/lib/active_support/core_ext/enumerable.rb +277 -7
  68. data/lib/active_support/core_ext/erb/util.rb +201 -0
  69. data/lib/active_support/core_ext/file/atomic.rb +72 -0
  70. data/lib/active_support/core_ext/file.rb +3 -0
  71. data/lib/active_support/core_ext/hash/conversions.rb +262 -0
  72. data/lib/active_support/core_ext/hash/deep_merge.rb +43 -0
  73. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  74. data/lib/active_support/core_ext/hash/except.rb +12 -0
  75. data/lib/active_support/core_ext/hash/indifferent_access.rb +19 -55
  76. data/lib/active_support/core_ext/hash/keys.rb +134 -44
  77. data/lib/active_support/core_ext/hash/reverse_merge.rb +22 -22
  78. data/lib/active_support/core_ext/hash/slice.rb +27 -0
  79. data/lib/active_support/core_ext/hash.rb +9 -8
  80. data/lib/active_support/core_ext/integer/inflections.rb +29 -13
  81. data/lib/active_support/core_ext/integer/multiple.rb +12 -0
  82. data/lib/active_support/core_ext/integer/time.rb +22 -0
  83. data/lib/active_support/core_ext/integer.rb +4 -6
  84. data/lib/active_support/core_ext/kernel/concern.rb +14 -0
  85. data/lib/active_support/core_ext/kernel/reporting.rb +45 -0
  86. data/lib/active_support/core_ext/kernel/singleton_class.rb +8 -0
  87. data/lib/active_support/core_ext/kernel.rb +4 -78
  88. data/lib/active_support/core_ext/load_error.rb +6 -35
  89. data/lib/active_support/core_ext/module/aliasing.rb +31 -0
  90. data/lib/active_support/core_ext/module/anonymous.rb +30 -0
  91. data/lib/active_support/core_ext/module/attr_internal.rb +48 -0
  92. data/lib/active_support/core_ext/module/attribute_accessors.rb +214 -0
  93. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +175 -0
  94. data/lib/active_support/core_ext/module/concerning.rb +140 -0
  95. data/lib/active_support/core_ext/module/delegation.rb +225 -0
  96. data/lib/active_support/core_ext/module/deprecation.rb +25 -0
  97. data/lib/active_support/core_ext/module/introspection.rb +65 -0
  98. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  99. data/lib/active_support/core_ext/module/remove_method.rb +17 -0
  100. data/lib/active_support/core_ext/module.rb +13 -0
  101. data/lib/active_support/core_ext/name_error.rb +59 -0
  102. data/lib/active_support/core_ext/numeric/bytes.rb +73 -42
  103. data/lib/active_support/core_ext/numeric/conversions.rb +145 -0
  104. data/lib/active_support/core_ext/numeric/time.rb +64 -57
  105. data/lib/active_support/core_ext/numeric.rb +4 -6
  106. data/lib/active_support/core_ext/object/acts_like.rb +45 -0
  107. data/lib/active_support/core_ext/object/blank.rb +199 -0
  108. data/lib/active_support/core_ext/object/conversions.rb +6 -0
  109. data/lib/active_support/core_ext/object/deep_dup.rb +71 -0
  110. data/lib/active_support/core_ext/object/duplicable.rb +69 -0
  111. data/lib/active_support/core_ext/object/inclusion.rb +37 -0
  112. data/lib/active_support/core_ext/object/instance_variables.rb +32 -0
  113. data/lib/active_support/core_ext/object/json.rb +267 -0
  114. data/lib/active_support/core_ext/object/to_param.rb +3 -0
  115. data/lib/active_support/core_ext/object/to_query.rb +93 -0
  116. data/lib/active_support/core_ext/object/try.rb +158 -0
  117. data/lib/active_support/core_ext/object/with.rb +46 -0
  118. data/lib/active_support/core_ext/object/with_options.rb +101 -0
  119. data/lib/active_support/core_ext/object.rb +17 -0
  120. data/lib/active_support/core_ext/pathname/blank.rb +20 -0
  121. data/lib/active_support/core_ext/pathname/existence.rb +23 -0
  122. data/lib/active_support/core_ext/pathname.rb +4 -0
  123. data/lib/active_support/core_ext/range/compare_range.rb +57 -0
  124. data/lib/active_support/core_ext/range/conversions.rb +58 -17
  125. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  126. data/lib/active_support/core_ext/range/sole.rb +17 -0
  127. data/lib/active_support/core_ext/range.rb +5 -4
  128. data/lib/active_support/core_ext/regexp.rb +14 -0
  129. data/lib/active_support/core_ext/securerandom.rb +57 -0
  130. data/lib/active_support/core_ext/string/access.rb +93 -56
  131. data/lib/active_support/core_ext/string/behavior.rb +8 -0
  132. data/lib/active_support/core_ext/string/conversions.rb +57 -16
  133. data/lib/active_support/core_ext/string/exclude.rb +13 -0
  134. data/lib/active_support/core_ext/string/filters.rb +151 -0
  135. data/lib/active_support/core_ext/string/indent.rb +45 -0
  136. data/lib/active_support/core_ext/string/inflections.rb +297 -54
  137. data/lib/active_support/core_ext/string/inquiry.rb +16 -0
  138. data/lib/active_support/core_ext/string/multibyte.rb +67 -0
  139. data/lib/active_support/core_ext/string/output_safety.rb +235 -0
  140. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -18
  141. data/lib/active_support/core_ext/string/strip.rb +27 -0
  142. data/lib/active_support/core_ext/string/zones.rb +16 -0
  143. data/lib/active_support/core_ext/string.rb +14 -10
  144. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  145. data/lib/active_support/core_ext/symbol.rb +3 -0
  146. data/lib/active_support/core_ext/thread/backtrace/location.rb +7 -0
  147. data/lib/active_support/core_ext/time/acts_like.rb +10 -0
  148. data/lib/active_support/core_ext/time/calculations.rb +358 -153
  149. data/lib/active_support/core_ext/time/compatibility.rb +15 -0
  150. data/lib/active_support/core_ext/time/conversions.rb +69 -30
  151. data/lib/active_support/core_ext/time/zones.rb +97 -0
  152. data/lib/active_support/core_ext/time.rb +6 -6
  153. data/lib/active_support/core_ext.rb +5 -1
  154. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  155. data/lib/active_support/current_attributes.rb +243 -0
  156. data/lib/active_support/deep_mergeable.rb +53 -0
  157. data/lib/active_support/delegation.rb +183 -0
  158. data/lib/active_support/dependencies/autoload.rb +72 -0
  159. data/lib/active_support/dependencies/interlock.rb +55 -0
  160. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  161. data/lib/active_support/dependencies.rb +84 -222
  162. data/lib/active_support/deprecation/behaviors.rb +148 -0
  163. data/lib/active_support/deprecation/constant_accessor.rb +74 -0
  164. data/lib/active_support/deprecation/deprecators.rb +104 -0
  165. data/lib/active_support/deprecation/disallowed.rb +54 -0
  166. data/lib/active_support/deprecation/method_wrappers.rb +68 -0
  167. data/lib/active_support/deprecation/proxy_wrappers.rb +189 -0
  168. data/lib/active_support/deprecation/reporting.rb +162 -0
  169. data/lib/active_support/deprecation.rb +81 -0
  170. data/lib/active_support/deprecator.rb +7 -0
  171. data/lib/active_support/descendants_tracker.rb +112 -0
  172. data/lib/active_support/digest.rb +22 -0
  173. data/lib/active_support/duration/iso8601_parser.rb +123 -0
  174. data/lib/active_support/duration/iso8601_serializer.rb +64 -0
  175. data/lib/active_support/duration.rb +524 -0
  176. data/lib/active_support/editor.rb +70 -0
  177. data/lib/active_support/encrypted_configuration.rb +126 -0
  178. data/lib/active_support/encrypted_file.rb +133 -0
  179. data/lib/active_support/environment_inquirer.rb +40 -0
  180. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  181. data/lib/active_support/error_reporter.rb +318 -0
  182. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  183. data/lib/active_support/event_reporter.rb +592 -0
  184. data/lib/active_support/evented_file_update_checker.rb +185 -0
  185. data/lib/active_support/execution_context/test_helper.rb +13 -0
  186. data/lib/active_support/execution_context.rb +110 -0
  187. data/lib/active_support/execution_wrapper.rb +150 -0
  188. data/lib/active_support/executor/test_helper.rb +7 -0
  189. data/lib/active_support/executor.rb +8 -0
  190. data/lib/active_support/file_update_checker.rb +166 -0
  191. data/lib/active_support/fork_tracker.rb +43 -0
  192. data/lib/active_support/gem_version.rb +17 -0
  193. data/lib/active_support/gzip.rb +41 -0
  194. data/lib/active_support/hash_with_indifferent_access.rb +464 -0
  195. data/lib/active_support/html_safe_translation.rb +56 -0
  196. data/lib/active_support/i18n.rb +17 -0
  197. data/lib/active_support/i18n_railtie.rb +140 -0
  198. data/lib/active_support/inflections.rb +68 -49
  199. data/lib/active_support/inflector/inflections.rb +290 -0
  200. data/lib/active_support/inflector/methods.rb +387 -0
  201. data/lib/active_support/inflector/transliterate.rb +147 -0
  202. data/lib/active_support/inflector.rb +7 -164
  203. data/lib/active_support/isolated_execution_state.rb +76 -0
  204. data/lib/active_support/json/decoding.rb +78 -0
  205. data/lib/active_support/json/encoding.rb +256 -0
  206. data/lib/active_support/json.rb +4 -0
  207. data/lib/active_support/key_generator.rb +66 -0
  208. data/lib/active_support/lazy_load_hooks.rb +107 -0
  209. data/lib/active_support/locale/en.rb +33 -0
  210. data/lib/active_support/locale/en.yml +141 -0
  211. data/lib/active_support/log_subscriber/test_helper.rb +106 -0
  212. data/lib/active_support/log_subscriber.rb +188 -0
  213. data/lib/active_support/logger.rb +55 -0
  214. data/lib/active_support/logger_silence.rb +21 -0
  215. data/lib/active_support/logger_thread_safe_level.rb +50 -0
  216. data/lib/active_support/message_encryptor.rb +374 -0
  217. data/lib/active_support/message_encryptors.rb +193 -0
  218. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  219. data/lib/active_support/message_pack/extensions.rb +310 -0
  220. data/lib/active_support/message_pack/serializer.rb +63 -0
  221. data/lib/active_support/message_pack.rb +50 -0
  222. data/lib/active_support/message_verifier.rb +377 -0
  223. data/lib/active_support/message_verifiers.rb +189 -0
  224. data/lib/active_support/messages/codec.rb +65 -0
  225. data/lib/active_support/messages/metadata.rb +146 -0
  226. data/lib/active_support/messages/rotation_configuration.rb +23 -0
  227. data/lib/active_support/messages/rotation_coordinator.rb +102 -0
  228. data/lib/active_support/messages/rotator.rb +69 -0
  229. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  230. data/lib/active_support/multibyte/chars.rb +188 -0
  231. data/lib/active_support/multibyte/unicode.rb +42 -0
  232. data/lib/active_support/multibyte.rb +27 -0
  233. data/lib/active_support/notifications/fanout.rb +467 -0
  234. data/lib/active_support/notifications/instrumenter.rb +240 -0
  235. data/lib/active_support/notifications.rb +281 -0
  236. data/lib/active_support/number_helper/number_converter.rb +190 -0
  237. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  238. data/lib/active_support/number_helper/number_to_delimited_converter.rb +30 -0
  239. data/lib/active_support/number_helper/number_to_human_converter.rb +69 -0
  240. data/lib/active_support/number_helper/number_to_human_size_converter.rb +60 -0
  241. data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
  242. data/lib/active_support/number_helper/number_to_phone_converter.rb +60 -0
  243. data/lib/active_support/number_helper/number_to_rounded_converter.rb +59 -0
  244. data/lib/active_support/number_helper/rounding_helper.rb +46 -0
  245. data/lib/active_support/number_helper.rb +479 -0
  246. data/lib/active_support/option_merger.rb +38 -0
  247. data/lib/active_support/ordered_hash.rb +50 -0
  248. data/lib/active_support/ordered_options.rb +141 -25
  249. data/lib/active_support/parameter_filter.rb +157 -0
  250. data/lib/active_support/rails.rb +26 -0
  251. data/lib/active_support/railtie.rb +180 -0
  252. data/lib/active_support/reloader.rb +138 -0
  253. data/lib/active_support/rescuable.rb +176 -0
  254. data/lib/active_support/secure_compare_rotator.rb +58 -0
  255. data/lib/active_support/security_utils.rb +38 -0
  256. data/lib/active_support/string_inquirer.rb +35 -0
  257. data/lib/active_support/structured_event_subscriber.rb +99 -0
  258. data/lib/active_support/subscriber.rb +141 -0
  259. data/lib/active_support/syntax_error_proxy.rb +67 -0
  260. data/lib/active_support/tagged_logging.rb +157 -0
  261. data/lib/active_support/test_case.rb +365 -0
  262. data/lib/active_support/testing/assertions.rb +369 -0
  263. data/lib/active_support/testing/autorun.rb +10 -0
  264. data/lib/active_support/testing/constant_lookup.rb +51 -0
  265. data/lib/active_support/testing/constant_stubbing.rb +54 -0
  266. data/lib/active_support/testing/declarative.rb +28 -0
  267. data/lib/active_support/testing/deprecation.rb +82 -0
  268. data/lib/active_support/testing/error_reporter_assertions.rb +124 -0
  269. data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
  270. data/lib/active_support/testing/file_fixtures.rb +38 -0
  271. data/lib/active_support/testing/isolation.rb +121 -0
  272. data/lib/active_support/testing/method_call_assertions.rb +69 -0
  273. data/lib/active_support/testing/notification_assertions.rb +92 -0
  274. data/lib/active_support/testing/parallelization/server.rb +98 -0
  275. data/lib/active_support/testing/parallelization/worker.rb +107 -0
  276. data/lib/active_support/testing/parallelization.rb +79 -0
  277. data/lib/active_support/testing/parallelize_executor.rb +81 -0
  278. data/lib/active_support/testing/setup_and_teardown.rb +57 -0
  279. data/lib/active_support/testing/stream.rb +41 -0
  280. data/lib/active_support/testing/tagged_logging.rb +27 -0
  281. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  282. data/lib/active_support/testing/time_helpers.rb +273 -0
  283. data/lib/active_support/time.rb +20 -0
  284. data/lib/active_support/time_with_zone.rb +613 -0
  285. data/lib/active_support/values/time_zone.rb +599 -158
  286. data/lib/active_support/version.rb +7 -6
  287. data/lib/active_support/xml_mini/jdom.rb +175 -0
  288. data/lib/active_support/xml_mini/libxml.rb +80 -0
  289. data/lib/active_support/xml_mini/libxmlsax.rb +83 -0
  290. data/lib/active_support/xml_mini/nokogiri.rb +83 -0
  291. data/lib/active_support/xml_mini/nokogirisax.rb +86 -0
  292. data/lib/active_support/xml_mini/rexml.rb +137 -0
  293. data/lib/active_support/xml_mini.rb +212 -0
  294. data/lib/active_support.rb +122 -10
  295. metadata +524 -93
  296. data/CHANGELOG +0 -283
  297. data/lib/active_support/binding_of_caller.rb +0 -84
  298. data/lib/active_support/breakpoint.rb +0 -523
  299. data/lib/active_support/class_attribute_accessors.rb +0 -57
  300. data/lib/active_support/class_inheritable_attributes.rb +0 -117
  301. data/lib/active_support/clean_logger.rb +0 -36
  302. data/lib/active_support/core_ext/blank.rb +0 -38
  303. data/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb +0 -14
  304. data/lib/active_support/core_ext/cgi.rb +0 -5
  305. data/lib/active_support/core_ext/exception.rb +0 -29
  306. data/lib/active_support/core_ext/integer/even_odd.rb +0 -24
  307. data/lib/active_support/core_ext/object_and_class.rb +0 -44
  308. data/lib/active_support/module_attribute_accessors.rb +0 -57
  309. data/lib/active_support/whiny_nil.rb +0 -38
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "tempfile"
5
+ require "active_support/message_encryptor"
6
+
7
+ module ActiveSupport
8
+ class EncryptedFile
9
+ class MissingContentError < RuntimeError
10
+ def initialize(content_path)
11
+ super "Missing encrypted content file in #{content_path}."
12
+ end
13
+ end
14
+
15
+ class MissingKeyError < RuntimeError
16
+ def initialize(key_path:, env_key:)
17
+ super \
18
+ "Missing encryption key to decrypt file with. " +
19
+ "Ask your team for your master key and write it to #{key_path} or put it in the ENV['#{env_key}']."
20
+ end
21
+ end
22
+
23
+ class InvalidKeyLengthError < RuntimeError
24
+ def initialize
25
+ super "Encryption key must be exactly #{EncryptedFile.expected_key_length} characters."
26
+ end
27
+ end
28
+
29
+ CIPHER = "aes-128-gcm"
30
+
31
+ def self.generate_key
32
+ SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER))
33
+ end
34
+
35
+ def self.expected_key_length # :nodoc:
36
+ @expected_key_length ||= generate_key.length
37
+ end
38
+
39
+
40
+ attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key
41
+
42
+ def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:)
43
+ @content_path = Pathname.new(content_path).yield_self { |path| path.symlink? ? path.realpath : path }
44
+ @key_path = Pathname.new(key_path)
45
+ @env_key, @raise_if_missing_key = env_key, raise_if_missing_key
46
+ end
47
+
48
+ # Returns the encryption key, first trying the environment variable
49
+ # specified by +env_key+, then trying the key file specified by +key_path+.
50
+ # If +raise_if_missing_key+ is true, raises MissingKeyError if the
51
+ # environment variable is not set and the key file does not exist.
52
+ def key
53
+ read_env_key || read_key_file || handle_missing_key
54
+ end
55
+
56
+ # Returns truthy if #key is truthy. Returns falsy otherwise. Unlike #key,
57
+ # does not raise MissingKeyError when +raise_if_missing_key+ is true.
58
+ def key?
59
+ read_env_key || read_key_file
60
+ end
61
+
62
+ # Reads the file and returns the decrypted content.
63
+ #
64
+ # Raises:
65
+ # - MissingKeyError if the key is missing and +raise_if_missing_key+ is true.
66
+ # - MissingContentError if the encrypted file does not exist or otherwise
67
+ # if the key is missing.
68
+ # - ActiveSupport::MessageEncryptor::InvalidMessage if the content cannot be
69
+ # decrypted or verified.
70
+ def read
71
+ if !key.nil? && content_path.exist?
72
+ decrypt content_path.binread.strip
73
+ else
74
+ raise MissingContentError, content_path
75
+ end
76
+ end
77
+
78
+ def write(contents)
79
+ IO.binwrite "#{content_path}.tmp", encrypt(contents)
80
+ FileUtils.mv "#{content_path}.tmp", content_path
81
+ end
82
+
83
+ def change(&block)
84
+ writing read, &block
85
+ end
86
+
87
+
88
+ private
89
+ def writing(contents)
90
+ Tempfile.create(["", "-" + content_path.basename.to_s.chomp(".enc")]) do |tmp_file|
91
+ tmp_path = Pathname.new(tmp_file)
92
+ tmp_path.binwrite contents
93
+
94
+ yield tmp_path
95
+
96
+ updated_contents = tmp_path.binread
97
+
98
+ write(updated_contents) if updated_contents != contents
99
+ end
100
+ end
101
+
102
+
103
+ def encrypt(contents)
104
+ check_key_length
105
+ encryptor.encrypt_and_sign contents
106
+ end
107
+
108
+ def decrypt(contents)
109
+ encryptor.decrypt_and_verify contents
110
+ end
111
+
112
+ def encryptor
113
+ @encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER, serializer: Marshal)
114
+ end
115
+
116
+
117
+ def read_env_key
118
+ ENV[env_key].presence
119
+ end
120
+
121
+ def read_key_file
122
+ @key_file_contents ||= (key_path.binread.strip if key_path.exist?)
123
+ end
124
+
125
+ def handle_missing_key
126
+ raise MissingKeyError.new(key_path: key_path, env_key: env_key) if raise_if_missing_key
127
+ end
128
+
129
+ def check_key_length
130
+ raise InvalidKeyLengthError if key&.length != self.class.expected_key_length
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/string_inquirer"
4
+ require "active_support/core_ext/object/inclusion"
5
+
6
+ module ActiveSupport
7
+ class EnvironmentInquirer < StringInquirer # :nodoc:
8
+ # Optimization for the three default environments, so this inquirer doesn't need to rely on
9
+ # the slower delegation through method_missing that StringInquirer would normally entail.
10
+ DEFAULT_ENVIRONMENTS = %w[ development test production ]
11
+
12
+ # Environments that'll respond true for #local?
13
+ LOCAL_ENVIRONMENTS = %w[ development test ]
14
+
15
+ def initialize(env)
16
+ raise(ArgumentError, "'local' is a reserved environment name") if env == "local"
17
+
18
+ super(env)
19
+
20
+ DEFAULT_ENVIRONMENTS.each do |default|
21
+ instance_variable_set :"@#{default}", env == default
22
+ end
23
+
24
+ @local = in? LOCAL_ENVIRONMENTS
25
+ end
26
+
27
+ DEFAULT_ENVIRONMENTS.each do |env|
28
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
29
+ def #{env}?
30
+ @#{env}
31
+ end
32
+ RUBY
33
+ end
34
+
35
+ # Returns true if we're in the development or test environment.
36
+ def local?
37
+ @local
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport::ErrorReporter::TestHelper # :nodoc:
4
+ class ErrorSubscriber
5
+ attr_reader :events
6
+
7
+ def initialize
8
+ @events = []
9
+ end
10
+
11
+ def report(error, handled:, severity:, source:, context:)
12
+ @events << [error, handled, severity, source, context]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,318 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ # = Active Support \Error Reporter
5
+ #
6
+ # +ActiveSupport::ErrorReporter+ is a common interface for error reporting services.
7
+ #
8
+ # To rescue and report any unhandled error, you can use the #handle method:
9
+ #
10
+ # Rails.error.handle do
11
+ # do_something!
12
+ # end
13
+ #
14
+ # If an error is raised, it will be reported and swallowed.
15
+ #
16
+ # Alternatively, if you want to report the error but not swallow it, you can use #record:
17
+ #
18
+ # Rails.error.record do
19
+ # do_something!
20
+ # end
21
+ #
22
+ # Both methods can be restricted to handle only a specific error class:
23
+ #
24
+ # maybe_tags = Rails.error.handle(Redis::BaseError) { redis.get("tags") }
25
+ #
26
+ class ErrorReporter
27
+ SEVERITIES = %i(error warning info)
28
+ DEFAULT_SOURCE = "application"
29
+ DEFAULT_RESCUE = [StandardError].freeze
30
+
31
+ attr_accessor :logger, :debug_mode
32
+
33
+ UnexpectedError = Class.new(Exception)
34
+
35
+ def initialize(*subscribers, logger: nil)
36
+ @subscribers = subscribers.flatten
37
+ @logger = logger
38
+ @debug_mode = false
39
+ @context_middlewares = ErrorContextMiddlewareStack.new
40
+ end
41
+
42
+ # Evaluates the given block, reporting and swallowing any unhandled error.
43
+ # If no error is raised, returns the return value of the block. Otherwise,
44
+ # returns the result of +fallback.call+, or +nil+ if +fallback+ is not
45
+ # specified.
46
+ #
47
+ # # Will report a TypeError to all subscribers and return nil.
48
+ # Rails.error.handle do
49
+ # 1 + '1'
50
+ # end
51
+ #
52
+ # Can be restricted to handle only specific error classes:
53
+ #
54
+ # maybe_tags = Rails.error.handle(Redis::BaseError) { redis.get("tags") }
55
+ #
56
+ # ==== Options
57
+ #
58
+ # * +:severity+ - This value is passed along to subscribers to indicate how
59
+ # important the error report is. Can be +:error+, +:warning+, or +:info+.
60
+ # Defaults to +:warning+.
61
+ #
62
+ # * +:context+ - Extra information that is passed along to subscribers. For
63
+ # example:
64
+ #
65
+ # Rails.error.handle(context: { section: "admin" }) do
66
+ # # ...
67
+ # end
68
+ #
69
+ # * +:fallback+ - A callable that provides +handle+'s return value when an
70
+ # unhandled error is raised. For example:
71
+ #
72
+ # user = Rails.error.handle(fallback: -> { User.anonymous }) do
73
+ # User.find_by(params)
74
+ # end
75
+ #
76
+ # * +:source+ - This value is passed along to subscribers to indicate the
77
+ # source of the error. Subscribers can use this value to ignore certain
78
+ # errors. Defaults to <tt>"application"</tt>.
79
+ def handle(*error_classes, severity: :warning, context: {}, fallback: nil, source: DEFAULT_SOURCE)
80
+ error_classes = DEFAULT_RESCUE if error_classes.empty?
81
+ yield
82
+ rescue *error_classes => error
83
+ report(error, handled: true, severity: severity, context: context, source: source)
84
+ fallback.call if fallback
85
+ end
86
+
87
+ # Evaluates the given block, reporting and re-raising any unhandled error.
88
+ # If no error is raised, returns the return value of the block.
89
+ #
90
+ # # Will report a TypeError to all subscribers and re-raise it.
91
+ # Rails.error.record do
92
+ # 1 + '1'
93
+ # end
94
+ #
95
+ # Can be restricted to handle only specific error classes:
96
+ #
97
+ # tags = Rails.error.record(Redis::BaseError) { redis.get("tags") }
98
+ #
99
+ # ==== Options
100
+ #
101
+ # * +:severity+ - This value is passed along to subscribers to indicate how
102
+ # important the error report is. Can be +:error+, +:warning+, or +:info+.
103
+ # Defaults to +:error+.
104
+ #
105
+ # * +:context+ - Extra information that is passed along to subscribers. For
106
+ # example:
107
+ #
108
+ # Rails.error.record(context: { section: "admin" }) do
109
+ # # ...
110
+ # end
111
+ #
112
+ # * +:source+ - This value is passed along to subscribers to indicate the
113
+ # source of the error. Subscribers can use this value to ignore certain
114
+ # errors. Defaults to <tt>"application"</tt>.
115
+ def record(*error_classes, severity: :error, context: {}, source: DEFAULT_SOURCE)
116
+ error_classes = DEFAULT_RESCUE if error_classes.empty?
117
+ yield
118
+ rescue *error_classes => error
119
+ report(error, handled: false, severity: severity, context: context, source: source)
120
+ raise
121
+ end
122
+
123
+ # Either report the given error when in production, or raise it when in development or test.
124
+ #
125
+ # When called in production, after the error is reported, this method will return
126
+ # nil and execution will continue.
127
+ #
128
+ # When called in development, the original error is wrapped in a different error class to ensure
129
+ # it's not being rescued higher in the stack and will be surfaced to the developer.
130
+ #
131
+ # This method is intended for reporting violated assertions about preconditions, or similar
132
+ # cases that can and should be gracefully handled in production, but that aren't supposed to happen.
133
+ #
134
+ # The error can be either an exception instance or a String.
135
+ #
136
+ # example:
137
+ #
138
+ # def edit
139
+ # if published?
140
+ # Rails.error.unexpected("[BUG] Attempting to edit a published article, that shouldn't be possible")
141
+ # return false
142
+ # end
143
+ # # ...
144
+ # end
145
+ #
146
+ def unexpected(error, severity: :warning, context: {}, source: DEFAULT_SOURCE)
147
+ error = RuntimeError.new(error) if error.is_a?(String)
148
+
149
+ if @debug_mode
150
+ ensure_backtrace(error)
151
+ raise UnexpectedError, "#{error.class.name}: #{error.message}", error.backtrace, cause: error
152
+ else
153
+ report(error, handled: true, severity: severity, context: context, source: source)
154
+ end
155
+ end
156
+
157
+ # Register a new error subscriber. The subscriber must respond to
158
+ #
159
+ # report(Exception, handled: Boolean, severity: (:error OR :warning OR :info), context: Hash, source: String)
160
+ #
161
+ # The +report+ method <b>should never</b> raise an error.
162
+ def subscribe(subscriber)
163
+ unless subscriber.respond_to?(:report)
164
+ raise ArgumentError, "Error subscribers must respond to #report"
165
+ end
166
+ @subscribers << subscriber
167
+ end
168
+
169
+ # Unregister an error subscriber. Accepts either a subscriber or a class.
170
+ #
171
+ # subscriber = MyErrorSubscriber.new
172
+ # Rails.error.subscribe(subscriber)
173
+ #
174
+ # Rails.error.unsubscribe(subscriber)
175
+ # # or
176
+ # Rails.error.unsubscribe(MyErrorSubscriber)
177
+ def unsubscribe(subscriber)
178
+ @subscribers.delete_if { |s| subscriber === s }
179
+ end
180
+
181
+ # Prevent a subscriber from being notified of errors for the
182
+ # duration of the block. You may pass in the subscriber itself, or its class.
183
+ #
184
+ # This can be helpful for error reporting service integrations, when they wish
185
+ # to handle any errors higher in the stack.
186
+ def disable(subscriber)
187
+ disabled_subscribers = (ActiveSupport::IsolatedExecutionState[self] ||= [])
188
+ disabled_subscribers << subscriber
189
+ begin
190
+ yield
191
+ ensure
192
+ disabled_subscribers.delete(subscriber)
193
+ end
194
+ end
195
+
196
+ # Update the execution context that is accessible to error subscribers. Any
197
+ # context passed to #handle, #record, or #report will be merged with the
198
+ # context set here.
199
+ #
200
+ # Rails.error.set_context(section: "checkout", user_id: @user.id)
201
+ #
202
+ def set_context(...)
203
+ ActiveSupport::ExecutionContext.set(...)
204
+ end
205
+
206
+ # Add a middleware to modify the error context before it is sent to subscribers.
207
+ #
208
+ # Middleware is added to a stack of callables run on an error's execution context
209
+ # before passing to subscribers. Allows creation of entries in error context that
210
+ # are shared by all subscribers.
211
+ #
212
+ # A context middleware receives the same parameters as #report.
213
+ # It must return a hash - the middleware stack returns the hash after it has
214
+ # run through all middlewares. A middleware can mutate or replace the hash.
215
+ #
216
+ # Rails.error.add_middleware(-> (error, context) { context.merge({ foo: :bar }) })
217
+ #
218
+ def add_middleware(middleware)
219
+ @context_middlewares.use(middleware)
220
+ end
221
+
222
+ # Report an error directly to subscribers. You can use this method when the
223
+ # block-based #handle and #record methods are not suitable.
224
+ #
225
+ # Rails.error.report(error)
226
+ #
227
+ # The +error+ argument must be an instance of Exception.
228
+ #
229
+ # Rails.error.report(Exception.new("Something went wrong"))
230
+ #
231
+ # Otherwise you can use #unexpected to report an error which does accept a
232
+ # string argument.
233
+ def report(error, handled: true, severity: handled ? :warning : :error, context: {}, source: DEFAULT_SOURCE)
234
+ return if error.instance_variable_defined?(:@__rails_error_reported)
235
+ raise ArgumentError, "Reported error must be an Exception, got: #{error.inspect}" unless error.is_a?(Exception)
236
+
237
+ ensure_backtrace(error)
238
+
239
+ unless SEVERITIES.include?(severity)
240
+ raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}"
241
+ end
242
+
243
+ full_context = @context_middlewares.execute(
244
+ error,
245
+ context: ActiveSupport::ExecutionContext.to_h.merge(context || {}),
246
+ handled:,
247
+ severity:,
248
+ source:
249
+ )
250
+
251
+ disabled_subscribers = ActiveSupport::IsolatedExecutionState[self]
252
+ @subscribers.each do |subscriber|
253
+ unless disabled_subscribers&.any? { |s| s === subscriber }
254
+ subscriber.report(error, handled: handled, severity: severity, context: full_context, source: source)
255
+ end
256
+ rescue => subscriber_error
257
+ if logger
258
+ logger.fatal(
259
+ "Error subscriber raised an error: #{subscriber_error.message} (#{subscriber_error.class})\n" +
260
+ subscriber_error.backtrace.join("\n")
261
+ )
262
+ else
263
+ raise
264
+ end
265
+ end
266
+
267
+ while error
268
+ unless error.frozen?
269
+ error.instance_variable_set(:@__rails_error_reported, true)
270
+ end
271
+ error = error.cause
272
+ end
273
+
274
+ nil
275
+ end
276
+
277
+ private
278
+ def ensure_backtrace(error)
279
+ return if error.frozen? # re-raising won't add a backtrace or set the cause
280
+ return unless error.backtrace.nil?
281
+
282
+ begin
283
+ # As of Ruby 3.4, we could use Exception#set_backtrace to set the backtrace,
284
+ # but there's nothing like Exception#set_cause. Raising+rescuing is the only way to set the cause.
285
+ raise error
286
+ rescue error.class => error
287
+ end
288
+
289
+ count = 0
290
+ while error.backtrace_locations.first&.path == __FILE__
291
+ count += 1
292
+ error.backtrace_locations.shift
293
+ end
294
+
295
+ error.backtrace.shift(count)
296
+ end
297
+
298
+ class ErrorContextMiddlewareStack # :nodoc:
299
+ def initialize
300
+ @stack = []
301
+ end
302
+
303
+ # Add a middleware to the error context stack.
304
+ def use(middleware)
305
+ unless middleware.respond_to?(:call)
306
+ raise ArgumentError, "Error context middleware must respond to #call"
307
+ end
308
+
309
+ @stack << middleware
310
+ end
311
+
312
+ # Run all middlewares in the stack
313
+ def execute(error, handled:, severity:, context:, source:)
314
+ @stack.inject(context) { |c, middleware| middleware.call(error, context: c, handled:, severity:, source:) }
315
+ end
316
+ end
317
+ end
318
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport::EventReporter::TestHelper # :nodoc:
4
+ class EventSubscriber # :nodoc:
5
+ attr_reader :events
6
+
7
+ def initialize
8
+ @events = []
9
+ end
10
+
11
+ def emit(event)
12
+ @events << event
13
+ end
14
+ end
15
+
16
+ def event_matcher(name:, payload: nil, tags: {}, context: {}, source_location: nil)
17
+ ->(event) {
18
+ return false unless event[:name] == name
19
+ return false unless event[:payload] == payload
20
+ return false unless event[:tags] == tags
21
+ return false unless event[:context] == context
22
+
23
+ [:filepath, :lineno, :label].each do |key|
24
+ if source_location && source_location[key]
25
+ return false unless event[:source_location][key] == source_location[key]
26
+ end
27
+ end
28
+
29
+ true
30
+ }
31
+ end
32
+ end