Update YARD docs [ci skip]

This commit is contained in:
Piotr Solnica 2019-04-23 15:55:27 +02:00
parent 1924074c05
commit 09170b68a8
No known key found for this signature in database
GPG Key ID: 66BF2FDA7BA0F29C
36 changed files with 676 additions and 40 deletions

View File

@ -1,8 +1,8 @@
--title 'dry-types'
--query '@api.text != "private"'
--embed-mixins
-o doc
-r README.md
--output doc
--readme README.md
--files CHANGELOG.md
--markup markdown
--markup-provider=redcarpet

View File

@ -24,6 +24,9 @@ require 'dry/types/module'
require 'dry/types/errors'
module Dry
# Main library namespace
#
# @api public
module Types
extend Dry::Core::Extensions
extend Dry::Core::ClassAttributes
@ -45,20 +48,32 @@ module Dry
raise RuntimeError, "Import Dry.Types, not Dry::Types"
end
# Return container with registered built-in type objects
#
# @return [Container{String => Nominal}]
#
# @api private
def self.container
@container ||= Container.new
end
# Check if a give type is registered
#
# @return [Boolean]
#
# @api private
def self.registered?(class_or_identifier)
container.key?(identifier(class_or_identifier))
end
# Register a new built-in type
#
# @param [String] name
# @param [Type] type
# @param [#call,nil] block
#
# @return [Container{String => Nominal}]
#
# @api private
def self.register(name, type = nil, &block)
container.register(name, type || block.call)
@ -67,7 +82,10 @@ module Dry
# Get a built-in type by its name
#
# @param [String,Class] name
#
# @return [Type,Class]
#
# @api public
def self.[](name)
type_map.fetch_or_store(name) do
case name
@ -92,19 +110,29 @@ module Dry
end
end
# Infer a type identifier from the provided class
#
# @param [#to_s] klass
#
# @return [String]
def self.identifier(klass)
Inflector.underscore(klass).tr('/', '.')
end
# Cached type map
#
# @return [Concurrent::Map]
#
# @api private
def self.type_map
@type_map ||= Concurrent::Map.new
end
# List of type keys defined in {Dry::Types.container}
# @return [<String>]
# List of type keys defined in {Dry::Types.container}
#
# @return [String]
#
# @api private
def self.type_keys
container.keys
end
@ -166,12 +194,18 @@ module Dry
# @param [Array<Symbol>] namespaces List of type namespaces to export
# @param [Symbol] default Default namespace to export
# @param [Hash{Symbol => Symbol}] aliases Optional renamings, like strict: :Draconian
#
# @return [Dry::Types::Module]
#
# @see Dry::types::Module
# @see Dry::Types::Module
#
# @api public
#
# rubocop:disable Naming/MethodName
def self.Types(*namespaces, default: Types::Undefined, **aliases)
Types::Module.new(Types.container, *namespaces, default: default, **aliases)
end
# rubocop:enable Naming/MethodName
end
require 'dry/types/core' # load built-in types

View File

@ -2,27 +2,41 @@
module Dry
module Types
# Any is a nominal type that defines Object as the primitive class
#
# This type is useful in places where you can't be specific about the type
# and anything is acceptable.
#
# @api public
class AnyClass < Nominal
def self.name
'Any'
end
# @api private
def initialize(**options)
super(::Object, options)
end
# @return [String]
#
# @api public
def name
'Any'
end
# @param [Hash] new_options
#
# @return [Type]
#
# @api public
def with(new_options)
self.class.new(**options, meta: @meta, **new_options)
end
# @return [Array]
#
# @api public
def to_ast(meta: true)
[:any, meta ? self.meta : EMPTY_HASH]
end

View File

@ -4,9 +4,17 @@ require 'dry/types/array/member'
module Dry
module Types
# Array type can be used to define an array with optional member type
#
# @api public
class Array < Nominal
# @param [Type] type
# Build an array type with a member type
#
# @param [Type,#call] type
#
# @return [Array::Member]
#
# @api public
def of(type)
member =
case type

View File

@ -3,22 +3,29 @@
module Dry
module Types
class Array < Nominal
# Member arrays define their member type that is applied to each element
#
# @api public
class Member < Array
# @return [Type]
attr_reader :member
# @param [Class] primitive
# @param [Hash] options
#
# @option options [Type] :member
#
# @api private
def initialize(primitive, options = {})
@member = options.fetch(:member)
super
end
# @api private
#
# @param [Object] input
#
# @return [Array]
#
# @api private
def call_unsafe(input)
if primitive?(input)
input.each_with_object([]) do |el, output|
@ -31,10 +38,10 @@ module Dry
end
end
# @api private
#
# @param [Object] input
# @return [Array]
#
# @api private
def call_safe(input)
if primitive?(input)
failed = false
@ -56,9 +63,13 @@ module Dry
# @param [Array, Object] input
# @param [#call,nil] block
#
# @yieldparam [Failure] failure
# @yieldreturn [Result]
#
# @return [Result,Logic::Result]
#
# @api public
def try(input, &block)
if primitive?(input)
output = []
@ -84,11 +95,15 @@ module Dry
# Build a lax type
#
# @return [Lax]
#
# @api public
def lax
Lax.new(Member.new(primitive, { **options, member: member.lax }))
end
# @see Nominal#to_ast
#
# @api public
def to_ast(meta: true)
if member.respond_to?(:to_ast)
[:array, [member.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]

View File

@ -4,42 +4,69 @@ require 'dry/core/deprecations'
module Dry
module Types
# Common API for building types and composition
#
# @api public
module Builder
include Dry::Core::Constants
# @return [Class]
#
# @api private
def constrained_type
Constrained
end
# @return [Class]
#
# @api private
def constructor_type
Constructor
end
# Compose two types into a Sum type
#
# @param [Type] other
#
# @return [Sum, Sum::Constrained]
#
# @api private
def |(other)
klass = constrained? && other.constrained? ? Sum::Constrained : Sum
klass.new(self, other)
end
# Turn a type into an optional type
#
# @return [Sum]
#
# @api public
def optional
Types['strict.nil'] | self
end
# Turn a type into a constrained type
#
# @param [Hash] options constraining rule (see {Types.Rule})
#
# @return [Constrained]
#
# @api public
def constrained(options)
constrained_type.new(self, rule: Types.Rule(options))
end
# Turn a type into a type with a default value
#
# @param [Object] input
# @param [Hash] options
# @param [#call,nil] block
#
# @raise [ConstraintError]
#
# @return [Default]
#
# @api public
def default(input = Undefined, options = EMPTY_HASH, &block)
unless input.frozen? || options[:shared]
where = Dry::Core::Deprecations::STACK.()
@ -62,8 +89,13 @@ module Dry
end
end
# Define an enum on top of the existing type
#
# @param [Array] values
#
# @return [Enum]
#
# @api public
def enum(*values)
mapping =
if values.length == 1 && values[0].is_a?(::Hash)
@ -75,15 +107,25 @@ module Dry
Enum.new(constrained(included_in: mapping.keys), mapping: mapping)
end
# Turn a type into a lax type that will rescue from type-errors and
# return the original input
#
# @return [Lax]
#
# @api public
def lax
Lax.new(self)
end
# Define a constructor for the type
#
# @param [#call,nil] constructor
# @param [Hash] options
# @param [#call,nil] block
#
# @return [Constructor]
#
# @api public
def constructor(constructor = nil, **options, &block)
constructor_type.new(with(options), fn: constructor || block)
end

View File

@ -2,6 +2,11 @@
module Dry
module Types
# Common API for building type objects in a convenient way
#
# rubocop:disable Naming/MethodName
#
# @api public
module BuilderMethods
# @api private
def included(base)
@ -10,7 +15,8 @@ module Dry
end
# Build an array type.
# It is a shortcut for Array.of
#
# Shortcut for Array#of.
#
# @example
# Types::Strings = Types.Array(Types::String)

View File

@ -2,12 +2,19 @@
module Dry
module Types
# Common coercion functions used by the built-in `Params` and `JSON` types
#
# @api public
module Coercions
include Dry::Core::Constants
# @param [String, Object] input
# @return [nil] if the input is an empty string
# @return [Object] otherwise the input object is returned
#
# @return [nil] if the input is an empty string or nil
#
# @raise CoercionError
#
# @api public
def to_nil(input, &_block)
if input.nil? || empty_str?(input)
nil
@ -19,8 +26,12 @@ module Dry
end
# @param [#to_str, Object] input
#
# @return [Date, Object]
#
# @see Date.parse
#
# @api public
def to_date(input, &block)
if input.respond_to?(:to_str)
begin
@ -36,8 +47,12 @@ module Dry
end
# @param [#to_str, Object] input
#
# @return [DateTime, Object]
#
# @see DateTime.parse
#
# @api public
def to_date_time(input, &block)
if input.respond_to?(:to_str)
begin
@ -53,8 +68,12 @@ module Dry
end
# @param [#to_str, Object] input
#
# @return [Time, Object]
#
# @see Time.parse
#
# @api public
def to_time(input, &block)
if input.respond_to?(:to_str)
begin
@ -72,8 +91,12 @@ module Dry
private
# Checks whether String is empty
#
# @param [String, Object] value
#
# @return [Boolean]
#
# @api private
def empty_str?(value)
EMPTY_STRING.eql?(value)
end

View File

@ -8,11 +8,19 @@ require 'time'
module Dry
module Types
module Coercions
# JSON-specific coercions
#
# @api public
module JSON
extend Coercions
# @param [#to_d, Object] input
#
# @return [BigDecimal,nil]
#
# @raise CoercionError
#
# @api public
def self.to_decimal(input, &block)
if input.is_a?(::Float)
input.to_d

View File

@ -6,6 +6,9 @@ require 'bigdecimal/util'
module Dry
module Types
module Coercions
# Params-specific coercions
#
# @api public
module Params
TRUE_VALUES = %w[1 on On ON t true True TRUE T y yes Yes YES Y].freeze
FALSE_VALUES = %w[0 off Off OFF f false False FALSE F n no No NO N].freeze
@ -16,9 +19,15 @@ module Dry
extend Coercions
# @param [String, Object] input
#
# @return [Boolean,Object]
#
# @see TRUE_VALUES
# @see FALSE_VALUES
#
# @raise CoercionError
#
# @api public
def self.to_true(input, &_block)
BOOLEAN_MAP.fetch(input.to_s) do
if block_given?
@ -30,9 +39,15 @@ module Dry
end
# @param [String, Object] input
#
# @return [Boolean,Object]
#
# @see TRUE_VALUES
# @see FALSE_VALUES
#
# @raise CoercionError
#
# @api public
def self.to_false(input, &_block)
BOOLEAN_MAP.fetch(input.to_s) do
if block_given?
@ -44,7 +59,12 @@ module Dry
end
# @param [#to_int, #to_i, Object] input
#
# @return [Integer, nil, Object]
#
# @raise CoercionError
#
# @api public
def self.to_int(input, &block)
if input.is_a? String
Integer(input, 10)
@ -56,7 +76,12 @@ module Dry
end
# @param [#to_f, Object] input
#
# @return [Float, nil, Object]
#
# @raise CoercionError
#
# @api public
def self.to_float(input, &block)
Float(input)
rescue ArgumentError, TypeError => error
@ -64,7 +89,12 @@ module Dry
end
# @param [#to_d, Object] input
#
# @return [BigDecimal, nil, Object]
#
# @raise CoercionError
#
# @api public
def self.to_decimal(input, &block)
to_float(input) do
if block_given?
@ -78,7 +108,12 @@ module Dry
end
# @param [Array, String, Object] input
#
# @return [Array, Object]
#
# @raise CoercionError
#
# @api public
def self.to_ary(input, &_block)
if empty_str?(input)
[]
@ -92,7 +127,12 @@ module Dry
end
# @param [Hash, String, Object] input
#
# @return [Hash, Object]
#
# @raise CoercionError
#
# @api public
def self.to_hash(input, &_block)
if empty_str?(input)
{}

View File

@ -6,6 +6,9 @@ require 'dry/types/constrained/coercible'
module Dry
module Types
# Constrained types apply rules to the input
#
# @api public
class Constrained
include Type
include Decorator
@ -17,14 +20,20 @@ module Dry
attr_reader :rule
# @param [Type] type
#
# @param [Hash] options
#
# @api public
def initialize(type, options)
super
@rule = options.fetch(:rule)
end
# @api private
#
# @return [Object]
#
# @api public
def call_unsafe(input)
result = rule.(input)
@ -36,7 +45,10 @@ module Dry
end
# @api private
#
# @return [Object]
#
# @api public
def call_safe(input, &block)
if rule[input]
type.call_safe(input, &block)
@ -59,6 +71,7 @@ module Dry
# @yieldreturn [Object]
# @return [Object]
#
# @api public
def try(input, &block)
result = rule.(input)
@ -73,19 +86,28 @@ module Dry
# @param [Hash] options
# The options hash provided to {Types.Rule} and combined
# using {&} with previous {#rule}
#
# @return [Constrained]
#
# @see Dry::Logic::Operators#and
#
# @api public
def constrained(options)
with(rule: rule & Types.Rule(options))
end
# @return [true]
#
# @api public
def constrained?
true
end
# @param [Object] value
#
# @return [Boolean]
#
# @api public
def ===(value)
valid?(value)
end
@ -93,11 +115,13 @@ module Dry
# Build lax type. Constraints are not applicable to lax types hence unwrapping
#
# @return [Lax]
# @api public
def lax
type.lax
end
# @see Nominal#to_ast
# @api public
def to_ast(meta: true)
[:constrained, [type.to_ast(meta: meta), rule.to_ast]]
end
@ -105,7 +129,10 @@ module Dry
private
# @param [Object] response
#
# @return [Boolean]
#
# @api private
def decorate?(response)
super || response.is_a?(Constructor)
end

View File

@ -3,9 +3,13 @@
module Dry
module Types
class Constrained
# Common coercion-related API for constrained types
#
# @api public
class Coercible < Constrained
# @api private
# @return [Object]
#
# @api private
def call_unsafe(input)
coerced = type.call_unsafe(input)
result = rule.(coerced)
@ -17,8 +21,9 @@ module Dry
end
end
# @api private
# @return [Object]
#
# @api private
def call_safe(input)
coerced = type.call_safe(input) { return yield }
@ -30,6 +35,8 @@ module Dry
end
# @see Dry::Types::Constrained#try
#
# @api public
def try(input, &block)
result = type.try(input)

View File

@ -5,9 +5,15 @@ require 'dry/logic/predicates'
require 'dry/logic/rule/predicate'
module Dry
# Helper methods for constraint types
#
# @api public
module Types
# @param [Hash] options
#
# @return [Dry::Logic::Rule]
#
# @api public
def self.Rule(options)
rule_compiler.(
options.map { |key, val|
@ -19,6 +25,8 @@ module Dry
end
# @return [Dry::Logic::RuleCompiler]
#
# @api private
def self.rule_compiler
@rule_compiler ||= Logic::RuleCompiler.new(Logic::Predicates)
end

View File

@ -5,6 +5,10 @@ require 'dry/types/constructor/function'
module Dry
module Types
# Constructor types apply a function to the input that is supposed to return
# a new value. Coercion is a common use case for constructor types.
#
# @api public
class Constructor < Nominal
include Dry::Equalizer(:type, :options, inspect: false)
@ -21,14 +25,20 @@ module Dry
# @param [Builder, Object] input
# @param [Hash] options
# @param [#call, nil] block
#
# @api public
def self.new(input, **options, &block)
type = input.is_a?(Builder) ? input : Nominal.new(input)
super(type, **options, fn: Function[options.fetch(:fn, block)])
end
# Instantiate a new constructor type instance
#
# @param [Type] type
# @param [Function] fn
# @param [Hash] options
#
# @api private
def initialize(type, fn: nil, **options)
@type = type
@fn = fn
@ -36,38 +46,53 @@ module Dry
super(type, **options, fn: fn)
end
# Return the inner type's primitive
#
# @return [Class]
#
# @api public
def primitive
type.primitive
end
# Return the inner type's name
#
# @return [String]
#
# @api public
def name
type.name
end
# @return [Boolean]
#
# @api public
def default?
type.default?
end
# @api private
# @return [Object]
#
# @api private
def call_safe(input)
coerced = fn.(input) { return yield }
type.call_safe(coerced) { |output = coerced| yield(output) }
end
# @api private
# @return [Object]
#
# @api private
def call_unsafe(input)
type.call_unsafe(fn.(input))
end
# @param [Object] input
# @param [#call,nil] block
#
# @return [Logic::Result, Types::Result]
# @return [Object] if block given and try fails
#
# @api public
def try(input, &block)
value = fn.(input)
rescue CoercionError => error
@ -82,7 +107,10 @@ module Dry
# @param [#call, nil] new_fn
# @param [Hash] options
# @param [#call, nil] block
#
# @return [Constructor]
#
# @api public
def constructor(new_fn = nil, **options, &block)
with({**options, fn: fn >> (new_fn || block)})
end
@ -90,11 +118,15 @@ module Dry
alias_method :>>, :constructor
# @return [Class]
#
# @api private
def constrained_type
Constrained::Coercible
end
# @see Nominal#to_ast
#
# @api public
def to_ast(meta: true)
[:constructor, [type.to_ast(meta: meta), fn.to_ast]]
end
@ -104,7 +136,10 @@ module Dry
# @param [#call, nil] new_fn
# @param [Hash] options
# @param [#call, nil] block
#
# @return [Constructor]
#
# @api public
def prepend(new_fn = nil, **options, &block)
with({**options, fn: fn << (new_fn || block)})
end
@ -113,6 +148,7 @@ module Dry
# Build a lax type
#
# @return [Lax]
# @api public
def lax
Lax.new(Constructor.new(type.lax, options))
end
@ -120,28 +156,30 @@ module Dry
# Wrap the type with a proc
#
# @return [Proc]
#
# @api public
def to_proc
proc { |value| self.(value) }
end
private
# @api private
#
# @param [Symbol] meth
# @param [Boolean] include_private
# @return [Boolean]
#
# @api private
def respond_to_missing?(meth, include_private = false)
super || type.respond_to?(meth)
end
# Delegates missing methods to {#type}
#
# @api private
#
# @param [Symbol] method
# @param [Array] args
# @param [#call, nil] block
#
# @api private
def method_missing(method, *args, &block)
if type.respond_to?(method)
response = type.__send__(method, *args, &block)

View File

@ -6,9 +6,10 @@ require 'concurrent/map'
module Dry
module Types
class Constructor < Nominal
# Function is used internally by Constructor types
#
# @api private
class Function
# Wrapper for unsafe coercion functions
#
# @api private

View File

@ -4,6 +4,9 @@ require 'dry/container'
module Dry
module Types
# Internal container for the built-in types
#
# @api private
class Container
include Dry::Container::Mixin
end

View File

@ -4,6 +4,9 @@ require 'dry/types/options'
module Dry
module Types
# Common API for types
#
# @api public
module Decorator
include Options
@ -18,30 +21,42 @@ module Dry
# @param [Object] input
# @param [#call, nil] block
#
# @return [Result,Logic::Result]
# @return [Object] if block given and try fails
#
# @api public
def try(input, &block)
type.try(input, &block)
end
# @return [Boolean]
#
# @api public
def default?
type.default?
end
# @return [Boolean]
#
# @api public
def constrained?
type.constrained?
end
# @return [Sum]
#
# @api public
def optional
Types['strict.nil'] | self
end
# @param [Symbol] meth
# @param [Boolean] include_private
#
# @return [Boolean]
#
# @api public
def respond_to_missing?(meth, include_private = false)
super || type.respond_to?(meth)
end
@ -49,6 +64,8 @@ module Dry
# Wrap the type with a proc
#
# @return [Proc]
#
# @api public
def to_proc
proc { |value| self.(value) }
end
@ -56,15 +73,21 @@ module Dry
private
# @param [Object] response
#
# @return [Boolean]
#
# @api private
def decorate?(response)
response.is_a?(type.class)
end
# Delegates missing methods to {#type}
#
# @param [Symbol] meth
# @param [Array] args
# @param [#call, nil] block
#
# @api private
def method_missing(meth, *args, &block)
if type.respond_to?(meth)
response = type.__send__(meth, *args, &block)
@ -80,6 +103,8 @@ module Dry
end
# Replace underlying type
#
# @api private
def __new__(type)
self.class.new(type, *@__args__[1..-1], **@options)
end

View File

@ -4,7 +4,11 @@ require 'dry/types/decorator'
module Dry
module Types
# Default types are useful when a missing value should be replaced by a default one
#
# @api public
class Default
# @api private
class Callable < Default
include Dry::Equalizer(:type, inspect: false)
@ -27,7 +31,10 @@ module Dry
alias_method :evaluate, :value
# @param [Object, #call] value
#
# @return [Class] {Default} or {Default::Callable}
#
# @api private
def self.[](value)
if value.respond_to?(:call)
Callable
@ -38,37 +45,52 @@ module Dry
# @param [Type] type
# @param [Object] value
#
# @api private
def initialize(type, value, **options)
super
@value = value
end
# Build a constrained type
#
# @param [Array] args see {Dry::Types::Builder#constrained}
#
# @return [Default]
#
# @api public
def constrained(*args)
type.constrained(*args).default(value)
end
# @return [true]
#
# @api public
def default?
true
end
# @param [Object] input
#
# @return [Result::Success]
#
# @api public
def try(input)
success(call(input))
end
# @return [Boolean]
#
# @api public
def valid?(value = Undefined)
Undefined.equal?(value) || super
end
# @api private
#
# @param [Object] input
#
# @return [Object] value passed through {#type} or {#default} value
#
# @api private
def call_unsafe(input = Undefined)
if input.equal?(Undefined)
evaluate
@ -77,10 +99,11 @@ module Dry
end
end
# @api pribate
#
# @param [Object] input
#
# @return [Object] value passed through {#type} or {#default} value
#
# @api private
def call_safe(input = Undefined, &block)
if input.equal?(Undefined)
evaluate

View File

@ -4,6 +4,9 @@ require 'dry/types/decorator'
module Dry
module Types
# Enum types can be used to define an enum on top of an existing type
#
# @api public
class Enum
include Type
include Dry::Equalizer(:type, :mapping, inspect: false)
@ -21,6 +24,8 @@ module Dry
# @param [Type] type
# @param [Hash] options
# @option options [Array] :values
#
# @api private
def initialize(type, options)
super
@mapping = options.fetch(:mapping).freeze
@ -29,23 +34,28 @@ module Dry
freeze
end
# @api private
# @return [Object]
#
# @api private
def call_unsafe(input)
type.call_unsafe(map_value(input))
end
# @api private
# @return [Object]
#
# @api private
def call_safe(input, &block)
type.call_safe(map_value(input), &block)
end
# @see Dry::Types::Constrained#try
#
# @api public
def try(input)
super(map_value(input))
end
# @api private
def default(*)
raise '.enum(*values).default(value) is not supported. Call '\
'.default(value).enum(*values) instead'
@ -55,11 +65,15 @@ module Dry
alias_method :include?, :valid?
# @see Nominal#to_ast
#
# @api public
def to_ast(meta: true)
[:enum, [type.to_ast(meta: meta), mapping]]
end
# @return [String]
#
# @api public
def to_s
PRINTER.(self)
end
@ -69,10 +83,11 @@ module Dry
# Maps a value
#
# @api private
#
# @param [Object] input
#
# @return [Object]
#
# @api private
def map_value(input)
if input.equal?(Undefined)
type.call

View File

@ -5,6 +5,9 @@ require 'dry/types/decorator'
module Dry
module Types
# Maybe extension provides Maybe types where values are wrapped using `Either` monad
#
# @api public
class Maybe
include Type
include Dry::Equalizer(:type, :options, inspect: false)
@ -13,10 +16,11 @@ module Dry
include Printable
include Dry::Monads::Maybe::Mixin
# @api private
#
# @param [Dry::Monads::Maybe, Object] input
#
# @return [Dry::Monads::Maybe]
#
# @api private
def call_unsafe(input = Undefined)
case input
when Dry::Monads::Maybe
@ -28,10 +32,11 @@ module Dry
end
end
# @api private
#
# @param [Dry::Monads::Maybe, Object] input
#
# @return [Dry::Monads::Maybe]
#
# @api private
def call_safe(input = Undefined, &block)
case input
when Dry::Monads::Maybe
@ -44,7 +49,10 @@ module Dry
end
# @param [Object] input
#
# @return [Result::Success]
#
# @api public
def try(input = Undefined)
res = if input.equal?(Undefined)
None()
@ -56,13 +64,19 @@ module Dry
end
# @return [true]
#
# @api public
def default?
true
end
# @param [Object] value
#
# @see Dry::Types::Builder#default
#
# @raise [ArgumentError] if nil provided as default value
#
# @api public
def default(value)
if value.nil?
raise ArgumentError, "nil cannot be used as a default of a maybe type"
@ -73,15 +87,21 @@ module Dry
end
module Builder
# Turn a type into a maybe type
#
# @return [Maybe]
#
# @api public
def maybe
Maybe.new(Types['strict.nil'] | self)
end
end
# @api private
class Printer
MAPPING[Maybe] = :visit_maybe
# @api private
def visit_maybe(maybe)
visit(maybe.type) do |type|
yield "Maybe<#{type}>"

View File

@ -4,6 +4,9 @@ require 'dry/types/container'
module Dry
module Types
# Internal container for constructor functions used by the built-in types
#
# @api private
class FnContainer
# @api private
def self.container

View File

@ -4,10 +4,13 @@ require 'dry/types/hash/constructor'
module Dry
module Types
# Hash types can be used to define maps and schemas
#
# @api public
class Hash < Nominal
NOT_REQUIRED = { required: false }.freeze
# @overload schmea(type_map, meta = EMPTY_HASH)
# @overload schema(type_map, meta = EMPTY_HASH)
# @param [{Symbol => Dry::Types::Nominal}] type_map
# @param [Hash] meta
# @return [Dry::Types::Schema]
@ -16,6 +19,8 @@ module Dry
# @param [Array<Dry::Types::Schema::Key>] key List of schema keys
# @param [Hash] meta
# @return [Dry::Types::Schema]
#
# @api public
def schema(keys_or_map, meta = EMPTY_HASH)
if keys_or_map.is_a?(::Array)
keys = keys_or_map
@ -30,7 +35,10 @@ module Dry
#
# @param [Type] key_type
# @param [Type] value_type
#
# @return [Map]
#
# @api public
def map(key_type, value_type)
Map.new(
primitive,
@ -51,9 +59,13 @@ module Dry
alias_method :symbolized, :weak
# Injects a type transformation function for building schemas
#
# @param [#call,nil] proc
# @param [#call,nil] block
#
# @return [Hash]
#
# @api public
def with_type_transform(proc = nil, &block)
fn = proc || block
@ -73,12 +85,17 @@ module Dry
# Whether the type transforms types of schemas created by {Dry::Types::Hash#schema}
#
# @return [Boolean]
#
# @api public
def transform_types?
!options[:type_transform_fn].nil?
end
# @param meta [Boolean] Whether to dump the meta to the AST
#
# @return [Array] An AST representation
#
# @api public
def to_ast(meta: true)
if RUBY_VERSION >= "2.5"
opts = options.slice(:type_transform_fn)

View File

@ -4,6 +4,9 @@ require 'dry/types/constructor'
module Dry
module Types
# Hash type exposes additional APIs for working with schema hashes
#
# @api public
class Hash < Nominal
class Constructor < ::Dry::Types::Constructor
# @api private
@ -12,6 +15,8 @@ module Dry
end
# @return [Lax]
#
# @api public
def lax
type.lax.constructor(fn, meta: meta)
end

View File

@ -5,6 +5,9 @@ require 'dry/types/decorator'
module Dry
module Types
# Lax types rescue from type-related errors when constructors fail
#
# @api public
class Lax
include Type
include Decorator
@ -15,7 +18,10 @@ module Dry
private :options, :constructor
# @param [Object] input
#
# @return [Object]
#
# @api public
def call(input)
type.call_safe(input) { |output = input| output }
end
@ -25,9 +31,13 @@ module Dry
# @param [Object] input
# @param [#call,nil] block
#
# @yieldparam [Failure] failure
# @yieldreturn [Result]
#
# @return [Result,Logic::Result]
#
# @api public
def try(input, &block)
type.try(input, &block)
rescue CoercionError => error
@ -36,11 +46,15 @@ module Dry
end
# @see Nominal#to_ast
#
# @api public
def to_ast(meta: true)
[:lax, type.to_ast(meta: meta)]
end
# @return [Lax]
#
# @api public
def lax
self
end
@ -48,7 +62,10 @@ module Dry
private
# @param [Object, Dry::Types::Constructor] response
#
# @return [Boolean]
#
# @api private
def decorate?(response)
super || response.is_a?(constructor_type)
end

View File

@ -9,34 +9,48 @@ module Dry
# Dry::Types['integer'].constrained(gteq: 1, lteq: 10),
# Dry::Types['string']
# )
#
# type.(1 => 'right')
# # => {1 => 'right'}
#
# type.('1' => 'wrong')
# # Dry::Types::MapError: "1" violates constraints (type?(Integer, "1") AND gteq?(1, "1") AND lteq?(10, "1") failed)
#
# type.(11 => 'wrong')
# # Dry::Types::MapError: 11 violates constraints (lteq?(10, 11) failed)
#
# @api public
class Map < Nominal
def initialize(_primitive, key_type: Types['any'], value_type: Types['any'], meta: EMPTY_HASH)
super(_primitive, key_type: key_type, value_type: value_type, meta: meta)
end
# @return [Type]
#
# @api public
def key_type
options[:key_type]
end
# @return [Type]
#
# @api public
def value_type
options[:value_type]
end
# @return [String]
#
# @api public
def name
'Map'
end
# @param [Hash] hash
#
# @return [Hash]
#
# @api private
def call_unsafe(hash)
try(hash) { |failure|
raise MapError, failure.error.message
@ -44,13 +58,19 @@ module Dry
end
# @param [Hash] hash
#
# @return [Hash]
#
# @api private
def call_safe(hash)
try(hash) { return yield }.input
end
# @param [Hash] hash
#
# @return [Result]
#
# @api public
def try(hash)
result = coerce(hash)
return result if result.success? || !block_given?
@ -58,7 +78,10 @@ module Dry
end
# @param meta [Boolean] Whether to dump the meta to the AST
#
# @return [Array] An AST representation
#
# @api public
def to_ast(meta: true)
[:map,
[key_type.to_ast(meta: true),
@ -67,6 +90,8 @@ module Dry
end
# @return [Boolean]
#
# @api public
def constrained?
value_type.constrained?
end

View File

@ -2,6 +2,9 @@
module Dry
module Types
# Storage for meta-data
#
# @api public
module Meta
def initialize(*args, meta: EMPTY_HASH, **options)
super(*args, **options)
@ -9,7 +12,10 @@ module Dry
end
# @param [Hash] new_options
#
# @return [Type]
#
# @api public
def with(options)
super(meta: @meta, **options)
end
@ -20,6 +26,8 @@ module Dry
# @overload meta(data)
# @param [Hash] new metadata to merge into existing metadata
# @return [Type] new type with added metadata
#
# @api public
def meta(data = nil)
if !data
@meta
@ -31,7 +39,10 @@ module Dry
end
# Resets meta
#
# @return [Dry::Types::Type]
#
# @api public
def pristine
with(meta: EMPTY_HASH)
end

View File

@ -10,10 +10,13 @@ module Dry
# module Types
# include Dry::Types(:strict, :coercible, :nominal, default: :strict)
# end
# # Types.constants
#
# Types.constants
# # => [:Class, :Strict, :Symbol, :Integer, :Float, :String, :Array, :Hash,
# # :Decimal, :Nil, :True, :False, :Bool, :Date, :Nominal, :DateTime, :Range,
# # :Coercible, :Time]
#
# @api public
class Module < ::Module
def initialize(registry, *args)
@registry = registry

View File

@ -8,6 +8,11 @@ require 'dry/types/meta'
module Dry
module Types
# Nominal types define a primitive class and do not apply any constructors or constraints
#
# Use these types for annotations and the base for building more complex types on top of them.
#
# @api public
class Nominal
include Type
include Options
@ -20,7 +25,10 @@ module Dry
attr_reader :primitive
# @param [Class] primitive
#
# @return [Type]
#
# @api private
def self.[](primitive)
if primitive == ::Array
Types::Array
@ -35,6 +43,8 @@ module Dry
# @param [Type,Class] primitive
# @param [Hash] options
#
# @api private
def initialize(primitive, **options)
super
@primitive = primitive
@ -42,55 +52,79 @@ module Dry
end
# @return [String]
#
# @api public
def name
primitive.name
end
# @return [false]
#
# @api public
def default?
false
end
# @return [false]
#
# @api public
def constrained?
false
end
# @return [false]
#
# @api public
def optional?
false
end
# @param [BasicObject] input
#
# @return [BasicObject]
#
# @api private
def call_unsafe(input)
input
end
# @param [BasicObject] input
#
# @return [BasicObject]
#
# @api private
def call_safe(input)
input
end
# @param [Object] input
# @param [#call,nil] block
#
# @yieldparam [Failure] failure
# @yieldreturn [Result]
#
# @return [Result,Logic::Result] when a block is not provided
# @return [nil] otherwise
#
# @api public
def try(input)
success(input)
end
# @param (see Dry::Types::Success#initialize)
#
# @return [Result::Success]
#
# @api public
def success(input)
Result::Success.new(input)
end
# @param (see Failure#initialize)
#
# @return [Result::Failure]
#
# @api public
def failure(input, error)
unless error.is_a?(CoercionError)
raise ArgumentError, "error must be a CoercionError"
@ -99,12 +133,17 @@ module Dry
end
# Checks whether value is of a #primitive class
#
# @param [Object] value
#
# @return [Boolean]
#
# @api public
def primitive?(value)
value.is_a?(primitive)
end
# @api private
def coerce(input, &_block)
if primitive?(input)
input
@ -115,6 +154,7 @@ module Dry
end
end
# @api private
def try_coerce(input)
result = success(input)
@ -135,6 +175,8 @@ module Dry
# Return AST representation of a type nominal
#
# @return [Array]
#
# @api public
def to_ast(meta: true)
[:nominal, [primitive, meta ? self.meta : EMPTY_HASH]]
end
@ -142,6 +184,8 @@ module Dry
# Return self. Nominal types are lax by definition
#
# @return [Nominal]
#
# @api public
def lax
self
end
@ -149,6 +193,8 @@ module Dry
# Wrap the type with a proc
#
# @return [Proc]
#
# @api public
def to_proc
ALWAYS
end

View File

@ -2,18 +2,26 @@
module Dry
module Types
# Common API for types with options
#
# @api private
module Options
# @return [Hash]
attr_reader :options
# @see Nominal#initialize
#
# @api private
def initialize(*args, **options)
@__args__ = args.freeze
@options = options.freeze
end
# @param [Hash] new_options
#
# @return [Type]
#
# @api private
def with(**new_options)
self.class.new(*@__args__, **options, **new_options)
end

View File

@ -2,8 +2,11 @@
module Dry
module Types
# @api private
module Printable
# @return [String]
#
# @api private
def to_s
PRINTER.(self) { super }
end

View File

@ -4,6 +4,9 @@ require 'dry/equalizer'
module Dry
module Types
# Result class used by {Type#try}
#
# @api public
class Result
include Dry::Equalizer(:input, inspect: false)
@ -11,22 +14,34 @@ module Dry
attr_reader :input
# @param [Object] input
#
# @api private
def initialize(input)
@input = input
end
# Success result
#
# @api public
class Success < Result
# @return [true]
#
# @api public
def success?
true
end
# @return [false]
#
# @api public
def failure?
false
end
end
# Failure result
#
# @api public
class Failure < Result
include Dry::Equalizer(:input, :error, inspect: false)
@ -34,23 +49,32 @@ module Dry
attr_reader :error
# @param [Object] input
#
# @param [#to_s] error
#
# @api private
def initialize(input, error)
super(input)
@error = error
end
# @return [String]
#
# @api private
def to_s
error.to_s
end
# @return [false]
#
# @api public
def success?
false
end
# @return [true]
#
# @api public
def failure?
true
end

View File

@ -16,6 +16,8 @@ module Dry
# @see Dry::Types::Default::Callable#evaluate
#
# {Schema} implements Enumerable using its keys as collection.
#
# @api public
class Schema < Hash
NO_TRANSFORM = Dry::Types::FnContainer.register { |x| x }
SYMBOLIZE_KEY = Dry::Types::FnContainer.register(:to_sym.to_proc)
@ -33,8 +35,11 @@ module Dry
# @param [Class] _primitive
# @param [Hash] options
#
# @option options [Array[Dry::Types::Schema::Key]] :keys
# @option options [String] :key_transform_fn
#
# @api private
def initialize(_primitive, **options)
@keys = options.fetch(:keys)
@name_key_map = keys.each_with_object({}) do |key, idx|
@ -49,31 +54,44 @@ module Dry
end
# @param [Hash] hash
#
# @return [Hash{Symbol => Object}]
#
# @api private
def call_unsafe(hash, options = EMPTY_HASH)
resolve_unsafe(coerce(hash), options)
end
# @param [Hash] hash
#
# @return [Hash{Symbol => Object}]
#
# @api private
def call_safe(hash, options = EMPTY_HASH)
resolve_safe(coerce(hash) { return yield }, options) { return yield }
end
# @param [Hash] hash
#
# @option options [Boolean] :skip_missing If true don't raise error if on missing keys
# @option options [Boolean] :resolve_defaults If false default value
# won't be evaluated for missing key
# @return [Hash{Symbol => Object}]
#
# @api public
def apply(hash, options = EMPTY_HASH)
call_unsafe(hash, options)
end
# @param [Hash] hash
#
# @yieldparam [Failure] failure
# @yieldreturn [Result]
#
# @return [Logic::Result]
# @return [Object] if coercion fails and a block is given
#
# @api public
def try(input)
if primitive?(input)
success = true
@ -122,7 +140,10 @@ module Dry
end
# @param meta [Boolean] Whether to dump the meta to the AST
#
# @return [Array] An AST representation
#
# @api public
def to_ast(meta: true)
if RUBY_VERSION >= "2.5"
opts = options.slice(:key_transform_fn, :type_transform_fn, :strict)
@ -141,21 +162,31 @@ module Dry
end
# Whether the schema rejects unknown keys
#
# @return [Boolean]
#
# @api public
def strict?
options.fetch(:strict, false)
end
# Make the schema intolerant to unknown keys
#
# @return [Schema]
#
# @api public
def strict
with(strict: true)
end
# Injects a key transformation function
#
# @param [#call,nil] proc
# @param [#call,nil] block
#
# @return [Schema]
#
# @api public
def with_key_transform(proc = nil, &block)
fn = proc || block
@ -170,6 +201,8 @@ module Dry
# Whether the schema transforms input keys
#
# @return [Boolean]
#
# @api public
def trasform_keys?
!options[:key_transform_fn].nil?
end
@ -178,10 +211,13 @@ module Dry
# @param [{Symbol => Dry::Types::Nominal}] type_map
# @param [Hash] meta
# @return [Dry::Types::Schema]
#
# @overload schema(keys)
# @param [Array<Dry::Types::Schema::Key>] key List of schema keys
# @param [Hash] meta
# @return [Dry::Types::Schema]
#
# @api public
def schema(keys_or_map)
if keys_or_map.is_a?(::Array)
new_keys = keys_or_map
@ -196,6 +232,8 @@ module Dry
# Iterate over each key type
#
# @return [Array<Dry::Types::Schema::Key>,Enumerator]
#
# @api public
def each(&block)
keys.each(&block)
end
@ -203,12 +241,16 @@ module Dry
# Whether the schema has the given key
#
# @param [Symbol] name Key name
#
# @return [Boolean]
#
# @api public
def key?(name)
name_key_map.key?(name)
end
# Fetch key type by a key name.
# Fetch key type by a key name
#
# Behaves as ::Hash#fetch
#
# @overload key(name, fallback = Undefined)
@ -220,6 +262,8 @@ module Dry
# @param [Symbol] name Key name
# @param [Proc] block Fallback block, runs if key is missing
# @return [Dry::Types::Schema::Key,Object] key type or block value if key is not in schema
#
# @api public
def key(name, fallback = Undefined, &block)
if Undefined.equal?(fallback)
name_key_map.fetch(name, &block)
@ -229,11 +273,15 @@ module Dry
end
# @return [Boolean]
#
# @api public
def constrained?
true
end
# @return [Lax]
#
# @api public
def lax
Lax.new(schema(keys.map(&:lax)))
end
@ -242,9 +290,9 @@ module Dry
# @param [Array<Dry::Types::Schema::Keys>] keys
#
# @api private
#
# @return [Dry::Types::Schema]
#
# @api private
def merge_keys(*keys)
keys.
flatten(1).
@ -311,6 +359,8 @@ module Dry
end
# Try to add missing keys to the hash
#
# @api private
def resolve_missing_keys(hash, options)
skip_missing = options.fetch(:skip_missing, false)
resolve_defaults = options.fetch(:resolve_defaults, true)
@ -331,13 +381,18 @@ module Dry
end
# @param hash_keys [Array<Symbol>]
#
# @return [UnknownKeysError]
#
# @api private
def unexpected_keys(hash_keys)
extra_keys = hash_keys.map(&transform_key) - name_key_map.keys
UnknownKeysError.new(extra_keys)
end
# @return [MissingKeyError]
#
# @api private
def missing_key(key)
MissingKeyError.new(key)
end

View File

@ -4,6 +4,9 @@ require 'dry/equalizer'
module Dry
module Types
# Schema is a hash with explicit member types defined
#
# @api public
class Schema < Hash
# Proxy type for schema keys. Contains only key name and
# whether it's required or not. All other calls deletaged
@ -30,15 +33,19 @@ module Dry
@name = name
end
# @api private
def call_safe(input, &block)
type.call_safe(input, &block)
end
# @api private
def call_unsafe(input)
type.call_unsafe(input)
end
# @see Dry::Types::Nominal#try
#
# @api public
def try(input, &block)
type.try(input, &block)
end
@ -46,6 +53,8 @@ module Dry
# Whether the key is required in schema input
#
# @return [Boolean]
#
# @api public
def required?
options.fetch(:required)
end
@ -60,6 +69,8 @@ module Dry
#
# @param [Boolean] required New value
# @return [Dry::Types::Schema::Key]
#
# @api public
def required(required = Undefined)
if Undefined.equal?(required)
options.fetch(:required)
@ -71,6 +82,8 @@ module Dry
# Make key not required
#
# @return [Dry::Types::Schema::Key]
#
# @api public
def omittable
required(false)
end
@ -78,6 +91,8 @@ module Dry
# Turn key into a lax type. Lax types are not strict hence such keys are not required
#
# @return [Lax]
#
# @api public
def lax
super.required(false)
end
@ -85,6 +100,8 @@ module Dry
# Dump to internal AST representation
#
# @return [Array]
#
# @api public
def to_ast(meta: true)
[
:key,
@ -98,6 +115,7 @@ module Dry
private
# @api private
def decorate?(response)
response.is_a?(Type)
end

View File

@ -5,6 +5,9 @@ require 'dry/types/meta'
module Dry
module Types
# Sum type
#
# @api public
class Sum
include Type
include Builder
@ -19,6 +22,7 @@ module Dry
# @return [Type]
attr_reader :right
# @api private
class Constrained < Sum
# @return [Dry::Logic::Operations::Or]
def rule
@ -34,6 +38,8 @@ module Dry
# @param [Type] left
# @param [Type] right
# @param [Hash] options
#
# @api private
def initialize(left, right, options = {})
super
@left, @right = left, right
@ -41,37 +47,54 @@ module Dry
end
# @return [String]
#
# @api public
def name
[left, right].map(&:name).join(' | ')
end
# @return [false]
#
# @api public
def default?
false
end
# @return [false]
#
# @api public
def constrained?
false
end
# @return [Boolean]
#
# @api public
def optional?
primitive?(nil)
end
# @param [Object] input
#
# @return [Object]
#
# @api private
def call_unsafe(input)
left.call_safe(input) { right.call_unsafe(input) }
end
# @param [Object] input
#
# @return [Object]
#
# @api private
def call_safe(input, &block)
left.call_safe(input) { right.call_safe(input, &block) }
end
# @param [Object] input
#
# @api public
def try(input)
left.try(input) do
right.try(input) do |failure|
@ -84,6 +107,7 @@ module Dry
end
end
# @api private
def success(input)
if left.valid?(input)
left.success(input)
@ -94,6 +118,7 @@ module Dry
end
end
# @api private
def failure(input, _error = nil)
if !left.valid?(input)
left.failure(input, left.try(input).error)
@ -103,7 +128,10 @@ module Dry
end
# @param [Object] value
#
# @return [Boolean]
#
# @api private
def primitive?(value)
left.primitive?(value) || right.primitive?(value)
end
@ -112,6 +140,8 @@ module Dry
# to the right branch
#
# @see [Meta#meta]
#
# @api public
def meta(data = nil)
if data.nil?
optional? ? right.meta : super
@ -123,13 +153,19 @@ module Dry
end
# @see Nominal#to_ast
#
# @api public
def to_ast(meta: true)
[:sum, [left.to_ast(meta: meta), right.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
end
# @param [Hash] options
#
# @return [Constrained,Sum]
#
# @see Builder#constrained
#
# @api public
def constrained(options)
if optional?
right.constrained(options).optional
@ -141,6 +177,8 @@ module Dry
# Wrap the type with a proc
#
# @return [Proc]
#
# @api public
def to_proc
proc { |value| self.(value) }
end

View File

@ -4,6 +4,9 @@ require 'dry/core/deprecations'
module Dry
module Types
# Common Type module denoting an object is a Type
#
# @api public
module Type
extend ::Dry::Core::Deprecations[:'dry-types']
@ -12,6 +15,8 @@ module Dry
# Whether a value is a valid member of the type
#
# @return [Boolean]
#
# @api private
def valid?(input = Undefined)
call_safe(input) { return false }
true
@ -36,6 +41,7 @@ module Dry
# @yieldparam [Object] output Partially coerced value
# @return [Object]
#
# @api public
def call(input = Undefined, &block)
if block_given?
call_safe(input, &block)

View File

@ -2,6 +2,6 @@
module Dry
module Types
VERSION = '1.0.0'.freeze
VERSION = '1.0.0'
end
end