dry-types

repository·main·Indexed 21 days ago

https://github.com/dry-rb/dry-types

A Ruby library for defining and using types to enforce type constraints and handle data validation. It provides tools for creating custom type constructors, nominal and strict types, and specialized array and hash schemas. Features include built-in type sets, coercion utilities for Date, DateTime, Time, and Symbol, and a Params-specific coercion module for handling web parameter inputs.

Tokens
8.2K
Snippets
45
Records
47
Agent score
74%

What's inside dry-types

  1. Define array member types with Dry::Types::Array::Member

    main

    The Dry::Types::Array::Member class is used to define an array type where every element must conform to a specific member type. When using this type, the member type is applied to each element during coercion or validation.

    Key behaviors:

    • Coercion: Each element in the input array is passed through the defined member type.
    • Lax Mode: You can create a lax version of a member array, which allows for more permissive coercion of the individual elements.
    • Try: The try method allows for safe processing of the array, returning a success result if all elements conform, or a failure result if any element fails or if the input is not an array.
    # Note: While the class is internal, it is used via the public Array API
    # to define types like:
    # Types::Array(Integer)
  2. Use Params-specific coercions for parameter-like inputs

    main

    The Dry::Types::Coercions::Params module provides specialized coercion logic designed for handling inputs typically found in web parameters (like strings from forms or URL queries). These methods handle common edge cases such as converting empty strings to nil, empty arrays, or empty hashes, and interpreting various truthy/falsy string representations.

    All coercion methods in this module support an optional block. If the coercion fails, the block is yielded, allowing you to define custom fallback behavior instead of raising a CoercionError.

  3. How Sum types (OR types) work in dry-types

    main

    A Sum type represents a choice between multiple types (e.g., TypeA | TypeB). When validating or coercing an input, Sum types attempt each constituent type sequentially from left to right. The first type that succeeds determines the result.

    Error Handling Behavior: If all constituent types fail, the error raised is from the rightmost (last attempted) type. This is done for performance reasons, but it means the error message might not always reflect the most 'relevant' reason for failure if an earlier type in the chain failed first.

    Example Scenario: If you have a type FixedAmount | Percentage:

    1. Input { type: 'fixed', value: -1.1 } is checked against FixedAmount. If it fails a value constraint (e.g., value must be positive), the engine moves to Percentage.
    2. If Percentage also fails (e.g., due to a type mismatch), the error raised will be the error from Percentage, even though FixedAmount was the first one to fail.
    # Conceptual example of Sum type behavior
    # Given: FixedAmount | Percentage
    # Input: { type: "fixed", value: -1.1 }
    # 1. FixedAmount fails (value constraint)
    # 2. Percentage fails (type mismatch)
    # Result: Error from Percentage is raised
  4. Define a custom Types module using Dry.Types

    main

    You can create a dedicated module for your types by including Dry.Types. This allows you to organize types into namespaces (like :strict, :coercible, or :nominal) and makes them available as constants within that module. This is the standard way to set up a type registry for use in your application.

    When you include Dry.Types, the resulting module will contain constants for the types registered in the underlying container, such as Integer, String, Bool, etc., categorized under the namespaces you specified.

    module Types
      include Dry.Types(:strict, :coercible, :nominal, default: :strict)
    end
    
    # Now you can access types via constants
    Types::Strict::Integer
    Types::Coercible::String
  5. Export types as a module using Dry.Types()

    main

    Instead of accessing types via Dry::Types['name'], you can generate a module that exports types as constants. This is the preferred way to organize types within your application.

    By default, Dry.Types() exports strict types. You can customize this by providing a default option (e.g., :nominal) or by cherry-picking specific namespaces.

    # 1. Standard usage: imports all types as constants (strict by default)
    module Types
      include Dry.Types()
    end
    Types::Integer # => Strict Integer type
    
    # 2. Changing default behavior to nominal
    module Types
      include Dry.Types(default: :nominal)
    end
    Types::Integer # => Nominal Integer type
    
    # 3. Cherry-picking specific namespaces
    # This discards default types; you must provide the :default option to include them
    module Types
      include Dry.Types(:strict, :coercible)
    end
    # Types.constants => [:Strict, :Coercible]
    
    # 4. Using custom aliases for namespaces
    module Types
      include Dry.Types(coercible: :Kernel)
    end
    Types::Kernel::Integer
  6. Configure namespaced optionals in Dry::Types

    main

    The use_namespaced_optionals setting controls how the .optional method behaves when called on a type within a namespace (like params).

    • When false (default): .optional uses Types['nil'].
    • When true: .optional uses Types['params.nil']. This allows empty strings to be treated as nil specifically within parameter types.
  7. Handle MapError when validating hashes

    main

    When using the direct call syntax type.(input) on a Map type, validation failures will raise a Dry::Types::MapError. This error occurs if:

    • A key does not match the key_type.
    • A value does not match the value_type.
    • A key is duplicated after coercion.
    • The input itself is not a valid hash primitive.

    To avoid exceptions and instead handle failures gracefully, use the .try method, which returns a result object.

    type = Dry::Types['hash'].map(Dry::Types['integer'], Dry::Types['string'])
    
    result = type.try('not_an_int' => 'value')
    if result.failure?
      puts result.error
    end
  8. Define custom type constructors with define_builder

    main

    Use Dry::Types.define_builder to add new methods to all types. This is useful for creating reusable type transformations or aliases that can be chained onto existing types.

    # Define a custom builder method :or_nil
    Dry::Types.define_builder(:or_nil) do |type|
      type.optional.fallback(nil)
    end
    
    # Now you can use .or_nil on any type
    Dry::Types['integer'].or_nil.("foo") # => nil
    
    # Example: fallback alias
    Dry::Types.define_builder(:or) do |type, fallback|
      type.fallback(fallback)
    end
    
    Dry::Types['integer'].or(100).("foo") # => 100
  9. Apply constraints to types using `constrained`

    main

    You can wrap an existing type with additional validation rules using the constrained method. This method allows you to pass nullary rules (rules that don't require arguments, like :odd) and unary rules (rules that require arguments, like gt: 0). The resulting object is a Dry::Types::Constrained instance that only validates input if it first satisfies the base type and then satisfies the provided rules.

    Note: Constraints are not applied to lax versions of types; calling .lax on a constrained type will unwrap it to the underlying lax type.

    # Example of applying constraints
    # Assuming 'Integer' is a base type and ':odd' is a nullary rule
    constrained_type = Integer.constrained(:odd)
    
    constrained_type[1]
    # => 1
    
    constrained_type[2]
    # => raises ConstraintError (or returns failure depending on usage)
  10. Coerce to Integer with to_int

    main

    The to_int method converts an input to an Integer. If the input is a String, it uses base 10 for conversion. It handles ArgumentError and TypeError by wrapping them in a CoercionError.

    Dry::Types::Coercions::Params.to_int("123") # => 123
    Dry::Types::Coercions::Params.to_int(123) # => 123
  11. Coerce inputs to Date, DateTime, Time, or Symbol

    main

    The Dry::Types::Coercions module provides utility functions for converting various input types into standard Ruby objects. These functions are commonly used by the built-in Params and JSON type sets.

    When a coercion fails (e.g., an invalid date string), these methods catch the underlying error (like ArgumentError) and pass it to CoercionError.handle. If a block is provided to the method, the block is yielded to handle the error or provide a fallback value.

    # Example of using coercion logic (conceptually via Dry::Types)
    # These methods are typically used within type definitions.
    
    # to_date(input, &block)
    # to_date_time(input, &block)
    # to_time(input, &block)
    # to_symbol(input, &block)