refined4s Documentation

website·Indexed 32 days ago

https://refined4s.kevinly.dev

A library for creating newtypes and refinement types for Scala 3. It provides tools for compile-time and runtime validation, including refined numeric, string, and network types, as well as integrations with Cats, Circe, Doobie, PureConfig, Tapir, and Chimney. Supports versions v0.x.0 and v1.21.0.

Tokens
18K
Snippets
126
Records
128
Agent score
59%

What's inside refined4s

  1. Introduction to Refined types in Scala

    Refined is a library that combines Newtype with validation, allowing developers to model validated domain values as types rather than using raw primitives (avoiding 'primitive obsession'). A refined value consists of the original value type (e.g., String, Int), a validation predicate, and a specific error message (invalidReason) used when validation fails. This approach enables catching invalid literals at compile-time and ensures runtime validation is explicit and total using Either.
    import refined4s.*
  2. Create Newtypes and Refinement Types in Scala 3 with Refined4s

    Refined4s allows developers to create newtypes and refinement types for existing types in Scala 3. Newtypes enable the transformation of generic type signatures (e.g., (Int, String, String, Int)) into domain-specific, type-safe signatures (e.g., (Id, Name, Description, Count)), improving code readability and preventing parameter transposition errors.
  3. Create domain-specific types with Newtype

    Newtype allows the creation of domain-specific types from existing value types with zero runtime overhead. It provides compile-time type safety and clear domain boundaries without the need for wrappers or additional allocations. Note that Newtype does not perform validation; for validation rules, use Refined or InlinedRefined.
  4. Create custom numeric refined types using InlinedNumeric traits

    The InlinedNumeric* traits provide a way to create custom numeric refined types with minimal boilerplate. They support inline compile-time validation for literals, runtime validation returning Either[String, X], and reusable domain-specific types. To use these, import refined4s.types.all.*.
    import refined4s.types.all.*
  5. Derive PureConfig instances using the deriving method

    For explicit control over ConfigReader and ConfigWriter instances within a Newtype, Refined, or InlinedRefined object, use the deriving method to define a given instance. This is an alternative to using pre-defined traits and requires the underlying type to already support PureConfig.
    import refined4s.modules.pureconfig.derivation._
    
    object Api {
      type NewtypeBaseUri = NewtypeBaseUri.Type
      object NewtypeBaseUri extends Newtype[Uri] {
        given configReaderNewtypeBaseUri: ConfigReader[NewtypeBaseUri] = deriving[ConfigReader]
      }
    
      type RefinedEndpointPath = RefinedEndpointPath.Type
      object RefinedEndpointPath extends Refined[String] {
        override inline def predicate(a: String): Boolean = a != ""
        override inline def invalidReason(a: String): String = "It must be a non-empty String"
        
        given configReaderRefinedEndpointPath: ConfigReader[RefinedEndpointPath] = deriving[ConfigReader]
      }
    }
  6. Import refined4s Cats derivation modules

    To use Cats type-class derivation in refined4s, import the derivation module. For versions prior to 1.8.0, an additional import of refined4s.modules.cats.derivation.types.all.given was required, but this is no longer necessary for version 1.8.0 and later.
    import refined4s.modules.cats.derivation.*
  7. Manually derive Cats instances using the deriving method

    v0
    For fine-grained control, you can use the deriving method within the companion object of a Newtype, Refined, or InlinedRefined type to create explicit Eq and Show instances. This requires the underlying base type to have the respective Cats instances.
    import cats.*
    import refined4s.*
    import refined4s.modules.cats.derivation.*
    
    type Name = Name.Type
    object Name extends Newtype[String] {
      given eqName: Eq[Name] = deriving[Eq]
      given showName: Show[Name] = deriving[Show]
    }
  8. Validate Refined types at compile-time

    Use the apply method (via the companion object) when the input value is a literal known at compile-time. If the value violates the predicate, the code will fail to compile with the message defined in invalidReason.
    // Assuming type Month is defined with predicate a >= 1 && a <= 12
    Month(1)  // Valid: compiles
    Month(13) // Invalid: Compile-time error: "Invalid value: [13]. It must be Int between 1 and 12 (1 - 12)"
  9. Use pre-defined Chimney instances for refined4s types

    When using Newtype, Refined, or InlinedRefined with Chimney, you need Transformer[NewType, RawType] and PartialTransformer[RawType, NewType] instances. These are provided by importing refined4s.modules.chimney.derivation.types.all.given. This enables the use of .intoPartial[T].transform or .transformIntoPartial[T] for safe transformations that may fail validation.
    import refined4s.*
    import refined4s.types.all.*
    import io.scalaland.chimney
    import io.scalaland.chimney.dsl.*
    import refined4s.modules.chimney.derivation.types.all.given
    
    final case class Person(name: NonEmptyString)
    final case class User(name: NonEmptyString)
    
    val person = Person(NonEmptyString("Wade Wilson"))
    
    // Transform using Chimney
    person.intoPartial[User].transform
    // res: Result[User] = Value(User("Wade Wilson"))
  10. Derive Circe instances for refined4s types using the deriving method

    Use the deriving method within a Newtype or Refined companion object to explicitly define Encoder and Decoder instances. This approach provides more control over how the instances are defined while still leveraging the underlying type's Circe instances.
    import refined4s.*
    import refined4s.modules.circe.derivation.*
    import io.circe.*
    
    type Name = Name.Type
    object Name extends Newtype[String] {
      given encoderName: Encoder[Name] = deriving[Encoder]
      given decoderName: Decoder[Name] = deriving[Decoder]
    }
    
    type NotEmptyStr = NotEmptyStr.Type
    object NotEmptyStr extends Refined[String] {
      inline def invalidReason(a: String): String = "non-empty String"
      inline def predicate(a: String): Boolean = a != ""
    
      given encoderNotEmptyStr: Encoder[NotEmptyStr] = deriving[Encoder]
      given decoderNotEmptyStr: Decoder[NotEmptyStr] = Decoder[String].emap(NotEmptyStr.from)
    }
  11. Add Render support to refined types using ExtrasRender trait

    v0
    You can provide Render support to a specific Newtype or Refined type by mixing in the ExtrasRender trait. This requires the underlying type to already have a Render instance.
    import refined4s.*
    import refined4s.modules.extras.derivation.*
    
    type Name = Name.Type
    object Name extends Newtype[String] with ExtrasRender[String]
    
    type NotEmptyStr = NotEmptyStr.Type
    object NotEmptyStr extends Refined[String] with ExtrasRender[String] {
      inline def invalidReason(a: String): String = "non-empty String"
      inline def predicate(a: String): Boolean = a != ""
    }