Last active
January 26, 2023 09:40
-
-
Save estum/288a0ae3f2ae2d45093407790124e86b to your computer and use it in GitHub Desktop.
Dry::Types::Tuple #dry-rb #dry-types
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
module Dry | |
module Types | |
# @example | |
# Types::ServiceArgs = Types.Tuple( | |
# Types::Params::Symbol, # --- positional types | |
# [Types::Params::Integer | Types::Coercible::String] # --- [type for the rest items] | |
# ) | |
# Types::ServiceArgs[['thumb', '300', '300', 'sample']] | |
# # => [:thumb, 300, 300, "sample"] | |
class Tuple < Array | |
def initialize(_primitive, types_index:, meta: EMPTY_HASH) | |
super(_primitive, types_index: types_index, meta: meta) | |
end | |
# @return [Hash] | |
# | |
# @api public | |
def types_index | |
options[:types_index] | |
end | |
# @return [Array<Type>] | |
# | |
# @api public | |
def fixed_types | |
options[:types_index].values | |
end | |
# @return [Type] | |
# | |
# @api public | |
def rest_type | |
options[:types_index].default | |
end | |
# @return [String] | |
# | |
# @api public | |
def name | |
"Tuple" | |
end | |
# @param [Array] tuple | |
# | |
# @return [Array] | |
# | |
# @api private | |
def call_unsafe(tuple) | |
try(tuple) { |failure| | |
raise MapError, failure.error.message | |
}.input | |
end | |
# @param [Array] tuple | |
# | |
# @return [Array] | |
# | |
# @api private | |
def call_safe(tuple) | |
try(tuple) { return yield }.input | |
end | |
# @param [Array] tuple | |
# | |
# @return [Result] | |
# | |
# @api public | |
def try(tuple) | |
result = coerce(tuple) | |
return result if result.success? || !block_given? | |
yield(result) | |
end | |
# @param meta [Boolean] Whether to dump the meta to the AST | |
# | |
# @return [Array] An AST representation | |
# | |
# @api public | |
def to_ast(meta: true) | |
structure = [*fixed_types.map { |type| type.to_ast(meta: true) }] | |
structure << [rest_type.to_ast(meta: true)] unless rest_type.nil? | |
structure << meta ? self.meta : EMPTY_HASH | |
[:tuple, structure] | |
end | |
# @return [Boolean] | |
# | |
# @api public | |
def constrained? | |
rest_type&.constrained? || options[:types_index].each_value.any?(&:contrained?) | |
end | |
private | |
# @api private | |
def coerce(input) | |
unless primitive?(input) | |
return failure( | |
input, CoercionError.new("#{input.inspect} must be an instance of #{primitive}") | |
) | |
end | |
output = [] | |
failures = [] | |
input.each_with_index do |value, index| | |
res_i = types_index[index].try(value) | |
if res_i.failure? | |
failures << res_i.error | |
else | |
output << res_i.input | |
end | |
end | |
if failures.empty? | |
success(output) | |
else | |
failure(input, MultipleError.new(failures)) | |
end | |
end | |
end | |
module BuilderMethods | |
# Build a tuple type. | |
# | |
# @overload Tuple(*fixed_types, rest_type) | |
# @param [Array<Dry::Types::Type>] fixed_types | |
# @param [Array(Dry::Types::Type)] rest_type | |
# | |
# @return [Dry::Types::Tuple] | |
def Tuple(*types) | |
rest_type = Undefined | |
if types[-1].is_a?(::Array) | |
if types[-1].size > 1 | |
raise ArgumentError, "rest_type should be an Array with single element to apply to the rest of items" | |
end | |
rest_type = types.pop[0] | |
end | |
types = ::Array.wrap(types) | |
types_index = ::Hash[types.size.times.zip(types)] | |
types_index.default = Undefined.default(rest_type, nil) | |
Tuple.new(::Array, types_index: types_index) | |
end | |
end | |
# @api private | |
class Printer | |
MAPPING[Tuple] = :visit_tuple | |
def visit_tuple(tuple) | |
options = tuple.options.dup | |
size = tuple.fixed_types.size | |
size += 1 unless tuple.rest_type.nil? | |
types = options.delete(:types_index) | |
visit_options(options, tuple.meta) do |opts| | |
header = "Tuple<#{opts}" | |
rest = visit(types.default) { |type| "*: #{type}" } if types.default | |
if size.zero? | |
yield "#{header}>" | |
else | |
yield header.dup << (types.map { |pos, pos_type| | |
visit(pos_type) { |type| "0: #{type}" } | |
} << rest).compact.join(", ") << ">" | |
end | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment