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,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class Constraint
|
|
6
|
+
class IsUuid < MatchesRegex
|
|
7
|
+
REGEX = /\A((?:\h{8})-(?:\h{4})-(?:\h{4})-(?:\h{4})-(?:\h{12}))\z/
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
super(REGEX)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def description
|
|
14
|
+
"is a UUID string"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class Constraint
|
|
6
|
+
class LessThan < self
|
|
7
|
+
include Concord.new(:value)
|
|
8
|
+
|
|
9
|
+
def meets_conditions?(input)
|
|
10
|
+
input < value
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def description
|
|
14
|
+
"less than #{value}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class Constraint
|
|
6
|
+
class LessThanOrEq < self
|
|
7
|
+
include Concord.new(:value)
|
|
8
|
+
|
|
9
|
+
def meets_conditions?(input)
|
|
10
|
+
input <= value
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def description
|
|
14
|
+
"less than or equal to #{value}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class Constraint
|
|
6
|
+
class MatchesRegex < self
|
|
7
|
+
include Concord.new(:regex)
|
|
8
|
+
|
|
9
|
+
def meets_conditions?(input)
|
|
10
|
+
input.match?(regex)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def description
|
|
14
|
+
"matches #{regex.inspect}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class Constraint
|
|
6
|
+
class MaxLength < self
|
|
7
|
+
include Concord.new(:max_length)
|
|
8
|
+
|
|
9
|
+
def meets_conditions?(input)
|
|
10
|
+
input.length <= max_length
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def description
|
|
14
|
+
"max length of #{max_length}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class Constraint
|
|
6
|
+
class MinLength < self
|
|
7
|
+
include Concord.new(:min_length)
|
|
8
|
+
|
|
9
|
+
def meets_conditions?(input)
|
|
10
|
+
input.length >= min_length
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def description
|
|
14
|
+
"min length of #{min_length}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
# The Alchemrest::Transforms::Constraint class is an abstract base that all other constraints should inherit from
|
|
6
|
+
# A constraint is basically just a predicate with metadata. The `meets_conditions?` method is the actual predicate,
|
|
7
|
+
# and description is a human-readable phrase that describes that predicate. We recommend using a phrase that will
|
|
8
|
+
# read well in a sentence like "<input> does not meet the constraint <description>", since that is how it will
|
|
9
|
+
# display in error messages
|
|
10
|
+
class Constraint
|
|
11
|
+
include AbstractType
|
|
12
|
+
|
|
13
|
+
abstract_method :meets_conditions?
|
|
14
|
+
abstract_method :description
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class ConstraintBuilder
|
|
6
|
+
class ForNumber < self
|
|
7
|
+
CONSTRAINT_METHODS = %i(greater_than less_than greater_than_or_eq less_than_or_eq positive non_negative integer).freeze
|
|
8
|
+
|
|
9
|
+
def greater_than(...) = apply_constraint(Constraint::GreaterThan.new(...))
|
|
10
|
+
|
|
11
|
+
def less_than(...) = apply_constraint(Constraint::LessThan.new(...))
|
|
12
|
+
|
|
13
|
+
def greater_than_or_eq(...) = apply_constraint(Constraint::GreaterThanOrEq.new(...))
|
|
14
|
+
|
|
15
|
+
def less_than_or_eq(...) = apply_constraint(Constraint::LessThanOrEq.new(...))
|
|
16
|
+
|
|
17
|
+
def positive = apply_constraint(Constraint::GreaterThan.new(0))
|
|
18
|
+
|
|
19
|
+
def non_negative = apply_constraint(Constraint::GreaterThanOrEq.new(0))
|
|
20
|
+
|
|
21
|
+
def integer = apply_constraint(Constraint::IsInstanceOf.new(Integer))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class ConstraintBuilder
|
|
6
|
+
class ForString < self
|
|
7
|
+
CONSTRAINT_METHODS = %i(max_length min_length matches in must_be_uuid).freeze
|
|
8
|
+
|
|
9
|
+
def max_length(...) = apply_constraint(Constraint::MaxLength.new(...))
|
|
10
|
+
|
|
11
|
+
def min_length(...) = apply_constraint(Constraint::MinLength.new(...))
|
|
12
|
+
|
|
13
|
+
def matches(...) = apply_constraint(Constraint::MatchesRegex.new(...))
|
|
14
|
+
|
|
15
|
+
def in(...) = apply_constraint(Constraint::InList.new(...))
|
|
16
|
+
|
|
17
|
+
def must_be_uuid = apply_constraint(Constraint::IsUuid.new)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/time'
|
|
4
|
+
|
|
5
|
+
module Alchemrest
|
|
6
|
+
module Transforms
|
|
7
|
+
class DateTransform < Morpher::Transform
|
|
8
|
+
def call(input)
|
|
9
|
+
if input.instance_of?(String)
|
|
10
|
+
begin
|
|
11
|
+
success(Date.iso8601(input))
|
|
12
|
+
rescue ArgumentError
|
|
13
|
+
not_a_valid_iso_date_string(input)
|
|
14
|
+
end
|
|
15
|
+
else
|
|
16
|
+
not_a_valid_iso_date_string(input)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def not_a_valid_iso_date_string(input)
|
|
21
|
+
failure(
|
|
22
|
+
error(
|
|
23
|
+
message: "Expected #{input} to be an iso date string",
|
|
24
|
+
input: input,
|
|
25
|
+
),
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class Enum < Morpher::Transform
|
|
6
|
+
include Concord.new(:values)
|
|
7
|
+
|
|
8
|
+
private_constant(*constants(false))
|
|
9
|
+
|
|
10
|
+
def call(input)
|
|
11
|
+
case values
|
|
12
|
+
when ::Hash
|
|
13
|
+
evaluate_hash(input)
|
|
14
|
+
when ::Array
|
|
15
|
+
evaluate_array(input)
|
|
16
|
+
else
|
|
17
|
+
failure_error(input)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def evaluate_array(input)
|
|
24
|
+
if values.include?(input)
|
|
25
|
+
success(input)
|
|
26
|
+
elsif input.is_a?(String) && values.include?(input.to_sym)
|
|
27
|
+
success(input.to_sym)
|
|
28
|
+
else
|
|
29
|
+
failure_error(input)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def evaluate_hash(input)
|
|
34
|
+
value = values[input]
|
|
35
|
+
if value
|
|
36
|
+
success(value)
|
|
37
|
+
else
|
|
38
|
+
failure_error(input)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def failure_error(input)
|
|
43
|
+
failure(
|
|
44
|
+
error(
|
|
45
|
+
message: "Expected: enum value from #{values} but got: #{input.inspect}",
|
|
46
|
+
input: input,
|
|
47
|
+
),
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/time'
|
|
4
|
+
|
|
5
|
+
module Alchemrest
|
|
6
|
+
module Transforms
|
|
7
|
+
class EpochTime < Morpher::Transform
|
|
8
|
+
include Concord.new(:unit)
|
|
9
|
+
|
|
10
|
+
def call(input)
|
|
11
|
+
if input.is_a?(Integer)
|
|
12
|
+
as_seconds = case unit
|
|
13
|
+
when :seconds
|
|
14
|
+
input
|
|
15
|
+
when :milliseconds
|
|
16
|
+
input / 1000
|
|
17
|
+
else
|
|
18
|
+
raise "Invalid unit #{unit}"
|
|
19
|
+
end
|
|
20
|
+
success(Time.at(as_seconds).in_time_zone("UTC"))
|
|
21
|
+
else
|
|
22
|
+
failure(
|
|
23
|
+
error(
|
|
24
|
+
message: "Expected #{input} to be an epoch integer",
|
|
25
|
+
input: input,
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class FromNumber
|
|
6
|
+
class ToTypeTransformRegistry < BaseToTypeTransformRegistry
|
|
7
|
+
def build_transforms
|
|
8
|
+
{
|
|
9
|
+
Money => {
|
|
10
|
+
cents: [MoneyTransform.new(:cents)],
|
|
11
|
+
dollars: [MoneyTransform.new(:dollars)],
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class FromNumber < FromType
|
|
6
|
+
OVERRIDES = { type: T.any(Integer, Float) }.freeze
|
|
7
|
+
CONSTRAINT_BUILDER_CLASS = ConstraintBuilder::ForNumber
|
|
8
|
+
|
|
9
|
+
def initialize(args = {})
|
|
10
|
+
super(**args, **OVERRIDES)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def where(constraint_or_description = nil, &block)
|
|
14
|
+
if constraint_or_description
|
|
15
|
+
super
|
|
16
|
+
elsif block
|
|
17
|
+
raise ArgumentError, "Must provide a constraint or description"
|
|
18
|
+
else
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def output_type_name
|
|
24
|
+
"Number"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
delegate(*CONSTRAINT_BUILDER_CLASS::CONSTRAINT_METHODS, to: :constraint_builder)
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def constraint_builder
|
|
32
|
+
CONSTRAINT_BUILDER_CLASS.new(self)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def transform
|
|
36
|
+
Sequence.new([
|
|
37
|
+
JsonNumber.new,
|
|
38
|
+
validate_constraints,
|
|
39
|
+
])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
memoize def to_type_transform_registry
|
|
43
|
+
ToTypeTransformRegistry.new(self)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class FromString
|
|
6
|
+
class ToTypeTransformRegistry < BaseToTypeTransformRegistry
|
|
7
|
+
def build_transforms
|
|
8
|
+
{
|
|
9
|
+
Time => ToType::FromStringToTimeSelector.new(from),
|
|
10
|
+
Date => [DateTransform.new],
|
|
11
|
+
BigDecimal => [ToDecimal.new],
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class FromString < FromType
|
|
6
|
+
OVERRIDES = { type: String }.freeze
|
|
7
|
+
CONSTRAINT_BUILDER_CLASS = ConstraintBuilder::ForString
|
|
8
|
+
|
|
9
|
+
def initialize(args = {})
|
|
10
|
+
super(**args, **OVERRIDES)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def where(constraint_or_description = nil, &block)
|
|
14
|
+
if constraint_or_description
|
|
15
|
+
super
|
|
16
|
+
elsif block
|
|
17
|
+
raise ArgumentError, "Must provide a constraint or description"
|
|
18
|
+
else
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
delegate(*CONSTRAINT_BUILDER_CLASS::CONSTRAINT_METHODS, to: :constraint_builder)
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def constraint_builder
|
|
28
|
+
CONSTRAINT_BUILDER_CLASS.new(self)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_type_transform_registry
|
|
32
|
+
ToTypeTransformRegistry.new(self)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
# Base class for transforms where we want to validate that the input is a particular
|
|
6
|
+
# type before operating on it. Additional validations can be chained off by use of the
|
|
7
|
+
# #where method. On success this transform will return the input, unchanged. For a transform
|
|
8
|
+
# that will actually change the input into something else, you can call #to and pass a block
|
|
9
|
+
# which will return a `ToType` transform that will run this transform, and then your block.
|
|
10
|
+
class FromType < Morpher::Transform
|
|
11
|
+
include Alchemrest::Transforms::Constrainable.new(additional_attributes: %i(type))
|
|
12
|
+
include Adamantium::Mutable
|
|
13
|
+
DEFAULTS = { constraints: [] }.freeze
|
|
14
|
+
|
|
15
|
+
def initialize(args)
|
|
16
|
+
super(**DEFAULTS.merge(args))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def output_type
|
|
20
|
+
OutputType.new(sorbet_type: type, constraints:)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def output_type_name
|
|
24
|
+
type.name
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def call(input)
|
|
28
|
+
transform.call(input)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to(type, &block)
|
|
32
|
+
if block.nil?
|
|
33
|
+
to_type_transform_registry.resolve(type)
|
|
34
|
+
else
|
|
35
|
+
ToType.using(to: type, from: self, &block)
|
|
36
|
+
end
|
|
37
|
+
rescue Alchemrest::NoRegisteredTransformError
|
|
38
|
+
raise ArgumentError,
|
|
39
|
+
"No transform registered to transform #{output_type.sorbet_type} to #{type}. Perhaps you should use the block form?"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def array
|
|
43
|
+
Typed.new(transform: super(), output_type: output_type.with(sorbet_type: T::Array[output_type.sorbet_type]))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def maybe
|
|
47
|
+
Typed.new(transform: super(), output_type: output_type.with(sorbet_type: T.nilable(output_type.sorbet_type)))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
memoize def to_type_transform_registry
|
|
53
|
+
EmptyToTypeTransformRegistry.new(self)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def transform
|
|
57
|
+
Sequence.new([
|
|
58
|
+
Primitive.new(type),
|
|
59
|
+
validate_constraints,
|
|
60
|
+
])
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/time'
|
|
4
|
+
|
|
5
|
+
module Alchemrest
|
|
6
|
+
module Transforms
|
|
7
|
+
class IsoTime < Morpher::Transform
|
|
8
|
+
include Anima.new(:to_timezone, :require_offset)
|
|
9
|
+
|
|
10
|
+
def call(input)
|
|
11
|
+
return not_a_valid_iso_string(input) unless input.instance_of?(String)
|
|
12
|
+
return missing_required_offset(input) unless has_offset?(input) || !require_offset
|
|
13
|
+
|
|
14
|
+
begin
|
|
15
|
+
success(parse(input))
|
|
16
|
+
rescue ArgumentError
|
|
17
|
+
not_a_valid_iso_string(input)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def parse(input)
|
|
24
|
+
if to_timezone
|
|
25
|
+
ActiveSupport::TimeZone[to_timezone].iso8601(input)
|
|
26
|
+
else
|
|
27
|
+
Time.iso8601(input)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def has_offset?(input)
|
|
32
|
+
parts = input.split("T")
|
|
33
|
+
time = parts.last
|
|
34
|
+
# checking for an offset
|
|
35
|
+
|
|
36
|
+
/[Z+-]/.match?(time)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def missing_required_offset(input)
|
|
40
|
+
failure(
|
|
41
|
+
error(
|
|
42
|
+
message: "Expected #{input} to have a valid ISO timezone offset",
|
|
43
|
+
input: input,
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def not_a_valid_iso_string(input)
|
|
49
|
+
failure(
|
|
50
|
+
error(
|
|
51
|
+
message: "Expected #{input} to be iso datetime string",
|
|
52
|
+
input: input,
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemrest
|
|
4
|
+
module Transforms
|
|
5
|
+
class JsonNumber < Morpher::Transform
|
|
6
|
+
def call(input)
|
|
7
|
+
if input.instance_of?(Integer) || input.instance_of?(Float)
|
|
8
|
+
success(input)
|
|
9
|
+
else
|
|
10
|
+
failure_error(input)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def failure_error(input)
|
|
17
|
+
failure(
|
|
18
|
+
error(
|
|
19
|
+
message: "Expected: Float, Integer but got #{input.class}",
|
|
20
|
+
input:,
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|