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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +22 -0
- data/.rubocop_todo.yml +242 -0
- data/.ruby-version +1 -0
- data/Appraisals +19 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +378 -0
- data/Rakefile +29 -0
- data/alchemrest.gemspec +71 -0
- data/coach.yml +5 -0
- data/examples/bank_api/client.rb +31 -0
- data/examples/bank_api/data/account.rb +21 -0
- data/examples/bank_api/data/ach.rb +16 -0
- data/examples/bank_api/data/business_account.rb +22 -0
- data/examples/bank_api/data/card.rb +21 -0
- data/examples/bank_api/data/check.rb +19 -0
- data/examples/bank_api/data/product.rb +20 -0
- data/examples/bank_api/data/transaction.rb +49 -0
- data/examples/bank_api/data/user.rb +27 -0
- data/examples/bank_api/factories.rb +68 -0
- data/examples/bank_api/graph_visualization.rb +45 -0
- data/examples/bank_api/positive_interest_string.rb +33 -0
- data/examples/bank_api/requests/delete_user.rb +17 -0
- data/examples/bank_api/requests/get_business_account.rb +24 -0
- data/examples/bank_api/requests/get_products.rb +12 -0
- data/examples/bank_api/requests/get_transactions.rb +34 -0
- data/examples/bank_api/requests/get_user.rb +19 -0
- data/examples/bank_api/requests/post_transaction.rb +20 -0
- data/examples/bank_api/requests/update_user.rb +28 -0
- data/examples/bank_api/root.rb +52 -0
- data/examples/bank_api.rb +33 -0
- data/gemfiles/faraday_2.gemfile +9 -0
- data/gemfiles/faraday_2.gemfile.lock +363 -0
- data/gemfiles/rails_7_0.gemfile.lock +341 -0
- data/gemfiles/rails_7_2.gemfile +9 -0
- data/gemfiles/rails_7_2.gemfile.lock +384 -0
- data/gemfiles/rails_8_0.gemfile +9 -0
- data/gemfiles/rails_8_0.gemfile.lock +385 -0
- data/lib/alchemrest/circuit_breaker.rb +84 -0
- data/lib/alchemrest/client/configuration/connection.rb +83 -0
- data/lib/alchemrest/client/configuration.rb +89 -0
- data/lib/alchemrest/client.rb +48 -0
- data/lib/alchemrest/cop.rb +8 -0
- data/lib/alchemrest/data/capture_configuration.rb +77 -0
- data/lib/alchemrest/data/field.rb +36 -0
- data/lib/alchemrest/data/graph.rb +40 -0
- data/lib/alchemrest/data/record.rb +60 -0
- data/lib/alchemrest/data/schema.rb +80 -0
- data/lib/alchemrest/data.rb +9 -0
- data/lib/alchemrest/endpoint_definition.rb +47 -0
- data/lib/alchemrest/error.rb +122 -0
- data/lib/alchemrest/factory_bot.rb +64 -0
- data/lib/alchemrest/faraday_middleware/external_api_instrumentation.rb +24 -0
- data/lib/alchemrest/faraday_middleware/json_parser.rb +30 -0
- data/lib/alchemrest/faraday_middleware/kill_switch.rb +22 -0
- data/lib/alchemrest/faraday_middleware/underscore_response.rb +24 -0
- data/lib/alchemrest/hash_path.rb +81 -0
- data/lib/alchemrest/http_request.rb +75 -0
- data/lib/alchemrest/kill_switch/adapters.rb +88 -0
- data/lib/alchemrest/kill_switch.rb +31 -0
- data/lib/alchemrest/railtie.rb +25 -0
- data/lib/alchemrest/request/endpoint.rb +29 -0
- data/lib/alchemrest/request/returns.rb +46 -0
- data/lib/alchemrest/request.rb +80 -0
- data/lib/alchemrest/request_definition/builder.rb +13 -0
- data/lib/alchemrest/request_definition.rb +26 -0
- data/lib/alchemrest/response/pipeline/extract_payload.rb +64 -0
- data/lib/alchemrest/response/pipeline/final.rb +11 -0
- data/lib/alchemrest/response/pipeline/omit.rb +46 -0
- data/lib/alchemrest/response/pipeline/sanitize.rb +59 -0
- data/lib/alchemrest/response/pipeline/transform.rb +26 -0
- data/lib/alchemrest/response/pipeline/was_successful.rb +29 -0
- data/lib/alchemrest/response/pipeline.rb +71 -0
- data/lib/alchemrest/response.rb +73 -0
- data/lib/alchemrest/response_captured_handler.rb +68 -0
- data/lib/alchemrest/result/halt.rb +15 -0
- data/lib/alchemrest/result/try_helpers.rb +16 -0
- data/lib/alchemrest/result.rb +128 -0
- data/lib/alchemrest/root.rb +77 -0
- data/lib/alchemrest/transforms/base_to_type_transform_registry.rb +42 -0
- data/lib/alchemrest/transforms/constrainable.rb +41 -0
- data/lib/alchemrest/transforms/constraint/block.rb +22 -0
- data/lib/alchemrest/transforms/constraint/greater_than.rb +19 -0
- data/lib/alchemrest/transforms/constraint/greater_than_or_eq.rb +19 -0
- data/lib/alchemrest/transforms/constraint/in_list.rb +19 -0
- data/lib/alchemrest/transforms/constraint/is_instance_of.rb +19 -0
- data/lib/alchemrest/transforms/constraint/is_uuid.rb +19 -0
- data/lib/alchemrest/transforms/constraint/less_than.rb +19 -0
- data/lib/alchemrest/transforms/constraint/less_than_or_eq.rb +19 -0
- data/lib/alchemrest/transforms/constraint/matches_regex.rb +19 -0
- data/lib/alchemrest/transforms/constraint/max_length.rb +19 -0
- data/lib/alchemrest/transforms/constraint/min_length.rb +19 -0
- data/lib/alchemrest/transforms/constraint.rb +17 -0
- data/lib/alchemrest/transforms/constraint_builder/for_number.rb +25 -0
- data/lib/alchemrest/transforms/constraint_builder/for_string.rb +21 -0
- data/lib/alchemrest/transforms/constraint_builder.rb +15 -0
- data/lib/alchemrest/transforms/date_transform.rb +30 -0
- data/lib/alchemrest/transforms/enum.rb +52 -0
- data/lib/alchemrest/transforms/epoch_time.rb +32 -0
- data/lib/alchemrest/transforms/from_chain.rb +15 -0
- data/lib/alchemrest/transforms/from_number/to_type_transform_registry.rb +18 -0
- data/lib/alchemrest/transforms/from_number.rb +47 -0
- data/lib/alchemrest/transforms/from_string/to_type_transform_registry.rb +17 -0
- data/lib/alchemrest/transforms/from_string.rb +36 -0
- data/lib/alchemrest/transforms/from_type/empty_to_type_transform_registry.rb +14 -0
- data/lib/alchemrest/transforms/from_type.rb +64 -0
- data/lib/alchemrest/transforms/iso_time.rb +58 -0
- data/lib/alchemrest/transforms/json_number.rb +26 -0
- data/lib/alchemrest/transforms/loose_hash.rb +96 -0
- data/lib/alchemrest/transforms/money_transform.rb +42 -0
- data/lib/alchemrest/transforms/number.rb +27 -0
- data/lib/alchemrest/transforms/output_type.rb +65 -0
- data/lib/alchemrest/transforms/to_decimal.rb +22 -0
- data/lib/alchemrest/transforms/to_type/from_string_to_time_selector.rb +29 -0
- data/lib/alchemrest/transforms/to_type/transforms_selector.rb +61 -0
- data/lib/alchemrest/transforms/to_type.rb +86 -0
- data/lib/alchemrest/transforms/typed.rb +32 -0
- data/lib/alchemrest/transforms/union.rb +44 -0
- data/lib/alchemrest/transforms/with_constraint.rb +26 -0
- data/lib/alchemrest/transforms.rb +93 -0
- data/lib/alchemrest/url_builder/encoders.rb +39 -0
- data/lib/alchemrest/url_builder/options.rb +33 -0
- data/lib/alchemrest/url_builder.rb +31 -0
- data/lib/alchemrest/version.rb +5 -0
- data/lib/alchemrest/webmock_helpers.rb +27 -0
- data/lib/alchemrest.rb +159 -0
- data/lib/generators/alchemrest/kill_switch_migration_generator.rb +27 -0
- data/lib/generators/alchemrest/templates/kill_switch_migration.rb.erb +17 -0
- data/lib/rubocop/cop/alchemrest/define_request_using_with_params.rb +53 -0
- data/lib/rubocop/cop/alchemrest/endpoint_definition_using_generic_params.rb +55 -0
- data/lib/rubocop/cop/alchemrest/request_hash_returning_block.rb +54 -0
- data/lib/rubocop/cop/alchemrest/time_transform_with_no_zone.rb +56 -0
- data/lib/tapioca/dsl/compilers/alchemrest_data.rb +84 -0
- data/lib/tapioca/dsl/compilers/alchemrest_root.rb +68 -0
- data/mutant.yml +16 -0
- data/rbi/alchemrest/result.rbi +80 -0
- data/rbi/alchemrest.rbi +246 -0
- data/sorbet/config +5 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/abstract_type@0.0.7.rbi +41 -0
- data/sorbet/rbi/gems/actionpack@8.0.4.rbi +11733 -0
- data/sorbet/rbi/gems/actionview@8.0.4.rbi +6560 -0
- data/sorbet/rbi/gems/activemodel@8.0.4.rbi +2891 -0
- data/sorbet/rbi/gems/activesupport@8.0.4.rbi +9621 -0
- data/sorbet/rbi/gems/adamantium@0.2.0.rbi +144 -0
- data/sorbet/rbi/gems/addressable@2.8.7.rbi +779 -0
- data/sorbet/rbi/gems/anima@0.3.2.rbi +103 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +107 -0
- data/sorbet/rbi/gems/base64@0.3.0.rbi +52 -0
- data/sorbet/rbi/gems/benchmark@0.5.0.rbi +153 -0
- data/sorbet/rbi/gems/bigdecimal@3.3.1.rbi +77 -0
- data/sorbet/rbi/gems/builder@3.3.0.rbi +9 -0
- data/sorbet/rbi/gems/circuitbox@2.0.0.rbi +297 -0
- data/sorbet/rbi/gems/concord@0.1.6.rbi +51 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.3.5.rbi +4716 -0
- data/sorbet/rbi/gems/connection_pool@2.5.4.rbi +9 -0
- data/sorbet/rbi/gems/crack@1.0.0.rbi +110 -0
- data/sorbet/rbi/gems/crass@1.0.6.rbi +294 -0
- data/sorbet/rbi/gems/date@3.4.1.rbi +58 -0
- data/sorbet/rbi/gems/drb@2.2.3.rbi +639 -0
- data/sorbet/rbi/gems/equalizer@0.0.11.rbi +38 -0
- data/sorbet/rbi/gems/erubi@1.13.1.rbi +85 -0
- data/sorbet/rbi/gems/factory_bot@6.5.0.rbi +1529 -0
- data/sorbet/rbi/gems/faraday-em_http@1.0.0.rbi +181 -0
- data/sorbet/rbi/gems/faraday-em_synchrony@1.0.1.rbi +120 -0
- data/sorbet/rbi/gems/faraday-excon@1.1.0.rbi +128 -0
- data/sorbet/rbi/gems/faraday-httpclient@1.0.1.rbi +123 -0
- data/sorbet/rbi/gems/faraday-multipart@1.2.0.rbi +190 -0
- data/sorbet/rbi/gems/faraday-net_http@1.0.2.rbi +140 -0
- data/sorbet/rbi/gems/faraday-net_http_persistent@1.2.0.rbi +116 -0
- data/sorbet/rbi/gems/faraday-patron@1.0.0.rbi +119 -0
- data/sorbet/rbi/gems/faraday-rack@1.0.0.rbi +113 -0
- data/sorbet/rbi/gems/faraday-retry@1.0.3.rbi +149 -0
- data/sorbet/rbi/gems/faraday@1.10.5.rbi +1620 -0
- data/sorbet/rbi/gems/hansi@0.2.1.rbi +9 -0
- data/sorbet/rbi/gems/hashdiff@1.1.2.rbi +174 -0
- data/sorbet/rbi/gems/i18n@1.14.7.rbi +1328 -0
- data/sorbet/rbi/gems/ice_nine@0.11.2.rbi +145 -0
- data/sorbet/rbi/gems/io-console@0.8.0.rbi +9 -0
- data/sorbet/rbi/gems/json@2.9.1.rbi +282 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +8057 -0
- data/sorbet/rbi/gems/logger@1.7.0.rbi +260 -0
- data/sorbet/rbi/gems/loofah@2.24.0.rbi +571 -0
- data/sorbet/rbi/gems/memoizable@0.4.2.rbi +131 -0
- data/sorbet/rbi/gems/memosa@0.8.2.rbi +185 -0
- data/sorbet/rbi/gems/minitest@5.26.0.rbi +824 -0
- data/sorbet/rbi/gems/money@6.19.0.rbi +815 -0
- data/sorbet/rbi/gems/morpher@0.4.2.rbi +388 -0
- data/sorbet/rbi/gems/mprelude@0.1.0.rbi +140 -0
- data/sorbet/rbi/gems/multi_json@1.15.0.rbi +180 -0
- data/sorbet/rbi/gems/multipart-post@2.4.1.rbi +154 -0
- data/sorbet/rbi/gems/mustermann-contrib@3.0.3.rbi +9 -0
- data/sorbet/rbi/gems/mustermann@3.0.3.rbi +809 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +112 -0
- data/sorbet/rbi/gems/nokogiri@1.19.1.rbi +3412 -0
- data/sorbet/rbi/gems/parallel@1.26.3.rbi +234 -0
- data/sorbet/rbi/gems/parser@3.3.7.0.rbi +4877 -0
- data/sorbet/rbi/gems/pp@0.6.2.rbi +176 -0
- data/sorbet/rbi/gems/prettyprint@0.2.0.rbi +155 -0
- data/sorbet/rbi/gems/prism@1.5.1.rbi +26368 -0
- data/sorbet/rbi/gems/procto@0.0.3.rbi +9 -0
- data/sorbet/rbi/gems/psych@5.2.3.rbi +806 -0
- data/sorbet/rbi/gems/public_suffix@6.0.1.rbi +267 -0
- data/sorbet/rbi/gems/racc@1.8.1.rbi +120 -0
- data/sorbet/rbi/gems/rack-session@2.1.1.rbi +458 -0
- data/sorbet/rbi/gems/rack-test@2.2.0.rbi +405 -0
- data/sorbet/rbi/gems/rack@3.1.14.rbi +2774 -0
- data/sorbet/rbi/gems/rackup@2.2.1.rbi +132 -0
- data/sorbet/rbi/gems/rails-dom-testing@2.2.0.rbi +266 -0
- data/sorbet/rbi/gems/rails-html-sanitizer@1.6.2.rbi +545 -0
- data/sorbet/rbi/gems/railties@8.0.4.rbi +2150 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +333 -0
- data/sorbet/rbi/gems/rake@13.2.1.rbi +2054 -0
- data/sorbet/rbi/gems/rbi@0.2.3.rbi +3961 -0
- data/sorbet/rbi/gems/rdoc@6.13.1.rbi +6784 -0
- data/sorbet/rbi/gems/regexp_parser@2.11.3.rbi +3020 -0
- data/sorbet/rbi/gems/reline@0.6.0.rbi +9 -0
- data/sorbet/rbi/gems/rexml@3.4.2.rbi +1777 -0
- data/sorbet/rbi/gems/rubocop-ast@1.38.0.rbi +5293 -0
- data/sorbet/rbi/gems/rubocop@1.71.1.rbi +31846 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +980 -0
- data/sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi +9 -0
- data/sorbet/rbi/gems/securerandom@0.4.1.rbi +33 -0
- data/sorbet/rbi/gems/sentry-ruby@5.22.1.rbi +3782 -0
- data/sorbet/rbi/gems/spoom@1.5.1.rbi +4321 -0
- data/sorbet/rbi/gems/stringio@3.1.2.rbi +9 -0
- data/sorbet/rbi/gems/tapioca@0.16.8.rbi +3399 -0
- data/sorbet/rbi/gems/thor@1.3.2.rbi +2012 -0
- data/sorbet/rbi/gems/thread_safe@0.3.6.rbi +711 -0
- data/sorbet/rbi/gems/timeout@0.4.4.rbi +80 -0
- data/sorbet/rbi/gems/tsort@0.2.0.rbi +50 -0
- data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +1677 -0
- data/sorbet/rbi/gems/unicode-display_width@2.6.0.rbi +62 -0
- data/sorbet/rbi/gems/uri@1.1.0.rbi +760 -0
- data/sorbet/rbi/gems/useragent@0.16.11.rbi +9 -0
- data/sorbet/rbi/gems/webmock@3.24.0.rbi +1362 -0
- data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +345 -0
- data/sorbet/rbi/gems/yard@0.9.37.rbi +8795 -0
- data/sorbet/rbi/gems/zeitwerk@2.7.1.rbi +589 -0
- data/sorbet/tapioca/config.yml +45 -0
- data/sorbet/tapioca/require.rb +8 -0
- data/sorbet/tapioca/sorbet/rbi/dsl/.gitattributes +1 -0
- data/sorbet/tapioca/sorbet/rbi/dsl/active_support/callbacks.rbi +23 -0
- metadata +737 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
Bundler::GemHelper.install_tasks
|
|
10
|
+
Bundler::GemHelper.tag_prefix = "#{Bundler::GemHelper.gemspec.name}-"
|
|
11
|
+
|
|
12
|
+
require 'rubocop/rake_task'
|
|
13
|
+
RuboCop::RakeTask.new
|
|
14
|
+
|
|
15
|
+
require 'rspec/core'
|
|
16
|
+
require 'rspec/core/rake_task'
|
|
17
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
18
|
+
|
|
19
|
+
def default_task
|
|
20
|
+
if ENV['APPRAISAL_INITIALIZED'] || ENV['CI']
|
|
21
|
+
%i(rubocop spec)
|
|
22
|
+
else
|
|
23
|
+
require 'appraisal'
|
|
24
|
+
Appraisal::Task.new
|
|
25
|
+
%i(appraisal)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
task(:default).clear.enhance(default_task)
|
data/alchemrest.gemspec
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/alchemrest/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "alchemrest"
|
|
7
|
+
spec.version = Alchemrest::VERSION
|
|
8
|
+
spec.authors = ["Andrew Swerlick", "James Boyer"]
|
|
9
|
+
spec.email = ["andrew.swerlick@betterment.com", "james.boyer@betterment.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = %(
|
|
12
|
+
A library to help you transform third party api's into a set of classes and models
|
|
13
|
+
designed to work nicely with your domain.
|
|
14
|
+
)
|
|
15
|
+
spec.description = %(
|
|
16
|
+
Betterment's library for building robust, reliable, performant integrations with
|
|
17
|
+
third party apis, with a focus on making APIs work with the rest of your domain layer
|
|
18
|
+
not against it.
|
|
19
|
+
)
|
|
20
|
+
spec.license = "MIT"
|
|
21
|
+
spec.homepage = "https://github.com/Betterment/alchemrest"
|
|
22
|
+
spec.required_ruby_version = ">= 3.2"
|
|
23
|
+
|
|
24
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
25
|
+
spec.metadata["changelog_uri"] = "https://github.com/Betterment/alchemrest/CHANGELOG.md"
|
|
26
|
+
|
|
27
|
+
# Specify which files should be added to the gem when it is released.
|
|
28
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
29
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
30
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
31
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
spec.bindir = "exe"
|
|
35
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
36
|
+
spec.require_paths = %w(lib)
|
|
37
|
+
|
|
38
|
+
rails_constraints = [">= 7.2", "< 8.1"]
|
|
39
|
+
|
|
40
|
+
spec.add_dependency 'activerecord', rails_constraints
|
|
41
|
+
spec.add_dependency 'activesupport', rails_constraints
|
|
42
|
+
spec.add_dependency "circuitbox", "~> 2.0.0"
|
|
43
|
+
spec.add_dependency "faraday", ">= 1.10", "< 3.0"
|
|
44
|
+
spec.add_dependency "memosa", ">= 0.8.2"
|
|
45
|
+
spec.add_dependency 'money', '>= 6.0'
|
|
46
|
+
spec.add_dependency 'morpher', '>=0.4.1'
|
|
47
|
+
spec.add_dependency 'multi_json', '~> 1.0'
|
|
48
|
+
spec.add_dependency 'mustermann-contrib', '>= 1.0'
|
|
49
|
+
spec.add_dependency 'railties', rails_constraints
|
|
50
|
+
spec.add_dependency 'sentry-ruby'
|
|
51
|
+
spec.add_dependency 'sorbet-runtime', '>= 0.5.0'
|
|
52
|
+
|
|
53
|
+
spec.add_development_dependency "activemodel"
|
|
54
|
+
spec.add_development_dependency 'appraisal'
|
|
55
|
+
spec.add_development_dependency "betterlint"
|
|
56
|
+
spec.add_development_dependency 'combustion'
|
|
57
|
+
spec.add_development_dependency "factory_bot"
|
|
58
|
+
spec.add_development_dependency "moneta"
|
|
59
|
+
spec.add_development_dependency 'mutant-rspec', '~> 0.13.4'
|
|
60
|
+
spec.add_development_dependency 'pg'
|
|
61
|
+
spec.add_development_dependency "pry"
|
|
62
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
63
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
64
|
+
spec.add_development_dependency "rspec_junit_formatter"
|
|
65
|
+
spec.add_development_dependency 'rubocop'
|
|
66
|
+
spec.add_development_dependency 'rubocop-rspec'
|
|
67
|
+
spec.add_development_dependency 'sorbet'
|
|
68
|
+
spec.add_development_dependency 'tapioca', '>= 0.16.6'
|
|
69
|
+
spec.add_development_dependency "timecop"
|
|
70
|
+
spec.add_development_dependency "webmock"
|
|
71
|
+
end
|
data/coach.yml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
class BankApi::Client < Alchemrest::Client
|
|
5
|
+
configure do |config|
|
|
6
|
+
config.connection.url = 'http://bank.example.com'
|
|
7
|
+
config.service_name = "bank"
|
|
8
|
+
config.use_kill_switch(true)
|
|
9
|
+
config.use_circuit_breaker(disabled_when: -> { ENV.fetch("DISABLE_BANK_API_CIRCUIT", nil) })
|
|
10
|
+
config.connection.customize do |c|
|
|
11
|
+
c.options[:open_timeout] = 4
|
|
12
|
+
c.options[:timeout] = 10
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def build_response(raw_response)
|
|
17
|
+
Response.new(raw_response)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Response < Alchemrest::Response
|
|
21
|
+
def error_details
|
|
22
|
+
error_data = body.fetch("errors", {})
|
|
23
|
+
case error_data
|
|
24
|
+
when Hash
|
|
25
|
+
error_data.symbolize_keys
|
|
26
|
+
else
|
|
27
|
+
error_data
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
module BankApi
|
|
5
|
+
module Data
|
|
6
|
+
class Account < Alchemrest::Data
|
|
7
|
+
schema do |s|
|
|
8
|
+
{
|
|
9
|
+
required: {
|
|
10
|
+
name: s.string,
|
|
11
|
+
status: s.enum(%w(open locked)),
|
|
12
|
+
cards: s.many_of(BankApi::Data::Card),
|
|
13
|
+
},
|
|
14
|
+
optional: {
|
|
15
|
+
nickname: s.string,
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BankApi
|
|
4
|
+
module Data
|
|
5
|
+
class Ach < Alchemrest::Data
|
|
6
|
+
schema do |s|
|
|
7
|
+
{
|
|
8
|
+
required: {
|
|
9
|
+
source_type: s.enum(%w(ach)),
|
|
10
|
+
trace_number: s.from.string.where.max_length(15),
|
|
11
|
+
},
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
module BankApi
|
|
5
|
+
module Data
|
|
6
|
+
class BusinessAccount < Alchemrest::Data
|
|
7
|
+
schema do |s|
|
|
8
|
+
{
|
|
9
|
+
required: {
|
|
10
|
+
name: s.string,
|
|
11
|
+
status: s.enum(%w(open locked)),
|
|
12
|
+
ein: s.string,
|
|
13
|
+
id: s.string,
|
|
14
|
+
},
|
|
15
|
+
optional: {
|
|
16
|
+
nickname: s.string,
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
module BankApi
|
|
5
|
+
module Data
|
|
6
|
+
class Card < Alchemrest::Data
|
|
7
|
+
schema do |s|
|
|
8
|
+
{
|
|
9
|
+
required: {
|
|
10
|
+
source_type: s.enum(%w(card)),
|
|
11
|
+
card_number: s.string,
|
|
12
|
+
expiration_date: s.from.string.to(Time).using(:utc, require_offset: false),
|
|
13
|
+
},
|
|
14
|
+
optional: {
|
|
15
|
+
secondary_user: s.one_of(User),
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
module BankApi
|
|
5
|
+
module Data
|
|
6
|
+
class Check < Alchemrest::Data
|
|
7
|
+
schema do |s|
|
|
8
|
+
{
|
|
9
|
+
required: {
|
|
10
|
+
source_type: s.enum(%w(check)),
|
|
11
|
+
check_number: s.integer,
|
|
12
|
+
check_image_back_url: s.string,
|
|
13
|
+
check_image_front_url: s.string,
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
module BankApi
|
|
5
|
+
module Data
|
|
6
|
+
class Product < Alchemrest::Data
|
|
7
|
+
schema do |s|
|
|
8
|
+
{
|
|
9
|
+
required: {
|
|
10
|
+
name: s.string,
|
|
11
|
+
interest_rate: PositiveInterestString.new(2),
|
|
12
|
+
partner_revenue_rate: s.from.string.where.matches(/^\d+(?:\.\d{1,4})?$/).to(BigDecimal).where("less than 10") do |input|
|
|
13
|
+
input < 10
|
|
14
|
+
end,
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
module BankApi
|
|
5
|
+
module Data
|
|
6
|
+
class Transaction < Alchemrest::Data
|
|
7
|
+
schema do |s|
|
|
8
|
+
{
|
|
9
|
+
required: {
|
|
10
|
+
amount_cents: s.money(:cents),
|
|
11
|
+
status: s.enum(%w(completed pending)),
|
|
12
|
+
settled_at: s.from.string.to(Time).using(:local),
|
|
13
|
+
description: s.string,
|
|
14
|
+
account_id: s.string,
|
|
15
|
+
user_id: s.number,
|
|
16
|
+
},
|
|
17
|
+
optional: {
|
|
18
|
+
source: s.one_of(
|
|
19
|
+
check: BankApi::Data::Check,
|
|
20
|
+
card: BankApi::Data::Card,
|
|
21
|
+
ach: BankApi::Data::Ach,
|
|
22
|
+
discriminator: :source_type,
|
|
23
|
+
),
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def source_identifer
|
|
29
|
+
case (source = self.source)
|
|
30
|
+
when BankApi::Data::Check
|
|
31
|
+
source.check_number
|
|
32
|
+
when BankApi::Data::Card
|
|
33
|
+
source.card_number
|
|
34
|
+
when BankApi::Data::Ach
|
|
35
|
+
source.trace_number
|
|
36
|
+
when nil
|
|
37
|
+
nil
|
|
38
|
+
else
|
|
39
|
+
T.absurd(source)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
configure_response_capture do
|
|
44
|
+
safe :account_id
|
|
45
|
+
omitted :description
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module BankApi
|
|
5
|
+
module Data
|
|
6
|
+
class User < Alchemrest::Data
|
|
7
|
+
schema do |s|
|
|
8
|
+
{
|
|
9
|
+
required: {
|
|
10
|
+
name: s.string,
|
|
11
|
+
status: s.enum(%w(open locked)),
|
|
12
|
+
date_of_birth: s.from.string.to(Time).using(:utc, require_offset: false),
|
|
13
|
+
account_ids: s.integer.array,
|
|
14
|
+
},
|
|
15
|
+
optional: {
|
|
16
|
+
nickname: s.string,
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def age
|
|
22
|
+
# NOTE: This isn't actually correct for a variety of reasons, just an example
|
|
23
|
+
ActiveSupport::TimeZone["UTC"].now.year - date_of_birth.year
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'factory_bot'
|
|
4
|
+
|
|
5
|
+
Alchemrest::FactoryBot.enable!
|
|
6
|
+
|
|
7
|
+
FactoryBot.define do
|
|
8
|
+
alchemrest_factory :bank_api_user, class: 'BankApi::Data::User' do
|
|
9
|
+
name { 'Kevin' }
|
|
10
|
+
status { 'open' }
|
|
11
|
+
date_of_birth { '2021-01-01T00:00:00' }
|
|
12
|
+
account_ids { [1, 2] }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
alchemrest_factory :bank_api_transaction, class: 'BankApi::Data::Transaction' do
|
|
16
|
+
sequence(:user_id)
|
|
17
|
+
amount_cents { 120_00 }
|
|
18
|
+
status { "completed" }
|
|
19
|
+
settled_at { "2023-10-10T00:00:00Z" }
|
|
20
|
+
description { "check transaction" }
|
|
21
|
+
source { association(:bank_api_check) }
|
|
22
|
+
account_id { "123456789" }
|
|
23
|
+
|
|
24
|
+
trait :from_ach do
|
|
25
|
+
source { association(:bank_api_ach) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
trait :from_check do
|
|
29
|
+
source { association(:bank_api_check) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
trait :from_card do
|
|
33
|
+
source { association(:bank_api_card) }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
alchemrest_factory :bank_api_check, class: 'BankApi::Data::Check' do
|
|
38
|
+
source_type { "check" }
|
|
39
|
+
check_number { 100 }
|
|
40
|
+
check_image_back_url { "http://bank.example.com/api/v1/check_images/100/back.png" }
|
|
41
|
+
check_image_front_url { "http://bank.example.com/api/v1/check_images/100/front.png" }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
alchemrest_factory :bank_api_ach, class: 'BankApi::Data::Ach' do
|
|
45
|
+
source_type { "ach" }
|
|
46
|
+
trace_number { '12354689' }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
alchemrest_factory :bank_api_card, class: 'BankApi::Data::Card' do
|
|
50
|
+
source_type { 'card' }
|
|
51
|
+
card_number { '12346' }
|
|
52
|
+
expiration_date { '2023-10-10T00:00:00' }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
alchemrest_factory :bank_api_product, class: 'BankApi::Data::Product' do
|
|
56
|
+
name { 'Savings Account' }
|
|
57
|
+
interest_rate { "4.00%" }
|
|
58
|
+
partner_revenue_rate { "0.0125" }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
alchemrest_factory :bank_api_business_account, class: 'BankApi::Data::BusinessAccount' do
|
|
62
|
+
name { 'ACME Inc' }
|
|
63
|
+
status { 'open' }
|
|
64
|
+
ein { '123456789' }
|
|
65
|
+
|
|
66
|
+
id { SecureRandom.uuid }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
require "active_support/core_ext/string/indent"
|
|
5
|
+
|
|
6
|
+
module BankApi
|
|
7
|
+
# A simple, toy example of how to interact with the introspection interface in alchemrest.
|
|
8
|
+
# Outputs a tree like string showing the graph of all response objects defined in our examples
|
|
9
|
+
class GraphVisualization
|
|
10
|
+
extend T::Sig
|
|
11
|
+
|
|
12
|
+
sig { returns(T::Array[Alchemrest::Data::Graph]) }
|
|
13
|
+
def graphs
|
|
14
|
+
BankApi::Data.constants.sort.map do |name|
|
|
15
|
+
BankApi::Data.const_get(name).graph
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def tree_string
|
|
20
|
+
io = StringIO.new
|
|
21
|
+
graphs.each do |graph|
|
|
22
|
+
print_graph!(graph, io)
|
|
23
|
+
end
|
|
24
|
+
io.string
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
sig { params(graph: Alchemrest::Data::Graph, io: StringIO, indent: Integer).void }
|
|
30
|
+
def print_graph!(graph, io, indent: 0)
|
|
31
|
+
io.puts(T.must(graph.type.name).indent(indent))
|
|
32
|
+
graph.fields.each do |key, field|
|
|
33
|
+
output_type = field.output_type
|
|
34
|
+
io.puts("- #{key} => #{output_type ? output_type.sorbet_type : 'unknown'}".indent(indent))
|
|
35
|
+
field.constraints.each do |constraint|
|
|
36
|
+
io.puts("* #{constraint.description}".indent(indent + 2))
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
graph.sub_graphs.each do |key, sub_graph|
|
|
40
|
+
io.puts "- #{key}"
|
|
41
|
+
print_graph!(sub_graph, io, indent: 2)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BankApi
|
|
4
|
+
class PositiveInterestString < Morpher::Transform
|
|
5
|
+
attr_reader :decimal_places
|
|
6
|
+
|
|
7
|
+
def initialize(decimal_places) # rubocop:disable Lint/MissingSuper
|
|
8
|
+
@decimal_places = decimal_places
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(input)
|
|
12
|
+
return failure(error(message: "Expected a string of the form '<num>%", input:)) unless input[-1] == "%"
|
|
13
|
+
|
|
14
|
+
decimal = BigDecimal(input.chop)
|
|
15
|
+
if input.chop.split(".")[1].length != decimal_places
|
|
16
|
+
failure(error(message: "Expected #{input} to have #{decimal_places} decimal_places", input:))
|
|
17
|
+
elsif decimal.negative?
|
|
18
|
+
failure(error(message: "#{input} is less than 0%", input:))
|
|
19
|
+
else
|
|
20
|
+
success(decimal)
|
|
21
|
+
end
|
|
22
|
+
rescue ArgumentError => e
|
|
23
|
+
raise unless e.message.start_with? "invalid value for BigDecimal()"
|
|
24
|
+
|
|
25
|
+
failure(
|
|
26
|
+
error(
|
|
27
|
+
message: "Expected: #{input} to be a decimal",
|
|
28
|
+
input:,
|
|
29
|
+
),
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module BankApi
|
|
5
|
+
module Requests
|
|
6
|
+
class DeleteUser < Alchemrest::Request
|
|
7
|
+
def initialize(id:)
|
|
8
|
+
@id = id
|
|
9
|
+
super()
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
endpoint :delete, '/api/v1/users/:id' do |url|
|
|
13
|
+
url.values = { id: @id }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
module BankApi
|
|
5
|
+
module Requests
|
|
6
|
+
class GetBusinessAccount < Alchemrest::Request
|
|
7
|
+
include ActiveModel::Model
|
|
8
|
+
|
|
9
|
+
enable_response_capture
|
|
10
|
+
|
|
11
|
+
attr_accessor :token, :id
|
|
12
|
+
|
|
13
|
+
endpoint :get, '/api/business_accounts/:id' do |url|
|
|
14
|
+
url.values = { id: }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
returns BankApi::Data::BusinessAccount
|
|
18
|
+
|
|
19
|
+
def headers
|
|
20
|
+
{ Authorization: "Bearer #{token}" }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BankApi
|
|
4
|
+
module Requests
|
|
5
|
+
class GetTransactions < Alchemrest::Request
|
|
6
|
+
include ActiveModel::Model
|
|
7
|
+
attr_accessor :account_id, :id, :amount
|
|
8
|
+
|
|
9
|
+
returns BankApi::Data::Transaction[]
|
|
10
|
+
endpoint :get, '/api/v1/users/:id/accounts/:account_id/transactions' do |url|
|
|
11
|
+
url.values = { id:, account_id: }
|
|
12
|
+
end
|
|
13
|
+
enable_response_capture
|
|
14
|
+
|
|
15
|
+
def response_transformer
|
|
16
|
+
super.insert_after(
|
|
17
|
+
Alchemrest::Response::Pipeline::ExtractPayload,
|
|
18
|
+
ExtractUserId.new(self),
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class ExtractUserId < Alchemrest::Response::Pipeline::Transform
|
|
23
|
+
def initialize(request)
|
|
24
|
+
super()
|
|
25
|
+
@request = request
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def call(payload)
|
|
29
|
+
success(payload.map { |item| item.merge("user_id" => @request.id) })
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module BankApi
|
|
5
|
+
module Requests
|
|
6
|
+
class GetUser < Alchemrest::Request
|
|
7
|
+
def initialize(id:) # rubocop:disable Lint/MissingSuper
|
|
8
|
+
@id = id
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
returns BankApi::Data::User
|
|
12
|
+
endpoint :get, '/api/v1/users/:id' do |url|
|
|
13
|
+
url.values = { id: @id }
|
|
14
|
+
url.query = { includeDetails: true }
|
|
15
|
+
end
|
|
16
|
+
enable_response_capture
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
module BankApi
|
|
5
|
+
module Requests
|
|
6
|
+
class PostTransaction < Alchemrest::Request
|
|
7
|
+
include ActiveModel::Model
|
|
8
|
+
attr_accessor :account_id, :id, :amount
|
|
9
|
+
|
|
10
|
+
endpoint :post, '/api/v1/users/:id/accounts/:account_id/transactions' do |url|
|
|
11
|
+
url.values = { id:, account_id: }
|
|
12
|
+
end
|
|
13
|
+
enable_response_capture
|
|
14
|
+
|
|
15
|
+
def body
|
|
16
|
+
{ amount: }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module BankApi
|
|
5
|
+
module Requests
|
|
6
|
+
class UpdateUser < Alchemrest::Request
|
|
7
|
+
def initialize(id:, name:, date_of_birth:)
|
|
8
|
+
@id = id
|
|
9
|
+
@name = name
|
|
10
|
+
@date_of_birth = date_of_birth
|
|
11
|
+
super()
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
endpoint :patch, '/api/v1/users/:id' do |url|
|
|
15
|
+
url.values = { id: @id }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def body
|
|
19
|
+
{
|
|
20
|
+
name: @name,
|
|
21
|
+
date_of_birth: @date_of_birth,
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
returns BankApi::Data::User, allow_empty_response: true
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|