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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class LooseHash < Morpher::Transform
|
|
6
|
+
include Anima.new(:allow_additional_properties, :optional, :required)
|
|
7
|
+
|
|
8
|
+
KEY_MESSAGE = 'Missing keys: %<missing>s, Unexpected keys: %<unexpected>s'
|
|
9
|
+
PRIMITIVE = Primitive.new(::Hash)
|
|
10
|
+
|
|
11
|
+
private_constant(*constants(false))
|
|
12
|
+
|
|
13
|
+
# Apply transformation to input
|
|
14
|
+
#
|
|
15
|
+
# @param [Object] input
|
|
16
|
+
#
|
|
17
|
+
# @return [Either<Error, Object>]
|
|
18
|
+
def call(input)
|
|
19
|
+
PRIMITIVE
|
|
20
|
+
.call(input)
|
|
21
|
+
.lmap { |e| lift_error(e) }
|
|
22
|
+
.bind { |o| reject_keys(o) }
|
|
23
|
+
.bind { |o| transform(o) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def transform(input)
|
|
29
|
+
transform_required(input).bind do |required|
|
|
30
|
+
transform_optional(input).fmap(&required.public_method(:merge))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def transform_required(input)
|
|
35
|
+
transform_keys(required, input)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
memoize def defaults
|
|
39
|
+
optional.map(&:value).product([nil]).to_h
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def transform_optional(input)
|
|
43
|
+
transform_keys(
|
|
44
|
+
optional.select { |key| input.key?(key.value) },
|
|
45
|
+
input,
|
|
46
|
+
).fmap(&defaults.public_method(:merge))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def transform_keys(keys, input)
|
|
50
|
+
success(
|
|
51
|
+
keys
|
|
52
|
+
.to_h do |key|
|
|
53
|
+
[
|
|
54
|
+
key.value,
|
|
55
|
+
coerce_key(key, input).from_right do |error|
|
|
56
|
+
return failure(error)
|
|
57
|
+
end,
|
|
58
|
+
]
|
|
59
|
+
end,
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def coerce_key(key, input)
|
|
64
|
+
key.call(input.fetch(key.value)).lmap do |error|
|
|
65
|
+
error(input: input, cause: error)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def reject_keys(input)
|
|
70
|
+
keys = input.keys
|
|
71
|
+
unexpected = allow_additional_properties ? [] : (keys - allowed_keys)
|
|
72
|
+
missing = required_keys - keys
|
|
73
|
+
unexpected_properties_exist = allow_additional_properties || unexpected.empty?
|
|
74
|
+
|
|
75
|
+
if unexpected_properties_exist && missing.empty?
|
|
76
|
+
success(input)
|
|
77
|
+
else
|
|
78
|
+
failure(
|
|
79
|
+
error(
|
|
80
|
+
input: input,
|
|
81
|
+
message: format(KEY_MESSAGE, missing: missing, unexpected: unexpected),
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
memoize def allowed_keys
|
|
88
|
+
required_keys + optional.map(&:value)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
memoize def required_keys
|
|
92
|
+
required.map(&:value)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class MoneyTransform < Morpher::Transform
|
|
6
|
+
include Concord.new(:unit)
|
|
7
|
+
|
|
8
|
+
private_constant(*constants(false))
|
|
9
|
+
|
|
10
|
+
def call(input)
|
|
11
|
+
case input
|
|
12
|
+
when Numeric
|
|
13
|
+
success(into_money(input))
|
|
14
|
+
else
|
|
15
|
+
failure_error(input)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def into_money(amount)
|
|
22
|
+
case unit
|
|
23
|
+
when :cents
|
|
24
|
+
Money.from_cents(amount)
|
|
25
|
+
when :dollars
|
|
26
|
+
Money.from_amount(amount)
|
|
27
|
+
else
|
|
28
|
+
raise "Invalid unit #{unit}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def failure_error(input)
|
|
33
|
+
failure(
|
|
34
|
+
error(
|
|
35
|
+
message: "Expected: Numeric but got #{input.class}",
|
|
36
|
+
input: input,
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class Number < Morpher::Transform
|
|
6
|
+
def call(input)
|
|
7
|
+
case input
|
|
8
|
+
when Numeric
|
|
9
|
+
success(input)
|
|
10
|
+
else
|
|
11
|
+
failure_error(input)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def failure_error(input)
|
|
18
|
+
failure(
|
|
19
|
+
error(
|
|
20
|
+
message: "Expected: Numeric but got #{input.class}",
|
|
21
|
+
input: input,
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
# A wrapper around other transforms that provides metadata about the return type of the transform.
|
|
6
|
+
# Note this does not do anything to actually enforce that return type, although we may want to change
|
|
7
|
+
# that in the future. It's main purpose is to provide metadata we can use to inspect the schema of
|
|
8
|
+
# an `Alchemrest::Data` class.
|
|
9
|
+
class OutputType < T::Struct
|
|
10
|
+
const :sorbet_type, T.any(Class, T::Types::Base)
|
|
11
|
+
const :constraints, T::Array[Alchemrest::Transforms::Constraint]
|
|
12
|
+
|
|
13
|
+
def self.simple(sorbet_type)
|
|
14
|
+
new(sorbet_type:, constraints: [])
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def graph
|
|
18
|
+
graphs = graph_types.select { |type| has_graph?(type) }.map(&:graph)
|
|
19
|
+
|
|
20
|
+
if graphs.size == 1
|
|
21
|
+
graphs.sole
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def with(args)
|
|
26
|
+
OutputType.new({ sorbet_type:, constraints: }.merge(args))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def ==(other)
|
|
30
|
+
sorbet_type == other.sorbet_type && constraints.to_set == other.constraints.to_set
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def has_graph?(type)
|
|
36
|
+
type.respond_to?(:<=) && type < Data
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def graph_types
|
|
40
|
+
recursively_unwrap_raw_type(sorbet_type)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def recursively_unwrap_raw_type(type)
|
|
44
|
+
current_types = case type
|
|
45
|
+
in T::Types::Simple
|
|
46
|
+
[type.raw_type]
|
|
47
|
+
in T::Types::TypedArray => array
|
|
48
|
+
[array.type]
|
|
49
|
+
in T::Types::Union => union
|
|
50
|
+
union.types
|
|
51
|
+
else
|
|
52
|
+
[type]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
current_types.map { |current_type|
|
|
56
|
+
if current_type.instance_of?(T::Types::TypedArray) || current_type.instance_of?(T::Types::Simple)
|
|
57
|
+
recursively_unwrap_raw_type(current_type)
|
|
58
|
+
else
|
|
59
|
+
current_type
|
|
60
|
+
end
|
|
61
|
+
}.flatten
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class ToDecimal < Morpher::Transform
|
|
6
|
+
def call(input)
|
|
7
|
+
success(BigDecimal(input, 0))
|
|
8
|
+
rescue TypeError, ArgumentError
|
|
9
|
+
cannot_make_decimal(input)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def cannot_make_decimal(input)
|
|
13
|
+
failure(
|
|
14
|
+
error(
|
|
15
|
+
message: "Expected #{input} to be castable to BigDecimal",
|
|
16
|
+
input:,
|
|
17
|
+
),
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class ToType
|
|
6
|
+
class FromStringToTimeSelector < TransformsSelector
|
|
7
|
+
def initialize(from)
|
|
8
|
+
super(from, nil, {})
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def using(timezone_identifier, require_offset: true)
|
|
12
|
+
use, to = case timezone_identifier
|
|
13
|
+
in :utc
|
|
14
|
+
[[IsoTime.new(to_timezone: 'UTC', require_offset:)], ActiveSupport::TimeWithZone]
|
|
15
|
+
in :local
|
|
16
|
+
[[IsoTime.new(to_timezone: Time.zone.name, require_offset:)], ActiveSupport::TimeWithZone]
|
|
17
|
+
in :offset
|
|
18
|
+
raise ArgumentError, "require_offset cannot be false when using :offset" unless require_offset
|
|
19
|
+
|
|
20
|
+
[[IsoTime.new(to_timezone: nil, require_offset: true)], Time]
|
|
21
|
+
in String
|
|
22
|
+
[[IsoTime.new(to_timezone: timezone_identifier, require_offset:)], ActiveSupport::TimeWithZone]
|
|
23
|
+
end
|
|
24
|
+
ToType.new(from:, to:, use:)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class ToType
|
|
6
|
+
class TransformsSelector
|
|
7
|
+
INVALID_KEY_OR_TRANSFORM_ERROR_MESSAGE = "Must provide a symbol key or a Morpher::Transform"
|
|
8
|
+
INVALID_TRANSFORMS_HASH_ERROR_MESSAGE = "transform_options must a Hash with symbol keys, and values of Morpher::Transform[]"
|
|
9
|
+
|
|
10
|
+
include Concord.new(:from, :to, :transform_options)
|
|
11
|
+
|
|
12
|
+
def initialize(*)
|
|
13
|
+
super
|
|
14
|
+
unless transform_options_valid?
|
|
15
|
+
raise ArgumentError, INVALID_TRANSFORMS_HASH_ERROR_MESSAGE
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def using(key_or_transform)
|
|
20
|
+
raise ArgumentError, INVALID_KEY_OR_TRANSFORM_ERROR_MESSAGE unless a_symbol_or_transform?(key_or_transform)
|
|
21
|
+
|
|
22
|
+
transforms_to_use = resolve_transforms_array(key_or_transform)
|
|
23
|
+
|
|
24
|
+
unless transforms_to_use
|
|
25
|
+
raise NoTransformOptionForNameError.new(from:, to:, name: key_or_transform, options: transform_options)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
ToType.new(from:, to:, use: transforms_to_use)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def options
|
|
32
|
+
transform_options.keys
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def transform_options_valid?
|
|
38
|
+
transform_options.instance_of?(Hash) &&
|
|
39
|
+
transform_options.keys.all?(Symbol) &&
|
|
40
|
+
transform_options.values.all? { |value| transform_array?(value) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def a_symbol_or_transform?(value)
|
|
44
|
+
value.instance_of?(Symbol) || value.is_a?(Morpher::Transform)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def transform_array?(value)
|
|
48
|
+
value.instance_of?(Array) && value.all?(Morpher::Transform)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def resolve_transforms_array(value)
|
|
52
|
+
if value.is_a?(Morpher::Transform)
|
|
53
|
+
[value]
|
|
54
|
+
elsif transform_options.key?(value)
|
|
55
|
+
transform_options.fetch(value)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
# A class for taking in an input and transforming it so something else. To initialize this class
|
|
6
|
+
# you must provide a `FromType` transform that first validates that the input is actually transformable
|
|
7
|
+
# (via the `from:` param) and an array of transforms that will actualy do the transformation
|
|
8
|
+
# (via the `use:` param) If you want to run additional validations after transformation, you can use the `#where` method.
|
|
9
|
+
class ToType < Morpher::Transform
|
|
10
|
+
include Alchemrest::Transforms::Constrainable.new(additional_attributes: %i(to from use))
|
|
11
|
+
DEFAULTS = { constraints: [] }.freeze
|
|
12
|
+
|
|
13
|
+
def self.using(args, &block)
|
|
14
|
+
new(**args, use: [Success.new(block)])
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(args)
|
|
18
|
+
super(**DEFAULTS.merge(args))
|
|
19
|
+
|
|
20
|
+
raise ArgumentError, ":to must be a Class" unless to.instance_of?(Class)
|
|
21
|
+
raise ArgumentError, ":from must be a FromType transform" unless from.is_a?(FromType)
|
|
22
|
+
|
|
23
|
+
unless use.instance_of?(::Array) && !use.empty? && use.all?(Morpher::Transform)
|
|
24
|
+
raise ArgumentError, ":use must be an array of Morpher::Transform instances"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def output_type
|
|
29
|
+
OutputType.new(sorbet_type: to, constraints: all_constraints)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def using(transforms)
|
|
33
|
+
with(use: transforms)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def call(input)
|
|
37
|
+
transform.call(input)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def all_constraints
|
|
41
|
+
[*from.constraints, *constraints]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def constraints_for(type)
|
|
45
|
+
raise ArgumentError, "Must provide a Class" unless type.instance_of?(Class)
|
|
46
|
+
|
|
47
|
+
unless [self, from].map { |transform| transform.output_type.sorbet_type }.include?(type)
|
|
48
|
+
raise ArgumentError,
|
|
49
|
+
"`type` must be either the to type (#{output_type.sorbet_type}) or the " \
|
|
50
|
+
"from type (#{from.output_type.sorbet_type}), was #{type}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
[self, from].select { |transform| transform.output_type.sorbet_type == type }.flat_map(&:constraints)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def array
|
|
57
|
+
Typed.new(transform: super(), output_type: output_type.with(sorbet_type: T::Array[(output_type.sorbet_type)]))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def maybe
|
|
61
|
+
Typed.new(transform: super(), output_type: output_type.with(sorbet_type: T.nilable(output_type.sorbet_type)))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def transform
|
|
67
|
+
Sequence.new([
|
|
68
|
+
from,
|
|
69
|
+
*use,
|
|
70
|
+
validate_output,
|
|
71
|
+
validate_constraints,
|
|
72
|
+
])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def validate_output
|
|
76
|
+
Block.capture("Alchemrest::Transform::ToType") do |current|
|
|
77
|
+
if current.is_a?(to)
|
|
78
|
+
success(current)
|
|
79
|
+
else
|
|
80
|
+
failure("Transform chain created an ouput of type: #{current.class}. Expected: #{to}")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
# A wrapper around other transforms that provides metadata about the return type of the transform.
|
|
6
|
+
# Note this does not do anything to actually enforce that return type, although we may want to change
|
|
7
|
+
# that in the future. It's main purpose is to provide metadata we can use to inspect the schema of
|
|
8
|
+
# an `Alchemrest::Data` class.
|
|
9
|
+
class Typed < Morpher::Transform
|
|
10
|
+
include Anima.new(:transform, :output_type)
|
|
11
|
+
include Adamantium::Mutable
|
|
12
|
+
|
|
13
|
+
def call(input)
|
|
14
|
+
transform.call(input)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def array
|
|
18
|
+
Typed.new(
|
|
19
|
+
transform: super(),
|
|
20
|
+
output_type: output_type.with(sorbet_type: T::Array[output_type.sorbet_type]),
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def maybe
|
|
25
|
+
Typed.new(
|
|
26
|
+
transform: super(),
|
|
27
|
+
output_type: output_type.with(sorbet_type: T.nilable(output_type.sorbet_type)),
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class Union < Morpher::Transform
|
|
6
|
+
include Anima.new(:types, :discriminator)
|
|
7
|
+
|
|
8
|
+
private_constant(*constants(false))
|
|
9
|
+
|
|
10
|
+
def call(input)
|
|
11
|
+
type_key = input&.fetch(discriminator.to_s)
|
|
12
|
+
if type_key.nil?
|
|
13
|
+
klass_not_found_failure_error(input)
|
|
14
|
+
else
|
|
15
|
+
perform_transformation(input, type_key)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def output_type
|
|
20
|
+
OutputType.simple(T.any(*types.values))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def perform_transformation(input, type_key)
|
|
26
|
+
klass = types[type_key.to_sym]
|
|
27
|
+
if klass
|
|
28
|
+
klass::TRANSFORM.call(input)
|
|
29
|
+
else
|
|
30
|
+
klass_not_found_failure_error(input)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def klass_not_found_failure_error(input)
|
|
35
|
+
failure(
|
|
36
|
+
error(
|
|
37
|
+
message: "Expected discriminator #{discriminator} to produce a value which is one of #{types.keys.join(',')} but got #{input&.fetch(discriminator).nil? ? 'nil' : input[discriminator]}", # rubocop:disable Layout/LineLength
|
|
38
|
+
input: input,
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class WithConstraint < Morpher::Transform
|
|
6
|
+
MESSAGE = %(Input %<actual>p does not meet the constraint "%<description>s")
|
|
7
|
+
include Concord.new(:constraint)
|
|
8
|
+
|
|
9
|
+
def initialize(constraint)
|
|
10
|
+
raise ArgumentError, "Must provide an instance of Alchemrest::Transform::Constraint" unless constraint.is_a?(Constraint)
|
|
11
|
+
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(input)
|
|
16
|
+
if constraint.meets_conditions?(input)
|
|
17
|
+
success(input)
|
|
18
|
+
else
|
|
19
|
+
failure(
|
|
20
|
+
error(input:, message: format(MESSAGE, actual: input, description: constraint.description)),
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
# The Transforms module is passed into the block provided to `Alchemrest::Data.schema`, giving developers an easy way to
|
|
5
|
+
# define the transforms for a given data class
|
|
6
|
+
#
|
|
7
|
+
# @example Using `Alchemrest::Transforms` inside a `schema` block
|
|
8
|
+
# class User < Alchemrest::Data
|
|
9
|
+
# schema do |s|
|
|
10
|
+
# # `s` is `Alchemrest::Transforms`
|
|
11
|
+
# end
|
|
12
|
+
# end
|
|
13
|
+
module Transforms
|
|
14
|
+
# A transformation that results in an integer number. Must be a true integer in JSON, not a string as number. Will
|
|
15
|
+
# result in a `Alchemrest::MorpherTransformError` if not an integer number.
|
|
16
|
+
def self.integer
|
|
17
|
+
Typed.new(transform: Morpher::Transform::INTEGER, output_type: OutputType.simple(Integer))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# A transformation that results in a string. Must be a true string in JSON, we will not coerce booleans or number to strings. Will
|
|
21
|
+
# result in a `Alchemrest::MorpherTransformError` if not a string.
|
|
22
|
+
def self.string
|
|
23
|
+
Typed.new(transform: Morpher::Transform::STRING, output_type: OutputType.simple(String))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# A transformation that results in a float. Must be a float in JSON, we will coerce strings into numbers. Will
|
|
27
|
+
# result in a `Alchemrest::MorpherTransformError` if not a float. Note, if the api sometimes returns a float and
|
|
28
|
+
# sometimes returns an integer, see {#number} instead.
|
|
29
|
+
def self.float
|
|
30
|
+
Typed.new(transform: Morpher::Transform::FLOAT, output_type: OutputType.simple(Float))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.from
|
|
34
|
+
FromChain
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# A transformation that results in some kind of numeric type, generally either a Float or Integer. If the input is not
|
|
38
|
+
# Numeric, will result in a `Alchemrest::MorpherTransformError`
|
|
39
|
+
def self.number
|
|
40
|
+
Typed.new(transform: Number.new, output_type: OutputType.simple(T.any(Float, Integer)))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# A transformation that results in a boolean. If the input is not a boolean, will result in a `Alchemrest::MorpherTransformError`
|
|
44
|
+
def self.boolean
|
|
45
|
+
Typed.new(transform: Morpher::Transform::BOOLEAN, output_type: OutputType.simple(T::Boolean))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# A transformation that results in a Date object.
|
|
49
|
+
# If the input is not an iso8601 date string, will result in a `Alchemrest::MorpherTransformError`
|
|
50
|
+
def self.date
|
|
51
|
+
Typed.new(transform: DateTransform.new, output_type: OutputType.simple(Date))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# A transformation that creates a {Money} object. You can indicate
|
|
55
|
+
# whether the original amount is in dollars our cents via the unit
|
|
56
|
+
# param. If the original value is not numeric, it will result in a
|
|
57
|
+
# `Alchemrest::MorpherTransformError`
|
|
58
|
+
#
|
|
59
|
+
# @param [Symbol] (:dollars | :cents) the unit for the original amount
|
|
60
|
+
def self.money(unit)
|
|
61
|
+
Typed.new(transform: MoneyTransform.new(unit), output_type: OutputType.simple(Money))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# A transformation that results in a {Symbol, String} from a predefined list.
|
|
65
|
+
# If the original value is not a string from the predefined list, it will
|
|
66
|
+
# result in a `Alchemrest::MorpherTransformError`
|
|
67
|
+
#
|
|
68
|
+
# @param [Array<Symbol>] the list of valid values for the field
|
|
69
|
+
def self.enum(enum)
|
|
70
|
+
Typed.new(transform: Enum.new(enum), output_type: OutputType.simple(T.any(Symbol, String)))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# A transformation that results in an Alchemrest::Data class. You can
|
|
74
|
+
# either provide a klass or a Hash with a `discriminator` value for
|
|
75
|
+
# polymorphic data types
|
|
76
|
+
#
|
|
77
|
+
# @param [Class, Hash<Symbol, Class>] Either an `Alchemrest::Data` class,
|
|
78
|
+
# or a hash of classes along with a `discriminator` key for polymorphic
|
|
79
|
+
# use cases
|
|
80
|
+
def self.one_of(klass_or_hash)
|
|
81
|
+
if klass_or_hash.instance_of? Hash
|
|
82
|
+
klasses = klass_or_hash.except(:discriminator)
|
|
83
|
+
Union.new(types: klasses, discriminator: klass_or_hash.fetch(:discriminator))
|
|
84
|
+
else
|
|
85
|
+
klass_or_hash::TRANSFORM
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.many_of(klass)
|
|
90
|
+
klass::TRANSFORM.array
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Alchemrest
|
|
5
|
+
class UrlBuilder
|
|
6
|
+
module Encoders
|
|
7
|
+
def self.find(name)
|
|
8
|
+
case name
|
|
9
|
+
in :rack
|
|
10
|
+
RackEncoded.new
|
|
11
|
+
in :form
|
|
12
|
+
FormUrlEncoded.new
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class RackEncoded
|
|
17
|
+
def call(query)
|
|
18
|
+
Rack::Utils.build_nested_query(query)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class FormUrlEncoded
|
|
23
|
+
def call(query)
|
|
24
|
+
URI.encode_www_form(query)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class Custom
|
|
29
|
+
def initialize(&block)
|
|
30
|
+
@block = block
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def call(query)
|
|
34
|
+
@block.call(query)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|