alchemrest 3.1.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 (245) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +22 -0
  4. data/.rubocop_todo.yml +242 -0
  5. data/.ruby-version +1 -0
  6. data/Appraisals +19 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +378 -0
  9. data/Rakefile +29 -0
  10. data/alchemrest.gemspec +71 -0
  11. data/coach.yml +5 -0
  12. data/examples/bank_api/client.rb +31 -0
  13. data/examples/bank_api/data/account.rb +21 -0
  14. data/examples/bank_api/data/ach.rb +16 -0
  15. data/examples/bank_api/data/business_account.rb +22 -0
  16. data/examples/bank_api/data/card.rb +21 -0
  17. data/examples/bank_api/data/check.rb +19 -0
  18. data/examples/bank_api/data/product.rb +20 -0
  19. data/examples/bank_api/data/transaction.rb +49 -0
  20. data/examples/bank_api/data/user.rb +27 -0
  21. data/examples/bank_api/factories.rb +68 -0
  22. data/examples/bank_api/graph_visualization.rb +45 -0
  23. data/examples/bank_api/positive_interest_string.rb +33 -0
  24. data/examples/bank_api/requests/delete_user.rb +17 -0
  25. data/examples/bank_api/requests/get_business_account.rb +24 -0
  26. data/examples/bank_api/requests/get_products.rb +12 -0
  27. data/examples/bank_api/requests/get_transactions.rb +34 -0
  28. data/examples/bank_api/requests/get_user.rb +19 -0
  29. data/examples/bank_api/requests/post_transaction.rb +20 -0
  30. data/examples/bank_api/requests/update_user.rb +28 -0
  31. data/examples/bank_api/root.rb +52 -0
  32. data/examples/bank_api.rb +33 -0
  33. data/gemfiles/faraday_2.gemfile +9 -0
  34. data/gemfiles/faraday_2.gemfile.lock +363 -0
  35. data/gemfiles/rails_7_0.gemfile.lock +341 -0
  36. data/gemfiles/rails_7_2.gemfile +9 -0
  37. data/gemfiles/rails_7_2.gemfile.lock +384 -0
  38. data/gemfiles/rails_8_0.gemfile +9 -0
  39. data/gemfiles/rails_8_0.gemfile.lock +385 -0
  40. data/lib/alchemrest/circuit_breaker.rb +84 -0
  41. data/lib/alchemrest/client/configuration/connection.rb +83 -0
  42. data/lib/alchemrest/client/configuration.rb +89 -0
  43. data/lib/alchemrest/client.rb +48 -0
  44. data/lib/alchemrest/cop.rb +8 -0
  45. data/lib/alchemrest/data/capture_configuration.rb +77 -0
  46. data/lib/alchemrest/data/field.rb +36 -0
  47. data/lib/alchemrest/data/graph.rb +40 -0
  48. data/lib/alchemrest/data/record.rb +60 -0
  49. data/lib/alchemrest/data/schema.rb +80 -0
  50. data/lib/alchemrest/data.rb +9 -0
  51. data/lib/alchemrest/endpoint_definition.rb +47 -0
  52. data/lib/alchemrest/error.rb +122 -0
  53. data/lib/alchemrest/factory_bot.rb +64 -0
  54. data/lib/alchemrest/faraday_middleware/external_api_instrumentation.rb +24 -0
  55. data/lib/alchemrest/faraday_middleware/json_parser.rb +30 -0
  56. data/lib/alchemrest/faraday_middleware/kill_switch.rb +22 -0
  57. data/lib/alchemrest/faraday_middleware/underscore_response.rb +24 -0
  58. data/lib/alchemrest/hash_path.rb +81 -0
  59. data/lib/alchemrest/http_request.rb +75 -0
  60. data/lib/alchemrest/kill_switch/adapters.rb +88 -0
  61. data/lib/alchemrest/kill_switch.rb +31 -0
  62. data/lib/alchemrest/railtie.rb +25 -0
  63. data/lib/alchemrest/request/endpoint.rb +29 -0
  64. data/lib/alchemrest/request/returns.rb +46 -0
  65. data/lib/alchemrest/request.rb +80 -0
  66. data/lib/alchemrest/request_definition/builder.rb +13 -0
  67. data/lib/alchemrest/request_definition.rb +26 -0
  68. data/lib/alchemrest/response/pipeline/extract_payload.rb +64 -0
  69. data/lib/alchemrest/response/pipeline/final.rb +11 -0
  70. data/lib/alchemrest/response/pipeline/omit.rb +46 -0
  71. data/lib/alchemrest/response/pipeline/sanitize.rb +59 -0
  72. data/lib/alchemrest/response/pipeline/transform.rb +26 -0
  73. data/lib/alchemrest/response/pipeline/was_successful.rb +29 -0
  74. data/lib/alchemrest/response/pipeline.rb +71 -0
  75. data/lib/alchemrest/response.rb +73 -0
  76. data/lib/alchemrest/response_captured_handler.rb +68 -0
  77. data/lib/alchemrest/result/halt.rb +15 -0
  78. data/lib/alchemrest/result/try_helpers.rb +16 -0
  79. data/lib/alchemrest/result.rb +128 -0
  80. data/lib/alchemrest/root.rb +77 -0
  81. data/lib/alchemrest/transforms/base_to_type_transform_registry.rb +42 -0
  82. data/lib/alchemrest/transforms/constrainable.rb +41 -0
  83. data/lib/alchemrest/transforms/constraint/block.rb +22 -0
  84. data/lib/alchemrest/transforms/constraint/greater_than.rb +19 -0
  85. data/lib/alchemrest/transforms/constraint/greater_than_or_eq.rb +19 -0
  86. data/lib/alchemrest/transforms/constraint/in_list.rb +19 -0
  87. data/lib/alchemrest/transforms/constraint/is_instance_of.rb +19 -0
  88. data/lib/alchemrest/transforms/constraint/is_uuid.rb +19 -0
  89. data/lib/alchemrest/transforms/constraint/less_than.rb +19 -0
  90. data/lib/alchemrest/transforms/constraint/less_than_or_eq.rb +19 -0
  91. data/lib/alchemrest/transforms/constraint/matches_regex.rb +19 -0
  92. data/lib/alchemrest/transforms/constraint/max_length.rb +19 -0
  93. data/lib/alchemrest/transforms/constraint/min_length.rb +19 -0
  94. data/lib/alchemrest/transforms/constraint.rb +17 -0
  95. data/lib/alchemrest/transforms/constraint_builder/for_number.rb +25 -0
  96. data/lib/alchemrest/transforms/constraint_builder/for_string.rb +21 -0
  97. data/lib/alchemrest/transforms/constraint_builder.rb +15 -0
  98. data/lib/alchemrest/transforms/date_transform.rb +30 -0
  99. data/lib/alchemrest/transforms/enum.rb +52 -0
  100. data/lib/alchemrest/transforms/epoch_time.rb +32 -0
  101. data/lib/alchemrest/transforms/from_chain.rb +15 -0
  102. data/lib/alchemrest/transforms/from_number/to_type_transform_registry.rb +18 -0
  103. data/lib/alchemrest/transforms/from_number.rb +47 -0
  104. data/lib/alchemrest/transforms/from_string/to_type_transform_registry.rb +17 -0
  105. data/lib/alchemrest/transforms/from_string.rb +36 -0
  106. data/lib/alchemrest/transforms/from_type/empty_to_type_transform_registry.rb +14 -0
  107. data/lib/alchemrest/transforms/from_type.rb +64 -0
  108. data/lib/alchemrest/transforms/iso_time.rb +58 -0
  109. data/lib/alchemrest/transforms/json_number.rb +26 -0
  110. data/lib/alchemrest/transforms/loose_hash.rb +96 -0
  111. data/lib/alchemrest/transforms/money_transform.rb +42 -0
  112. data/lib/alchemrest/transforms/number.rb +27 -0
  113. data/lib/alchemrest/transforms/output_type.rb +65 -0
  114. data/lib/alchemrest/transforms/to_decimal.rb +22 -0
  115. data/lib/alchemrest/transforms/to_type/from_string_to_time_selector.rb +29 -0
  116. data/lib/alchemrest/transforms/to_type/transforms_selector.rb +61 -0
  117. data/lib/alchemrest/transforms/to_type.rb +86 -0
  118. data/lib/alchemrest/transforms/typed.rb +32 -0
  119. data/lib/alchemrest/transforms/union.rb +44 -0
  120. data/lib/alchemrest/transforms/with_constraint.rb +26 -0
  121. data/lib/alchemrest/transforms.rb +93 -0
  122. data/lib/alchemrest/url_builder/encoders.rb +39 -0
  123. data/lib/alchemrest/url_builder/options.rb +33 -0
  124. data/lib/alchemrest/url_builder.rb +31 -0
  125. data/lib/alchemrest/version.rb +5 -0
  126. data/lib/alchemrest/webmock_helpers.rb +27 -0
  127. data/lib/alchemrest.rb +159 -0
  128. data/lib/generators/alchemrest/kill_switch_migration_generator.rb +27 -0
  129. data/lib/generators/alchemrest/templates/kill_switch_migration.rb.erb +17 -0
  130. data/lib/rubocop/cop/alchemrest/define_request_using_with_params.rb +53 -0
  131. data/lib/rubocop/cop/alchemrest/endpoint_definition_using_generic_params.rb +55 -0
  132. data/lib/rubocop/cop/alchemrest/request_hash_returning_block.rb +54 -0
  133. data/lib/rubocop/cop/alchemrest/time_transform_with_no_zone.rb +56 -0
  134. data/lib/tapioca/dsl/compilers/alchemrest_data.rb +84 -0
  135. data/lib/tapioca/dsl/compilers/alchemrest_root.rb +68 -0
  136. data/mutant.yml +16 -0
  137. data/rbi/alchemrest/result.rbi +80 -0
  138. data/rbi/alchemrest.rbi +246 -0
  139. data/sorbet/config +5 -0
  140. data/sorbet/rbi/gems/.gitattributes +1 -0
  141. data/sorbet/rbi/gems/abstract_type@0.0.7.rbi +41 -0
  142. data/sorbet/rbi/gems/actionpack@8.0.4.rbi +11733 -0
  143. data/sorbet/rbi/gems/actionview@8.0.4.rbi +6560 -0
  144. data/sorbet/rbi/gems/activemodel@8.0.4.rbi +2891 -0
  145. data/sorbet/rbi/gems/activesupport@8.0.4.rbi +9621 -0
  146. data/sorbet/rbi/gems/adamantium@0.2.0.rbi +144 -0
  147. data/sorbet/rbi/gems/addressable@2.8.7.rbi +779 -0
  148. data/sorbet/rbi/gems/anima@0.3.2.rbi +103 -0
  149. data/sorbet/rbi/gems/ast@2.4.2.rbi +107 -0
  150. data/sorbet/rbi/gems/base64@0.3.0.rbi +52 -0
  151. data/sorbet/rbi/gems/benchmark@0.5.0.rbi +153 -0
  152. data/sorbet/rbi/gems/bigdecimal@3.3.1.rbi +77 -0
  153. data/sorbet/rbi/gems/builder@3.3.0.rbi +9 -0
  154. data/sorbet/rbi/gems/circuitbox@2.0.0.rbi +297 -0
  155. data/sorbet/rbi/gems/concord@0.1.6.rbi +51 -0
  156. data/sorbet/rbi/gems/concurrent-ruby@1.3.5.rbi +4716 -0
  157. data/sorbet/rbi/gems/connection_pool@2.5.4.rbi +9 -0
  158. data/sorbet/rbi/gems/crack@1.0.0.rbi +110 -0
  159. data/sorbet/rbi/gems/crass@1.0.6.rbi +294 -0
  160. data/sorbet/rbi/gems/date@3.4.1.rbi +58 -0
  161. data/sorbet/rbi/gems/drb@2.2.3.rbi +639 -0
  162. data/sorbet/rbi/gems/equalizer@0.0.11.rbi +38 -0
  163. data/sorbet/rbi/gems/erubi@1.13.1.rbi +85 -0
  164. data/sorbet/rbi/gems/factory_bot@6.5.0.rbi +1529 -0
  165. data/sorbet/rbi/gems/faraday-em_http@1.0.0.rbi +181 -0
  166. data/sorbet/rbi/gems/faraday-em_synchrony@1.0.1.rbi +120 -0
  167. data/sorbet/rbi/gems/faraday-excon@1.1.0.rbi +128 -0
  168. data/sorbet/rbi/gems/faraday-httpclient@1.0.1.rbi +123 -0
  169. data/sorbet/rbi/gems/faraday-multipart@1.2.0.rbi +190 -0
  170. data/sorbet/rbi/gems/faraday-net_http@1.0.2.rbi +140 -0
  171. data/sorbet/rbi/gems/faraday-net_http_persistent@1.2.0.rbi +116 -0
  172. data/sorbet/rbi/gems/faraday-patron@1.0.0.rbi +119 -0
  173. data/sorbet/rbi/gems/faraday-rack@1.0.0.rbi +113 -0
  174. data/sorbet/rbi/gems/faraday-retry@1.0.3.rbi +149 -0
  175. data/sorbet/rbi/gems/faraday@1.10.5.rbi +1620 -0
  176. data/sorbet/rbi/gems/hansi@0.2.1.rbi +9 -0
  177. data/sorbet/rbi/gems/hashdiff@1.1.2.rbi +174 -0
  178. data/sorbet/rbi/gems/i18n@1.14.7.rbi +1328 -0
  179. data/sorbet/rbi/gems/ice_nine@0.11.2.rbi +145 -0
  180. data/sorbet/rbi/gems/io-console@0.8.0.rbi +9 -0
  181. data/sorbet/rbi/gems/json@2.9.1.rbi +282 -0
  182. data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +8057 -0
  183. data/sorbet/rbi/gems/logger@1.7.0.rbi +260 -0
  184. data/sorbet/rbi/gems/loofah@2.24.0.rbi +571 -0
  185. data/sorbet/rbi/gems/memoizable@0.4.2.rbi +131 -0
  186. data/sorbet/rbi/gems/memosa@0.8.2.rbi +185 -0
  187. data/sorbet/rbi/gems/minitest@5.26.0.rbi +824 -0
  188. data/sorbet/rbi/gems/money@6.19.0.rbi +815 -0
  189. data/sorbet/rbi/gems/morpher@0.4.2.rbi +388 -0
  190. data/sorbet/rbi/gems/mprelude@0.1.0.rbi +140 -0
  191. data/sorbet/rbi/gems/multi_json@1.15.0.rbi +180 -0
  192. data/sorbet/rbi/gems/multipart-post@2.4.1.rbi +154 -0
  193. data/sorbet/rbi/gems/mustermann-contrib@3.0.3.rbi +9 -0
  194. data/sorbet/rbi/gems/mustermann@3.0.3.rbi +809 -0
  195. data/sorbet/rbi/gems/netrc@0.11.0.rbi +112 -0
  196. data/sorbet/rbi/gems/nokogiri@1.19.1.rbi +3412 -0
  197. data/sorbet/rbi/gems/parallel@1.26.3.rbi +234 -0
  198. data/sorbet/rbi/gems/parser@3.3.7.0.rbi +4877 -0
  199. data/sorbet/rbi/gems/pp@0.6.2.rbi +176 -0
  200. data/sorbet/rbi/gems/prettyprint@0.2.0.rbi +155 -0
  201. data/sorbet/rbi/gems/prism@1.5.1.rbi +26368 -0
  202. data/sorbet/rbi/gems/procto@0.0.3.rbi +9 -0
  203. data/sorbet/rbi/gems/psych@5.2.3.rbi +806 -0
  204. data/sorbet/rbi/gems/public_suffix@6.0.1.rbi +267 -0
  205. data/sorbet/rbi/gems/racc@1.8.1.rbi +120 -0
  206. data/sorbet/rbi/gems/rack-session@2.1.1.rbi +458 -0
  207. data/sorbet/rbi/gems/rack-test@2.2.0.rbi +405 -0
  208. data/sorbet/rbi/gems/rack@3.1.14.rbi +2774 -0
  209. data/sorbet/rbi/gems/rackup@2.2.1.rbi +132 -0
  210. data/sorbet/rbi/gems/rails-dom-testing@2.2.0.rbi +266 -0
  211. data/sorbet/rbi/gems/rails-html-sanitizer@1.6.2.rbi +545 -0
  212. data/sorbet/rbi/gems/railties@8.0.4.rbi +2150 -0
  213. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +333 -0
  214. data/sorbet/rbi/gems/rake@13.2.1.rbi +2054 -0
  215. data/sorbet/rbi/gems/rbi@0.2.3.rbi +3961 -0
  216. data/sorbet/rbi/gems/rdoc@6.13.1.rbi +6784 -0
  217. data/sorbet/rbi/gems/regexp_parser@2.11.3.rbi +3020 -0
  218. data/sorbet/rbi/gems/reline@0.6.0.rbi +9 -0
  219. data/sorbet/rbi/gems/rexml@3.4.2.rbi +1777 -0
  220. data/sorbet/rbi/gems/rubocop-ast@1.38.0.rbi +5293 -0
  221. data/sorbet/rbi/gems/rubocop@1.71.1.rbi +31846 -0
  222. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +980 -0
  223. data/sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi +9 -0
  224. data/sorbet/rbi/gems/securerandom@0.4.1.rbi +33 -0
  225. data/sorbet/rbi/gems/sentry-ruby@5.22.1.rbi +3782 -0
  226. data/sorbet/rbi/gems/spoom@1.5.1.rbi +4321 -0
  227. data/sorbet/rbi/gems/stringio@3.1.2.rbi +9 -0
  228. data/sorbet/rbi/gems/tapioca@0.16.8.rbi +3399 -0
  229. data/sorbet/rbi/gems/thor@1.3.2.rbi +2012 -0
  230. data/sorbet/rbi/gems/thread_safe@0.3.6.rbi +711 -0
  231. data/sorbet/rbi/gems/timeout@0.4.4.rbi +80 -0
  232. data/sorbet/rbi/gems/tsort@0.2.0.rbi +50 -0
  233. data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +1677 -0
  234. data/sorbet/rbi/gems/unicode-display_width@2.6.0.rbi +62 -0
  235. data/sorbet/rbi/gems/uri@1.1.0.rbi +760 -0
  236. data/sorbet/rbi/gems/useragent@0.16.11.rbi +9 -0
  237. data/sorbet/rbi/gems/webmock@3.24.0.rbi +1362 -0
  238. data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +345 -0
  239. data/sorbet/rbi/gems/yard@0.9.37.rbi +8795 -0
  240. data/sorbet/rbi/gems/zeitwerk@2.7.1.rbi +589 -0
  241. data/sorbet/tapioca/config.yml +45 -0
  242. data/sorbet/tapioca/require.rb +8 -0
  243. data/sorbet/tapioca/sorbet/rbi/dsl/.gitattributes +1 -0
  244. data/sorbet/tapioca/sorbet/rbi/dsl/active_support/callbacks.rbi +23 -0
  245. metadata +737 -0
@@ -0,0 +1,33 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Alchemrest
5
+ class UrlBuilder
6
+ class Options
7
+ attr_accessor :values, :query
8
+
9
+ attr_reader :query_param_encoder
10
+
11
+ InvalidEncoderError = Class.new(StandardError)
12
+
13
+ def encode_query_with=(name)
14
+ @query_param_encoder = Encoders.find(name)
15
+ rescue NoMatchingPatternError
16
+ raise InvalidEncoderError, ":#{name} is not a known query string encoder type. Known types are :rack and :form"
17
+ end
18
+
19
+ def encode_query_with(&)
20
+ @query_param_encoder = Encoders::Custom.new(&)
21
+ end
22
+
23
+ def create_builder(template:)
24
+ UrlBuilder.new(
25
+ template:,
26
+ query: query,
27
+ values: values,
28
+ query_param_encoder: query_param_encoder,
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Alchemrest
5
+ class UrlBuilder
6
+ include Anima.new(:template, :query_param_encoder, :values, :query)
7
+
8
+ def initialize(args)
9
+ super({ values: nil, query: nil, query_param_encoder: Encoders.find(:form) }.merge(**args.compact))
10
+ end
11
+
12
+ def url
13
+ base = values ? Mustermann::Expander.new(template).expand(values) : template
14
+
15
+ if query_string
16
+ "#{base}?#{query_string}"
17
+ else
18
+ base
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def query_string
25
+ return nil unless query
26
+ return nil if query.compact.empty?
27
+
28
+ query_param_encoder.call(query.compact)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemrest
4
+ VERSION = "3.1.0"
5
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemrest
4
+ module WebmockHelpers
5
+ module_function def stub_alchemrest_request(alchemrest_request, with_request_body: false, with_headers: false)
6
+ stub_url = if defined?(alchemrest_request.url)
7
+ alchemrest_request.url
8
+ else
9
+ Addressable::Template.new("http://{host}#{alchemrest_request.path}")
10
+ end
11
+
12
+ http_method = alchemrest_request.http_method.to_sym
13
+
14
+ stub = if with_request_body
15
+ stub_request(http_method, stub_url).with(body: alchemrest_request.body)
16
+ else
17
+ stub_request(http_method, stub_url)
18
+ end
19
+
20
+ if with_headers
21
+ stub.with(headers: alchemrest_request.headers)
22
+ else
23
+ stub
24
+ end
25
+ end
26
+ end
27
+ end
data/lib/alchemrest.rb ADDED
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "morpher"
4
+ require "money"
5
+ require 'faraday'
6
+ require 'multi_json'
7
+ require "active_support"
8
+ require "active_support/core_ext/hash"
9
+ require "active_support/core_ext/object"
10
+ require "active_support/parameter_filter"
11
+ require "active_support/hash_with_indifferent_access"
12
+ require "circuitbox"
13
+ require 'sorbet-runtime'
14
+ require "rack/utils"
15
+ require_relative "alchemrest/version"
16
+ require_relative "alchemrest/hash_path"
17
+ require_relative "alchemrest/transforms"
18
+ require_relative "alchemrest/transforms/constrainable"
19
+ require_relative "alchemrest/transforms/from_type"
20
+ require_relative "alchemrest/transforms/to_type"
21
+ require_relative "alchemrest/transforms/to_type/transforms_selector"
22
+ require_relative "alchemrest/transforms/constraint"
23
+ require_relative "alchemrest/transforms/constraint_builder"
24
+ require_relative "alchemrest/transforms/constraint_builder/for_string"
25
+ require_relative "alchemrest/transforms/constraint_builder/for_number"
26
+ require_relative "alchemrest/transforms/constraint/block"
27
+ require_relative "alchemrest/transforms/with_constraint"
28
+ require_relative "alchemrest/data/field"
29
+ require_relative "alchemrest/data/graph"
30
+ require_relative "alchemrest/data/capture_configuration"
31
+ require_relative "alchemrest/data/record"
32
+ require_relative "alchemrest/data/schema"
33
+ require_relative "alchemrest/data"
34
+ require_relative "alchemrest/transforms/base_to_type_transform_registry"
35
+ require_relative "alchemrest/transforms/from_type/empty_to_type_transform_registry"
36
+
37
+ Dir.glob(File.join(__dir__, 'alchemrest', 'transforms', '*.rb')).each do |file|
38
+ require_relative file
39
+ end
40
+
41
+ require_relative "alchemrest/transforms/constraint/matches_regex"
42
+ Dir.glob(File.join(__dir__, 'alchemrest', 'transforms', 'constraint', '*.rb')).each do |file|
43
+ require_relative file
44
+ end
45
+
46
+ require_relative "alchemrest/transforms/to_type/from_string_to_time_selector"
47
+ require_relative "alchemrest/transforms/from_string/to_type_transform_registry"
48
+ require_relative "alchemrest/transforms/from_number/to_type_transform_registry"
49
+ require_relative "alchemrest/result"
50
+ require_relative 'alchemrest/result/halt'
51
+ require_relative 'alchemrest/result/try_helpers'
52
+ require_relative "alchemrest/error"
53
+ require_relative "alchemrest/response"
54
+ require_relative "alchemrest/response/pipeline"
55
+ require_relative "alchemrest/response/pipeline/transform"
56
+ require_relative "alchemrest/response/pipeline/was_successful"
57
+ require_relative "alchemrest/response/pipeline/final"
58
+ require_relative "alchemrest/response/pipeline/extract_payload"
59
+ require_relative "alchemrest/response/pipeline/sanitize"
60
+ require_relative "alchemrest/response/pipeline/omit"
61
+ require_relative "alchemrest/response_captured_handler"
62
+ require_relative "alchemrest/client/configuration/connection"
63
+ require_relative "alchemrest/client/configuration"
64
+ require_relative "alchemrest/client"
65
+ require_relative "alchemrest/url_builder"
66
+ require_relative "alchemrest/url_builder/encoders"
67
+ require_relative "alchemrest/url_builder/options"
68
+ require_relative "alchemrest/endpoint_definition"
69
+ require_relative "alchemrest/request/endpoint"
70
+ require_relative "alchemrest/request/returns"
71
+ require_relative "alchemrest/request"
72
+ require_relative "alchemrest/request_definition"
73
+ require_relative "alchemrest/request_definition/builder"
74
+ require_relative "alchemrest/root"
75
+ require_relative "alchemrest/kill_switch"
76
+ require_relative "alchemrest/kill_switch/adapters"
77
+ require_relative "alchemrest/circuit_breaker"
78
+ require_relative "alchemrest/http_request"
79
+ require_relative "alchemrest/faraday_middleware/json_parser"
80
+ require_relative "alchemrest/faraday_middleware/underscore_response"
81
+ require_relative "alchemrest/faraday_middleware/external_api_instrumentation"
82
+ require_relative "alchemrest/faraday_middleware/kill_switch"
83
+ require_relative "alchemrest/webmock_helpers"
84
+ require_relative "alchemrest/factory_bot"
85
+ require_relative "alchemrest/railtie"
86
+
87
+ module Alchemrest
88
+ DEFAULT_RESCUABLE_EXCEPTIONS = [Alchemrest::Error].freeze
89
+
90
+ DEFAULT_FILTER_PARAMETERS = %i(ssn password token).freeze
91
+
92
+ @filter_parameters = DEFAULT_FILTER_PARAMETERS
93
+
94
+ def self.logger
95
+ @logger ||= Logger.new($stdout)
96
+ end
97
+
98
+ def self.logger=(logger)
99
+ @logger = logger
100
+ end
101
+
102
+ # mutant:disable
103
+ def self.deprecator
104
+ @deprecator ||= ActiveSupport::Deprecation.new("3.0", "Alchemrest")
105
+ end
106
+
107
+ def self.filter_parameters
108
+ @filter_parameters
109
+ end
110
+
111
+ def self.filter_parameters=(params)
112
+ @filter_parameters = params.uniq
113
+ end
114
+
115
+ def self.parameter_filter
116
+ ActiveSupport::ParameterFilter.new(@filter_parameters)
117
+ end
118
+
119
+ def self.on_result_rescued(&block)
120
+ @on_result_rescued = block if block
121
+ end
122
+
123
+ def self.on_response_captured(&block)
124
+ @on_response_captured = block if block
125
+ @on_response_captured
126
+ end
127
+
128
+ def self.restore_default_result_rescue_behavior
129
+ @on_result_rescued = nil
130
+ end
131
+
132
+ def self.restore_default_response_capture_behavior
133
+ @on_response_captured = nil
134
+ end
135
+
136
+ def self.handle_rescued_result(error)
137
+ if @on_result_rescued
138
+ @on_result_rescued.call(error)
139
+ else
140
+ logger.warn(error.to_s)
141
+ end
142
+ end
143
+
144
+ def self.rescuable_exceptions
145
+ @rescuable_exceptions || DEFAULT_RESCUABLE_EXCEPTIONS
146
+ end
147
+
148
+ def self.rescuable_exceptions=(exceptions)
149
+ @rescuable_exceptions = exceptions
150
+ end
151
+
152
+ def self.kill_switch_adapter
153
+ @kill_switch_adapter ||= KillSwitch::Adapters::ActiveRecord.new
154
+ end
155
+
156
+ def self.kill_switch_adapter=(adapter)
157
+ @kill_switch_adapter = adapter
158
+ end
159
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/migration'
4
+ require 'rails/generators/active_record'
5
+
6
+ module Alchemrest
7
+ class KillSwitchMigrationGenerator < Rails::Generators::Base
8
+ include Rails::Generators::Migration
9
+
10
+ source_paths << File.join(File.dirname(__FILE__), 'templates')
11
+
12
+ def self.next_migration_number(dirname)
13
+ ActiveRecord::Generators::Base.next_migration_number dirname
14
+ end
15
+
16
+ def create_migration_file
17
+ migration_template 'kill_switch_migration.rb.erb', "db/migrate/create_alchemrest_kill_switches.rb",
18
+ migration_version:
19
+ end
20
+
21
+ private
22
+
23
+ def migration_version
24
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]" if ActiveRecord::VERSION::MAJOR >= 5
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ class CreateAlchemrestKillSwitches < ActiveRecord::Migration<%= migration_version %>
2
+ disable_ddl_transaction!
3
+
4
+ def self.up
5
+ create_table :alchemrest_kill_switches, id: :uuid do |t|
6
+ t.string :service_name, null: false
7
+ t.boolean :enabled, null: false, default: false
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :alchemrest_kill_switches, [:service_name], name: "index_alchemrest_kill_switches_on_service_name", unique: true, algorithm: :concurrently
12
+ end
13
+
14
+ def self.down
15
+ drop_table :alchemrest_kill_switches
16
+ end
17
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Alchemrest
6
+ class DefineRequestUsingWithParams < RuboCop::Cop::Base
7
+ extend RuboCop::Cop::AutoCorrector
8
+
9
+ MSG = <<~MSG
10
+ Using `with_params` to set defaults for a request is deprecated.
11
+ Instead make your block that takes a `request` argument and call `request.defaults = { <some values> }`
12
+
13
+ BAD:
14
+ ```
15
+ define_request :get_users, GetUsers, with_params: -> { { id: user_id } }
16
+ ```
17
+
18
+ GOOD:
19
+ ```
20
+ define_request :get_users, GetUsers do |request|
21
+ request.defaults = { id: user_id }
22
+ end
23
+ ```
24
+ MSG
25
+
26
+ def_node_matcher :on_define_request_with_params_proc_call?, <<~PATTERN
27
+ (send nil? :define_request $(sym _) $(const ...) $(hash (pair (sym :with_params) $(block ...))))
28
+ PATTERN
29
+
30
+ def on_send(node)
31
+ on_define_request_with_params_proc_call?(node) do |method_name, klass, with_params_arg, block_node|
32
+ add_offense(with_params_arg) do |corrector|
33
+ correction = build_autocorrection(method_name, klass, block_node)
34
+ corrector.replace(node, correction) if correction
35
+ end
36
+ end
37
+ end
38
+
39
+ def build_autocorrection(method_name, klass, block)
40
+ block_body = block.body
41
+
42
+ params_value = block_body.source
43
+
44
+ <<~CODE.chomp
45
+ define_request #{method_name.source}, #{klass.source} do |request|
46
+ request.defaults = #{params_value}
47
+ end
48
+ CODE
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Alchemrest
6
+ class EndpointDefinitionUsingGenericParams < RuboCop::Cop::Base
7
+ extend RuboCop::Cop::AutoCorrector
8
+
9
+ MSG = <<~MSG
10
+ You are using a generic params hash to build a url, rather than explicitly defining which values in the
11
+ hash should be inserted into the path of the url, vs which are query string params. This is deprecated,
12
+ and you should update your code to use `values=` and `query=` instead.
13
+
14
+ BAD:
15
+ endpoint :get, '/api/v1/users/:id' do |url|
16
+ url.params = { id: @id, includeDetails: true }
17
+ end
18
+
19
+ GOOD:
20
+ endpoint :get, '/api/v1/users/:id' do |url|
21
+ url.values = { id: @id }
22
+ url.query = { includeDetails: true }
23
+ end
24
+ MSG
25
+
26
+ def_node_matcher :on_endpoint_call?, <<~PATTERN
27
+ (block (send _ :endpoint ...) (args (arg _)) $_)
28
+ PATTERN
29
+
30
+ def_node_matcher :generic_params_assignment?, <<~PATTERN
31
+ (send (lvar $_) :params= _)
32
+ PATTERN
33
+
34
+ def on_block(node)
35
+ on_endpoint_call?(node) do |block_node|
36
+ if generic_params_assignment?(block_node)
37
+ add_offense(block_node) do |corrector|
38
+ correction = build_autocorrection(block_node)
39
+ corrector.replace(block_node, correction) if correction
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ def build_autocorrection(block)
46
+ params_value = block.arguments.first.source
47
+
48
+ <<~CODE.chomp
49
+ url.values = #{params_value}
50
+ CODE
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Alchemrest
6
+ class RequestHashReturningBlock < RuboCop::Cop::Base
7
+ extend RuboCop::Cop::AutoCorrector
8
+
9
+ MSG = <<~MSG
10
+ Returning a hash to set url params is deprecated. Instead make your block take a `url` argument and call `url.params = {...}`
11
+
12
+ BAD:
13
+ ```
14
+ endpoint :get, '/api/users/:id', -> { { id: user_id } }
15
+ ```
16
+
17
+ GOOD:
18
+ ```
19
+ endpoint :get, '/api/users/:id' do |url|
20
+ url.params = { id: user_id }
21
+ end
22
+ ```
23
+ MSG
24
+
25
+ def_node_matcher :on_endpoint_params_proc_call?, <<~PATTERN
26
+ (send nil? :endpoint $(sym _) $({str | dstr} ...) $(block ...))
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ on_endpoint_params_proc_call?(node) do |http_method, url, block_node|
31
+ add_offense(block_node) do |corrector|
32
+ correction = build_autocorrection(http_method, url, block_node)
33
+ corrector.replace(node, correction) if correction
34
+ end
35
+ end
36
+ end
37
+
38
+ def build_autocorrection(http_method, url, block)
39
+ block_body = block.body
40
+
41
+ params_value = block_body.source
42
+
43
+ url_string = url.source
44
+
45
+ <<~CODE.chomp
46
+ endpoint :#{http_method.value}, #{url_string} do |url|
47
+ url.params = #{params_value}
48
+ end
49
+ CODE
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Alchemrest
6
+ class TimeTransformWithNoZone < RuboCop::Cop::Base
7
+ extend RuboCop::Cop::AutoCorrector
8
+
9
+ MSG = <<~MSG
10
+ Calling `s.from.string.to(Time)` without specifying the timezone is no longer supported
11
+
12
+ BAD:
13
+ ```
14
+ s.from.string.to(Time)
15
+ ```
16
+
17
+ GOOD:
18
+ ```
19
+ s.from.string.to(Time).using(:utc)
20
+ s.from.string.to(Time).using(:local)
21
+ s.from.string.to(Time).using(ActiveSupport::TimeZone['Mountain Time (US & Canada)'])
22
+ ```
23
+ MSG
24
+
25
+ def_node_matcher :on_time_transform?, <<~PATTERN
26
+ (send (send (send ... :from) :string) :to (const nil? :Time))
27
+ PATTERN
28
+
29
+ def_node_matcher :on_good_time_transform?, <<~PATTERN
30
+ (send (send (send (send ... :from) :string) :to (const nil? :Time)) :using ...)
31
+ PATTERN
32
+
33
+ def on_hash(node)
34
+ node.pairs.each do |pair|
35
+ transform_node = pair.value
36
+
37
+ next if on_good_time_transform?(transform_node)
38
+
39
+ on_time_transform?(transform_node) do
40
+ add_offense(transform_node) do |corrector|
41
+ correction = build_autocorrection(transform_node)
42
+ corrector.replace(transform_node, correction) if correction
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def build_autocorrection(node)
49
+ <<~CODE.chomp
50
+ #{node.source}.using(:utc)
51
+ CODE
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,84 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'active_support/core_ext/enumerable'
5
+ require 'alchemrest'
6
+
7
+ module Tapioca
8
+ module Dsl
9
+ module Compilers
10
+ # This compiler generates RBI files for classes that inherit from `Alchemrest::Data`.
11
+ #
12
+ # For example, given the following class:
13
+ #
14
+ # class Profile < Alchemrest::Data
15
+ # schema do |s|
16
+ # {
17
+ # required: {
18
+ # name: s.string,
19
+ # },
20
+ # }
21
+ # end
22
+ # end
23
+ #
24
+ # This compiler will generate an RBI file `profile.rbi` with the following content:
25
+ #
26
+ # sig { returns(::String) }
27
+ # def name; end
28
+ #
29
+ class AlchemrestData < Tapioca::Dsl::Compiler
30
+ extend T::Sig
31
+
32
+ Type = T.type_alias { T.any(Class, T::Types::Base) }
33
+
34
+ ConstantType = type_member { { upper: T.class_of(Alchemrest::Data) } }
35
+
36
+ sig { override.returns(T::Enumerable[Module]) }
37
+ def self.gather_constants
38
+ descendants_of(Alchemrest::Data)
39
+ end
40
+
41
+ sig { override.void }
42
+ def decorate
43
+ root.create_path(constant) do |rbi|
44
+ constant.graph.fields.each do |name, field|
45
+ type = T::Utils.coerce(extract_type(field.output_type))
46
+ rbi.create_method(name.to_s, return_type: type.to_s)
47
+ end
48
+ end
49
+ end
50
+
51
+ sig { params(output_type: T.nilable(Alchemrest::Transforms::OutputType)).returns(Type) }
52
+ def extract_type(output_type)
53
+ return T.untyped if output_type.nil?
54
+
55
+ output_type.sorbet_type
56
+ end
57
+
58
+ sig { params(types: T::Array[Class]).returns(Type) }
59
+ def extract_output_type(types)
60
+ types.many? ? T.any(*T.unsafe(types)) : types.sole
61
+ end
62
+
63
+ sig { params(field: Alchemrest::Data::Field).returns(T::Boolean) }
64
+ def nilable?(field)
65
+ !field.required || field.transform.is_a?(Morpher::Transform::Maybe)
66
+ end
67
+
68
+ sig { params(transform: Morpher::Transform).returns(T::Boolean) }
69
+ def array?(transform)
70
+ case transform
71
+ when Alchemrest::Transforms::Typed
72
+ transform.collection
73
+ when Morpher::Transform::Array
74
+ true
75
+ when Morpher::Transform::Maybe
76
+ array?(transform.send(:transform))
77
+ else
78
+ false
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,68 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'active_support/core_ext/enumerable'
5
+ require 'alchemrest'
6
+
7
+ module Tapioca
8
+ module Dsl
9
+ module Compilers
10
+ # This compiler generates RBI files for classes that inherit from `Alchemrest::Root`.
11
+ #
12
+ # For example, given the following class:
13
+ #
14
+ # class Root < Alchemrest::Root
15
+ # define_request :get_profile, Requests::GetProfile
16
+ # end
17
+ #
18
+ # This compiler will generate an RBI file `root.rbi` with the following content:
19
+ #
20
+ # sig { params(params: T::Hash[Symbol, T.untyped]).returns(::Alchemrest::Result[::Profile]) }
21
+ # def get_profile(params = {}); end
22
+ #
23
+ class AlchemrestRoot < Tapioca::Dsl::Compiler
24
+ extend T::Sig
25
+
26
+ ConstantType = type_member { { upper: T.class_of(Alchemrest::Root) } }
27
+
28
+ sig { override.returns(T::Enumerable[Module]) }
29
+ def self.gather_constants
30
+ descendants_of(Alchemrest::Root)
31
+ end
32
+
33
+ sig { override.void }
34
+ def decorate
35
+ root.create_path(constant) do |rbi|
36
+ constant.request_definitions.each do |name, definition|
37
+ rbi.create_method(
38
+ name.to_s,
39
+ parameters: [create_opt_param('params', type: 'T::Hash[Symbol, T.untyped]', default: '{}')],
40
+ return_type: return_type_for(definition),
41
+ )
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ sig { params(definition: Alchemrest::RequestDefinition).returns(String) }
49
+ def return_type_for(definition)
50
+ transform = extract_transform(definition)
51
+
52
+ if transform.nil? || !transform.respond_to?(:output_type)
53
+ '::Alchemrest::Result[T.untyped]'
54
+ else
55
+ "::Alchemrest::Result[#{transform.output_type.sorbet_type}]"
56
+ end
57
+ end
58
+
59
+ sig { params(definition: Alchemrest::RequestDefinition).returns(T.nilable(Alchemrest::Transforms::Typed)) }
60
+ def extract_transform(definition)
61
+ request_class = definition.instance_variable_get(:@request_class)
62
+ returns_modules = request_class.ancestors.grep(Alchemrest::Request::Returns)
63
+ returns_modules.sole.domain_type.const_get(:TRANSFORM) if returns_modules.one?
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end