refined4s

newtype and refinement (refined) type for Scala 3

MIT License

Stars
7

Bot releases are hidden (Show)

refined4s - v0.16.0 Latest Release

Published by kevin-lee 3 months ago

0.16.0 - 2024-08-03

Improvement

  • [refined4s-core] Add unicode encoding for the error message from NonBlankString.from (#312)
    val s = "\u0009\u200a\u2004\u1680"
    // String = "	   "
    NonBlankString.from(s)
    // Left(Invalid value: [	   ], unicode=[\u0009\u200a\u2004\u1680]. It must be not all whitespace non-empty String)
    
  • [refined4s-circe] Add numeric, strings and network objects in refined4s.modules.circe.derivation.types
refined4s - v0.15.0

Published by kevin-lee 6 months ago

0.15.0 - 2024-04-13

New Features

  • [refined4s-core] Add NonBlankString which can be neither all whitespace chars nor an empty String (#281)

    NonBlankString("")
    // Invalid value: [""]. It must be not all whitespace non-empty String
    
    NonBlankString(" ")
    // Invalid value: [" "]. It must be not all whitespace non-empty String
    
    NonBlankString("   ")
    // Invalid value: ["   "]. It must be not all whitespace non-empty String
    
    NonBlankString("\n")
    // Invalid value: ["\n"]. It must be not all whitespace non-empty String
    
    NonBlankString("\t")
    // Invalid value: ["\t"]. It must be not all whitespace non-empty String
    
    NonBlankString("\t\n")
    // Invalid value: ["\t\n"]. It must be not all whitespace non-empty String
    
    NonBlankString(" \t \n")
    // Invalid value: [" \t \n"]. It must be not all whitespace non-empty String
    
  • [refined4s-cats] Add cats support for NonBlankString (#283)

  • [refined4s-circe] Add circe support for NonBlankString (#284)

  • [refined4s-pureconfig] Add pureconfig support for NonBlankString (#285)

  • [refined4s-doobie] Add doobie support for NonBlankString (#286)

  • [refined4s-extras-render] Add extras-render support for NonBlankString (#287)

  • [refined4s-tapir] Add tapir support for NonBlankString (#288)

refined4s - v0.14.0

Published by kevin-lee 7 months ago

0.14.0 - 2024-04-05

New Features

  • Add refined4s-tapir to support tapir (#272)
  • [refined4s-tapir] Add TapirNewtypeSchema and TapirRefinedSchema to support sttp.tapir.Schema for refined4s (#273)
  • [refined4s-tapir] Add Schemas for pre-defined refined types (#276)
  • [refined4s-tapir] Add Schema type-class instances with auto deriving (#278)
refined4s - v0.13.0

Published by kevin-lee 9 months ago

0.13.0 - 2024-01-21

New Features

  • [refined4s-core] Add Url to refined4s.types.network and the type-class instances for Url in the other modules (#246)

  • [refined4s-core] Add Uuid String which can be validated as java.util.UUID and the type-class instances for Uuid in the other modules (#248)

  • [refined4s-core] Add Uri.apply(java.net.URI) (#251)

  • [refined4s-core] Add Url.apply(java.net.URL) (#252)

  • [refined4s-refined-compat-scala2] Add Uri, Url and Uuid (#255)
refined4s - v0.12.0

Published by kevin-lee 9 months ago

0.12.0 - 2024-01-20

New Feature

  • [refined4s-core] Make pre-defined types in types.all importable from each type category (i.e. numeric, strings and network) (#237)

    So it means a type like refined4s.types.numeric.NegInt should be exactly the same as refined4s.types.all.NegInt.


  • Add refined4s-refined-compat modules for compatibility with the refined library in Scala 2 (#241)
    • refined4s-refined-compat-scala2 for compatibility with refined in Scala 2
    • refined4s-refined-compat-scala3 for just using refined4s in Scala 3

  • Add RefinedCompatAllTypes to refined4s-refined-compat-scala2 and refined4s-refined-compat-scala3 (#243)

Internal Change

  • [refined4s-core] Move MinValue and MaxValue from each MinMax to Min and Max (#239)

    e.g.)

    type NegInt = NegInt.Type
    object NegInt extends Numeric[Int], MinMax[Int] {
      override def min: Type = apply(Int.MinValue)
      override def max: Type = apply(-1)
    
      val MinValue: Type = min
      val MaxValue: Type = max
    }
    

    to

    trait Min[A] {
      self: NewtypeBase[A] =>
      def min: Type
    
      val MinValue: Type = min
    }
    
    trait Max[A] {
      self: NewtypeBase[A] =>
      def max: Type
    
      val MaxValue: Type = max
    }
    

refined4s - v0.11.0

Published by kevin-lee 10 months ago

0.11.0 - 2024-01-04

New Feature

  • [refined4s-core] Add MinValue and MaxValue to refined basic numeric types (#223)

    • Add Min trait
    • Add Max trait
    • Add MinMax trait

    Add MinMax to the following types to have MinValue and MaxValue

    • NegInt
    • NonNegInt
    • PosInt
    • NonPosInt
    • NegLong
    • NonNegLong
    • PosLong
    • NonPosLong
    • NegShort
    • NonNegShort
    • PosShort
    • NonPosShort
    • NegByte
    • NonNegByte
    • PosByte
    • NonPosByte
    • NegFloat
    • NonNegFloat
    • PosFloat
    • NonPosFloat
    • NegDouble
    • NonNegDouble
    • PosDouble
    • NonPosDouble

Internal Change

  • [refined4s-core] Move Numeric and InlinedNumeric from numeric trait to numeric object (#225)
refined4s - v0.10.0

Published by kevin-lee 10 months ago

0.10.0 - 2024-01-02

New Feature

  • Release refined4s-extras-render module (#220)
  • Add refined4s-extras-render to support Render for refined4s (#215)
    refined4s.modules.extras.derivation.ExtrasRender
    refined4s.modules.extras.derivation.types.all.given
    refined4s.modules.extras.derivation.generic.auto.given
    
    e.g.)
    import extras.render.Render
    
    import refined4s.types.all.*
    import refined4s.modules.extras.derivation.types.all.given
    
    val name = NonEmptyString("Kevin")
    Render[NonEmptyString].render(name)
    // String = Kevin
    
    import extras.render.Render
    
    import refined4s.Newtype
    import refined4s.modules.extras.derivation.ExtrasRender
    
    type Name = Name.Type
    object Name extends Newtype[String], ExtrasRender[String]
    
    val name = Name("Kevin")
    Render[Name].render(name)
    // String = Kevin
    
    import extras.render.Render
    
    import refined4s.Newtype
    
    type Name = Name.Type
    object Name extends Newtype[String]
    
    import refined4s.modules.extras.derivation.generic.auto.given
    
    val name = Name("Kevin")
    Render[Name].render(name)
    // String = Kevin
    
refined4s - v0.9.0

Published by kevin-lee 10 months ago

0.9.0 - 2024-01-02

NOTE:
refined4s-extras-render was not released by mistake, and it will be released in v0.10.0.

New Feature

  • Add refined4s-extras-render to support Render for refined4s (#215)
    refined4s.modules.extras.derivation.ExtrasRender
    refined4s.modules.extras.derivation.types.all.given
    refined4s.modules.extras.derivation.generic.auto.given
    
    e.g.)
    import extras.render.Render
    
    import refined4s.types.all.*
    import refined4s.modules.extras.derivation.types.all.given
    
    val name = NonEmptyString("Kevin")
    Render[NonEmptyString].render(name)
    // String = Kevin
    
    import extras.render.Render
    
    import refined4s.Newtype
    import refined4s.modules.extras.derivation.ExtrasRender
    
    type Name = Name.Type
    object Name extends Newtype[String], ExtrasRender[String]
    
    val name = Name("Kevin")
    Render[Name].render(name)
    // String = Kevin
    
    import extras.render.Render
    
    import refined4s.Newtype
    
    type Name = Name.Type
    object Name extends Newtype[String]
    
    import refined4s.modules.extras.derivation.generic.auto.given
    
    val name = Name("Kevin")
    Render[Name].render(name)
    // String = Kevin
    
refined4s - v0.8.0

Published by kevin-lee 10 months ago

0.8.0 - 2023-12-31

Changes

  • [refined4s-core] Change NewtypeBase.unapply and RefinedBase.unapply to return Some[A] instead of Option[A] (#208)

    The reason for having Some[A] as the return type of the unapply methods can be found at https://github.com/scala/bug/issues/12232. 😔

refined4s - v0.7.0

Published by kevin-lee 10 months ago

0.7.0 - 2023-12-30

Changes

  • [refined4s-cats] Rename validateAs in refined4s.modules.cats.syntax to refinedNewtypeNec (#182)

  • [refined4s-core] Move toValue from refined4s.syntax to refined4s.NewtypeBase (#186)

    So with the given following code,

    import refined4s.*
    import refined4s.types.all.*
    
    type Name = Name.Type
    object Name extends Newtype[NonEmptyString]
    
    

    the following is possible without importing any syntax.

    val name = Name(NonEmptyString("Kevin"))
    name.toValue
    // String = "Kevin"
    

New Features

  • [refined4s-cats] Add refinedNewtypeNel in refined4s.modules.cats.syntax (#184)

    import refined4s.*
    import refined4s.types.all.*
    
    import refined4s.modules.cats.syntax.*
    
    type Name = Name.Type
    object Name extends Newtype[NonEmptyString]
    
    "Kevin".refinedNewtypeNel[Name]
    // EitherNel[String, Name] = Right(Name(NonEmptyString("Kevin")))
    
    "".refinedNewtypeNel[Name]
    // EitherNel[String, Name] = Left(NonEmptyList("Failed to create Name: Invalid value: []. It has to be a non-empty String but got \"\"))
    

  • [refined4s-cats] Add validateAs in refined4s.modules.cats.syntax to validate a value and return Validated (#188)

    import refined4s.*
    import refined4s.types.all.*
    
    import refined4s.modules.cats.syntax.*
    
    type Name = Name.Type
    object Name extends Newtype[NonEmptyString]
    
    "Kevin".validateAs[Name]
    // Validated[String, Name] = Valid(Name(NonEmptyString("Kevin")))
    
    "".validateAs[Name]
    // Validated[String, Name] = Invalid("Failed to create Name: Invalid value: []. It has to be a non-empty String but got \"\")
    

  • [refined4s-cats] Add validateNecAs in refined4s.modules.cats.syntax to validate a value and return ValidatedNec (#189)

    import refined4s.*
    import refined4s.types.all.*
    
    import refined4s.modules.cats.syntax.*
    
    type Name = Name.Type
    object Name extends Newtype[NonEmptyString]
    
    "Kevin".validateNecAs[Name]
    // ValidatedNec[String, Name] = Valid(Name(NonEmptyString("Kevin")))
    
    "".validateNecAs[Name]
    // ValidatedNec[String, Name] = Invalid(NonEmptyChain("Failed to create Name: Invalid value: []. It has to be a non-empty String but got \"\"))
    

  • [refined4s-cats] Add validateNelAs in refined4s.modules.cats.syntax to validate a value and return ValidatedNel (#190)

    import refined4s.*
    import refined4s.types.all.*
    
    import refined4s.modules.cats.syntax.*
    
    type Name = Name.Type
    object Name extends Newtype[NonEmptyString]
    
    "Kevin".validateNelAs[Name]
    // ValidatedNel[String, Name] = Valid(Name(NonEmptyString("Kevin")))
    
    "".validateNelAs[Name]
    // ValidatedNel[String, Name] = Invalid(NonEmptyList("Failed to create Name: Invalid value: []. It has to be a non-empty String but got \"\"))
    

  • [refined4s-cats] Add derivedOrder to have the instance of Order[A] derived from Coercible[A, B] and Order[B] (#194)

    Given

    import refined4s.*
    
    type MyNum = MyNum.Type
    object MyNum extends Newtype[Int]
    

    it can be

    import cats.*
    
    val n1 = MyNum(1)
    val n2 = MyNum(2)
    
    Order[MyNum].compare(n1, n2)
    // Int = -1
    

  • [refined4s-core] Add CanBeOrdered for providing Ordering and Conversion[Type, Ordered[Type]] (#196)

    import refined4s.*
    
    type MyNum = MyNum.Type
    object MyNum extends Newtype[Int]
    
    val input1 = MyNum(1)
    val input2 = MyNum(2)
    
    Ordering[MyNum].compare(input1, input2)
    // Int = -1
    
    (input1: Ordered[MyNum]).compare(input2)
    // Int = -1
    

  • [refined4s-core] Make NonEmptyString CanBeOrdered to have Ordering[NonEmptyString] and Conversion[NonEmptyString, Ordered[NonEmptyString]] (#198)

  • [refined4s-cats] Add CatsOrder (#203)
    import refined4s.*
    
    type MyNum = MyNum.Type
    object MyNum extends Newtype[Int], CatsOrder[Int]
    
    import cats.*
    val myNum1 = NyNum(1)
    val myNum2 = NyNum(2)
    
    Order[MyNum].compare(myNum1, myNum1)
    // Int = 0
    
    Order[MyNum].compare(myNum1, myNum2)
    // Int = -1
    
    Order[MyNum].compare(myNum2, myNum1)
    // Int = 1
    

Internal Changes

  • [refined4s-core] Replace Ordering and Conversion[Type, Ordered[Type]] for numeric types with CanBeOrdered (#199)
refined4s - v0.6.0

Published by kevin-lee 10 months ago

0.6.0 - 2023-12-28

Changes

  • All modules: importing derivation.instances.given can cause an issue as it overrides all type-classes defined in the companion objects (#163)
    • [refined4s-cats] Add explicit Eq and Show for pre-defined types (#171)

      • Now it has refined4s.modules.cats.derivation.types.all for the all pre-defined types (e.g. NegInt, PosInt, NonEmptyString, etc.)
      • refined4s.modules.cats.derivation.instances => refined4s.modules.cats.derivation.generic.auto
      • refined4s.modules.cats.derivation.instances.contraCoercible is moved to refined4s.modules.cats.syntax
    • [refined4s-circe] Add explicit Encoder and Decoder for pre-defined types (#166)

    • [refined4s-pureconfig] Add explicit ConfigReader and ConfigWriter for pre-defined types (#172)

    • [refined4s-doobie] Add explicit Get and Put for pre-defined types (#173)


  • [refined4s-core] Keep only all object for pre-defined types and remove all the others (#168)

    So this will be the only import available for using pre-defined types

    import refined4s.types.all.*
    

    The following ones have been removed.

    import refined4s.types.numeric.*
    import refined4s.types.strings.*
    import refined4s.types.network.*
    

  • Rename derived type classes - some of them have a naming conflict issue (#165)
refined4s - v0.5.0

Published by kevin-lee 10 months ago

0.5.0 - 2023-12-23

New Features

  • [refined4s-cats] Add contraCoercible to F[B] => F[A] with Contravariant[F] and Coercible[A, B] (#152)

    This is useful to derive type-class instances for Newtype, Refined and InlinedRefined from the type-class instances of the actual types.

    e.g.)

    import refined4s.modules.cats.derivation.instances.contraCoercible
    
    inline given eqDerived[A, B](using coercible: Coercible[A, B], eqB: Eq[B]): Eq[A] =
      contraCoercible(eqB)
    
    inline given showDerived[A, B](using coercible: Coercible[A, B], showB: Show[B]): Show[A] =
      contraCoercible(showB)
    

    where B is the actual type and A could be a newtype or a refined type.


Improvement

  • Add missing inline to invalidReason in the sub-types of InlinedNumeric (#146)

  • [refined4s-pureconfig] Add type name to the error message of PureconfigRefinedConfigReader and refined4s.modules.pureconfig.derivation.instances.derivedRefinedConfigReader (#150)

    So a message like this

    
    Invalid value found: -2373683071661092303 with error: Invalid value: [-2373683071661092303]. It must be a positive Long
    

    should be like this instead.

    The value -2373683071661092303 cannot be created as the expected type, mytypes.blah.Id.Type, due to the following error: Invalid value: [-2373683071661092303]. It must be a positive Long
    

  • [refined4s-doobie] Make type-classes in refined4s.modules.doobie.derivation.instances inline (#156)

  • [refined4s-core] Improve compile-time error message for inline apply method (#158)

    The old compile-time error for the constant value passed to the inline apply method may look like this.

    [error] 2780 |      NegBigInt(1)
    [error]      |      ^^^^^^^^^^^^
    [error]      |A literal string is expected as an argument to `compiletime.error`. Got "Invalid value: [BigInt.apply(1)]. ".+(
    [error]      |  {
    [error]      |    val a$proxy4: BigInt = BigInt.apply(1)
    [error]      |    "It must be a negative BigInt":String:String
    [error]      |  }:String
    [error]      |)
    [error] one error found
    

    It is not so readable, and it is hard to comprehend what is wrong with the input.

    After this release, it will be like

    [error] 2780 |      NegBigInt(1)
    [error]      |      ^^^^^^^^^^^^
    [error]      |      Invalid value: [BigInt.apply(1)]. It must be a negative BigInt
    [error] one error found
    

Internal Housekeeping

  • [refined4s-circe] Update refined4s.modules.circe.derivation.instances.derivedEncoder to use contraCoercible and make it inline (#154)

    The old code has the following issue if it's turned into an inline method.

    [error] 13 |    a => encoder(coercible(a))
    [error]    |    ^^^^^^^^^^^^^^^^^^^^^^^^^^
    [error]    |An inline given alias with a function value as right-hand side can significantly increase
    [error]    |generated code size. You should either drop the `inline` or rewrite the given with an
    [error]    |explicit `apply` method.
    

  • Rename Coercible methods and type parameters (#160)
    • wrap and unwrap suffixed with M: M to TC (type constructor)
    • wrap and unwrap suffixed with MOfM: MOfM to HKT (higher-kinded type)
    • M[*] to F[*]
    • M1[*] to F[*] and M2[*] to G[*] e.g.) M1[M2[A]] to F[G[A]]
refined4s - v0.4.0

Published by kevin-lee 10 months ago

0.4.0 - 2023-12-16

New Features

  • Add refined4s-doobie-ce2 and refined4s-doobie-ce3 modules to support doobie (#123)
  • [refined4s-doobie] Add Gets and Put for Newtype, Refined and InlinedRefined with Coercible and RefinedCtor (#124)
  • [refined4s-doobie] Add DoobiePut, DoobieNewtypeGet, DoobieRefinedGet, DoobieNewtypeGetPut and DoobieRefinedGetPut to have circe Get and Put derived from the actual type for Newtype, Refined and InlinedRefined (#127)
refined4s - v0.3.0

Published by kevin-lee 10 months ago

0.3.0 - 2023-12-15

Changes

  • [refined4s-cats] Replace Eq and Show instances for the existing refined types with the ones derived from the actual types using Coercible (#107)

New Features

  • Add refined4s-circe module to support circe (#101)

  • [refined4s-circe] Add Encoder and Decoders for Newtype, Refined and InlinedRefined with Coercible and RefinedCtor (#103)

  • [refined4s-circe] Add CirceEncoder, CirceNewtypeDecoder, CirceRefinedDecoder, CirceNewtypeCodec and CirceRefinedCodec to have circe Encoder and Decoder derived from the actual type for Newtype, Refined and InlinedRefined (#104)

    import refined4s.modules.circe.derivation.*
    
    type MyNewtype = MyNewtype.Type
    object MyNewtype extends Newtype[String] with CirceEncoder[String]
    
    type MyRefinedType = MyRefinedType.Type
    object MyRefinedType extends Refined[String] with CirceEncoder[String] {
      override inline def invalidReason(a: String): String =
        "It has to be a non-empty String but got \"" + a + "\""
    
      override inline def predicate(a: String): Boolean = a != ""
    }
    
    type MyRefinedNewtype = MyRefinedNewtype.Type
    object MyRefinedNewtype extends Newtype[MyRefinedType] with CirceEncoder[MyRefinedType]
    
    type MyNewtype = MyNewtype.Type
    object MyNewtype extends Newtype[String] with CirceNewtypeDecoder[String]
    
    type MyRefinedType = MyRefinedType.Type
    object MyRefinedType extends Refined[String] with CirceRefinedDecoder[String] {
      override inline def invalidReason(a: String): String =
        "It has to be a non-empty String but got \"" + a + "\""
    
      override inline def predicate(a: String): Boolean = a != ""
    }
    
    type MyRefinedNewtype = MyRefinedNewtype.Type
    object MyRefinedNewtype extends Newtype[MyRefinedType] with CirceNewtypeDecoder[MyRefinedType]
    
  • [refined4s-core] Add PortNumber, SystemPortNumber, NonSystemPortNumber, UserPortNumber and DynamicPortNumber (#110)

  • Add refined4s-pureconfig module to support pureconfig (#112)

  • [refined4s-pureconfig] Add ConfigReader and ConfigWriters for Newtype, Refined and InlinedRefined with Coercible and RefinedCtor (#113)

    import refined4s.modules.pureconfig.derivation.instances.given
    
  • [refined4s-pureconfig] Add PureconfigNewtypeConfigReader and PureconfigRefinedConfigReader to provider ConfigReader for Newtype, Refined and InlinedRefined (#116)

    type MyNewtype = MyNewtype.Type
    object MyNewtype extends Newtype[String] with PureconfigNewtypeConfigReader[String]
    
    type MyRefinedType = MyRefinedType.Type
    object MyRefinedType extends Refined[String] with PureconfigRefinedConfigReader[String] {
      override inline def invalidReason(a: String): String =
        "It has to be a non-empty String but got \"" + a + "\""
    
      override inline def predicate(a: String): Boolean = a != ""
    }
    
    type MyRefinedNewtype = MyRefinedNewtype.Type
    object MyRefinedNewtype extends Newtype[MyRefinedType] with PureconfigNewtypeConfigReader[MyRefinedType]
    
    type MyInlinedRefinedType = MyInlinedRefinedType.Type
    object MyInlinedRefinedType extends InlinedRefined[String] with PureconfigRefinedConfigReader[String] {
      override inline def invalidReason(a: String): String =
        "It has to be a non-empty String but got \"" + a + "\""
    
      override inline def predicate(a: String): Boolean = a != ""
    
      override inline def inlinedPredicate(inline a: String): Boolean = a != ""
    }
    
    type MyInlinedRefinedNewtype = MyInlinedRefinedNewtype.Type
    object MyInlinedRefinedNewtype extends Newtype[MyRefinedType] with PureconfigNewtypeConfigReader[MyRefinedType]
    
  • [refined4s-pureconfig] Add PureconfigConfigWriter to provider ConfigWriter for Newtype, Refined and InlinedRefined (#118)

refined4s - v0.2.0

Published by kevin-lee 10 months ago

0.2.0 - 2023-12-10

Changes

  • Rename refined4s.cats to refined4s.modules.cats (#94)
    import refined4s.modules.cats.derivation.*
    import refined4s.modules.cats.syntax.*
    

New Features

  • Add Eq[A] and Show[A] instances for the existing refined types (#95)

    So with

    import refined4s.modules.cats.derivation.instances.given
    

    Eq and Show instances for the following types are available

    • NegInt
    • NonNegInt
    • PosInt
    • NonPosInt
    • NegLong
    • NonNegLong
    • PosLong
    • NonPosLong
    • NegShort
    • NonNegShort
    • PosShort
    • NonPosShort
    • NegByte
    • NonNegByte
    • PosByte
    • NonPosByte
    • NegFloat
    • NonNegFloat
    • PosFloat
    • NonPosFloat
    • NegDouble
    • NonNegDouble
    • PosDouble
    • NonPosDouble
    • NegBigInt
    • NonNegBigInt
    • PosBigInt
    • NonPosBigInt
    • NegBigDecimal
    • NonNegBigDecimal
    • PosBigDecimal
    • NonPosBigDecimal
    • NonEmptyString
    • Uri
refined4s - v0.1.0

Published by kevin-lee 10 months ago

0.1.0 - 2023-12-10

New Features

Add Refined[A] (#1)

A trait Refined[A] should provide a way to create a refined type with validation.
It should also provide a way to validate in compile-time if applicable.

So it should be able to do like this.

type NonEmptyString = NonEmptyString.Type

object NonEmptyString extends Refined[String] {
  inline override def predicate(a: String): Boolean = a != ""
}

NonEmptyString("Blah") // It compiles
NonEmptyString("") // A compile-time error

NonEmptyString.from("Blah") // Either[String, NonEmptyString] = Right(NonEmptyString("Blah"))
NonEmptyString.from("") // Either[String, NonEmptyString] = Left("Invalid value: ")

NonEmptyString.unsafeFrom("Blah") // NonEmptyString = NonEmptyString("Blah")
NonEmptyString.unsafeFrom("") // IllegalArgumentException

[core] Add NonEmptyString (#5)

NonEmptyString("blah") // compiles
NonEmptyString("") // compile-time error

NonEmptyString.from("blah")
// Either[String, NonEmptyString] = Right(NonEmptyString("blah"))

NonEmptyString.from("")
// Either[String, NonEmptyString] = Left(Invalid value: []. It should be a non-empty String value but got [])

NonEmptyString.unsafeFrom("blah")
// NonEmptyString = NonEmptyString("blah")

NonEmptyString.unsafeFrom("")
// IllegalArgumentException(Invalid value: []. It should be a non-empty String value but got []) is thrown

[core] Add numeric.NegInt (#7)

NegInt(-1) // compiles
NegInt(0) // compile-time error
NegInt(1) // compile-time error

NegInt.from(-123)
// Either[String, NegInt] = Right(NegInt(-123))

NegInt.from(123)
// Either[String, NegInt] = Left(Invalid value: 123. It must be a negative Int)

NegInt.unsafeFrom(-999)
// NegInt = NegInt(-999)

NegInt.unsafeFrom(999)
// IllegalArgumentException(Invalid value: 999. It must be a negative Int) is thrown

[core] Add numeric.NonPosInt (#8)

NonPosInt(-1) // compiles
NonPosInt(0)  // compiles
NonPosInt(1)  // compile-time error

NonPosInt.from(-123)
// Either[String, NonPosInt] = Right(NonPosInt(-123))

NonPosInt.from(123)
// Either[String, NonPosInt] = Left(Invalid value: 123. It must be a non-positive Int)

NonPosInt.unsafeFrom(-999)
// NegInt = NonPosInt(-999)

NonPosInt.unsafeFrom(999)
// IllegalArgumentException(Invalid value: 999. It must be a non-positive Int) is thrown

[core] Add numeric.PosInt (#9)

PosInt(1) // compiles
PosInt(0) // compile-time error
PosInt(-1) // compile-time error

PosInt.from(123)
// Either[String, PosInt] = Right(PosInt(123))

PosInt.from(-123)
// Either[String, PosInt] = Left(Invalid value: -123. It must be a positive Int)

PosInt.unsafeFrom(999)
// PosInt = PosInt(999)

PosInt.unsafeFrom(-999)
// IllegalArgumentException(Invalid value: -999. It must be a positive Int) is thrown

[core] Add numeric.NonNegInt (#10)

NonNegInt(1) // compiles
NonNegInt(0) // compiles
NonNegInt(-1) // compile-time error

NonNegInt.from(123)
// Either[String, NonNegInt] = Right(NonNegInt(123))

NonNegInt.from(-123)
// Either[String, NonNegInt] = Left(Invalid value: -123. It must be a non-negative Int)

NonNegInt.unsafeFrom(999)
// NonNegInt = NonNegInt(999)

NonNegInt.unsafeFrom(-999)
// IllegalArgumentException(Invalid value: -999. It must be a non-negative Int) is thrown

[core] Add numeric.Numeric (#11)

Numeric[A: math.Ordering] provides Ordering[Numeric[A]#Type] derived from A and Ordered[Numeric[A]#Type] converted from Ordering[Numeric[A]#Type].


[core] Add numeric.NegLong (#18)

NegLong(-1L) // compiles
NegLong(0L)  // compile-time error
NegLong(1L)  // compile-time error

NegLong.from(-123L)
// Either[String, NegLong] = Right(NegLong(-123L))

NegLong.from(123L)
// Either[String, NegLong] = Left("Invalid value: 123L. It must be a negative Long")

NegLong.unsafeFrom(-999L)
// NegLong = NegLong(-999L)

NegLong.unsafeFrom(999L)
// IllegalArgumentException(Invalid value: 999L. It must be a negative Long) is thrown

[core] Add numeric.NonNegLong (#19)

NonNegLong(1L)  // compiles
NonNegLong(0L)  // compiles
NonNegLong(-1L) // compile-time error

NonNegLong.from(123L)
// Either[String, NonNegLong] = Right(NonNegLong(123L))

NonNegLong.from(-123L)
// Either[String, NonNegLong] = Left("Invalid value: -123L. It must be a non-negative Long")

NonNegLong.unsafeFrom(999L)
// NonNegLong = NonNegLong(999L)

NonNegLong.unsafeFrom(-999L)
// IllegalArgumentException(Invalid value: -999L. It must be a non-negative Long) is thrown

[core] Add numeric.PosLong (#20)

PosLong(1L)  // compiles
PosLong(0L)  // compile-time error
PosLong(-1L) // compile-time error

PosLong.from(123L)
// Either[String, PosLong] = Right(PosLong(123L))

PosLong.from(-123L)
// Either[String, PosLong] = Left("Invalid value: -123L. It must be a positive Long")

PosLong.unsafeFrom(999L)
// PosLong = PosLong(999L)

PosLong.unsafeFrom(-999L)
// IllegalArgumentException(Invalid value: -999L. It must be a positive Long) is thrown

[core] Add numeric.NonPosLong (#21)

NonPosLong(-1L) // compiles
NonPosLong(0L)  // compiles
NonPosLong(1L)  // compile-time error

NonPosLong.from(-123L)
// Either[String, NonPosLong] = Right(NonPosLong(-123L))

NonPosLong.from(123L)
// Either[String, NonPosLong] = Left("Invalid value: 123L. It must be a non-positive Long")

NonPosLong.unsafeFrom(-999L)
// NonPosLong = NonPosLong(-999L)

NonPosLong.unsafeFrom(999L)
// IllegalArgumentException(Invalid value: 999L. It must be a non-positive Long) is thrown

Extract the essential properties of Refined and create RefinedBase (#30)

Refined to

  • RefinedBase
  • Refined

Refined should have only the apply method for the compile-time validation to create a value for the refined type.


Add InlinedRefined (#32)

It should look like this.

trait InlinedRefined[A] extends RefinedBase[A] {

  inline def inlinedInvalidReason(inline a: A): String

  inline def inlinedPredicate(inline a: A): Boolean

  inline def apply(inline a: A): Type
}

Add deriving to derive type-class from the base type (#40)

import cats.*

type MyType = MyType.Type
object MyType extends Refined[String] {

  override inline def invalidReason(a: String): String =
    "It has to be a non-empty String but got [" + a + "]"

  override inline def predicate(a: String): Boolean = a != ""

  given eqMyType: Eq[MyType] = deriving[Eq]

  given showMyType: Show[MyType] = deriving[Show]
}
import cats.syntax.all.*

MyType("blah") === MyType("blah")
// Boolean = true

MyType("blah").show
// String = blah

Add Coercible to type-safely cast refined type to the actual type (#42)

type MoreThan2CharString = MoreThan2CharsString.Type
object MoreThan2CharsString extends InlinedRefined[String] {

  override inline def invalidReason(a: String): String =
    "The String should have more than 2 chars but got " + a

  override def predicate(a: String): Boolean = a.length > 2

  override inline def inlinedInvalidReason(inline a: String): String =
    invalidReason(codeOf(a))

  override inline def inlinedPredicate(inline a: String): Boolean =
    ${ checkStringLength('a) }

  private def checkStringLength(strExpr: Expr[String])(using Quotes): Expr[Boolean] = {
    val str = strExpr.valueOrAbort
    if predicate(str) then Expr(true) else Expr(false)
  }
}
def foo[T, S](t: T)(using coercible: Coercible[T, S]): S =
  coercible(t)

val s: String = foo(MoreThan2CharsString(">>>>> aaa"))
println(s)
// String = >>>>> aaa

NOTE: For this ticket, the focus is solely on adding Coercible; subsequent integration with Refined to utilize Coercible will be addressed later.

The idea of Coercible is from scala-newtype's Coercible.


Add Newtype (#44)

It is solely to create a newtype just like an opaque type but with a few mandatory methods and type-classes.

type Something = Something.Type
object Something extends Newtype[String] {
  given eqMyType: Eq[Something]     = deriving[Eq]
  given showMyType: Show[Something] = deriving[Show]
}

Then it can be

val something = Something("blah")
something.value
// String = blah

something.show
// String = blah

Something("blah") === Something("blah")
// Boolean = true

Something("blah") =!= Something("blah")
// Boolean = false

Something("blah") === Something("lala")
// Boolean = false

Something("blah") =!= Something("lala")
// Boolean = true

Make RefinedBase extend NewtypeBase to support unwrapping (getting value) for Refined and InlinedRefined types with Coercible (#46)

type MyType = MyType.Type
object MyType extends InlinedRefined[String] {

  override inline def invalidReason(a: String): String =
    "It has to be a non-empty String but got [" + a + "]"

  override inline def predicate(a: String): Boolean = a != ""

  override inline def inlinedPredicate(inline a: String): Boolean = a != ""
}
type Something = Something.Type
object Something extends InlinedRefined[Int] {

  private def inlinedPredicate0(a: Expr[Int])(using Quotes): Expr[Boolean] = {
    import quotes.reflect.*
    a.asTerm match {
      case Inlined(_, _, Literal(IntConstant(num))) =>
        try {
          validate(num)
          Expr(true)
        } catch {
          case _: Throwable => Expr(false)
        }
      case _ =>
        report.error(
          "Something must be a Int literal.",
          a,
        )
        Expr(false)
    }

  }

  override inline def inlinedPredicate(inline a: Int): Boolean = ${ inlinedPredicate0('a) }

  override def invalidReason(a: Int): String = s"The number is a negative Int. [a: ${a.toString}"

  override def predicate(a: Int): Boolean =
    try {
      validate(a)
      true
    } catch {
      case _: Throwable => false
    }
}
def unwrap[A, B](a: A)(using coercible: Coercible[A, B]): B = coercible(a)

unwrap(MyType("abc"))
// String = abc

unwrap(Something(999))
// Int = 999

Add RefinedCtor[T, A] to provide a way to create a validated type T from an actual type A with validation (#49)

type MyType = MyType.Type
object MyType extends Refined[String] {

  override inline def invalidReason(a: String): String =
    "It has to be non-empty String but got \"" + a + "\""

  override inline def predicate(a: String): Boolean = a != ""

  given refinedCtor: RefinedCtor[Type, String] with {
    override def create(a: String): Either[String, MyType] = from(a)
  }
}
RefinedCtor[MyType, String].create("blah")
// Either[String, MyType] = Right("blah")

RefinedCtor[MyType, String].create("")
// Either[String, MyType] = Left("Invalid value: []. It has to be non-empty String but got \"\"")

NOTE: This ticket is only about adding RefinedCtor[T, A], so providing RefinedCtor for Refined and InlinedRefined will be done separately.


Add RefinedCtor type-class instance for RefinedBase (#51)


Add A.refinedTo[T] syntax to create Refined[A] and InlinedRefined[A] (#53)

type MyType = MyType.Type
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
object MyType extends Refined[String] {

  override inline def invalidReason(a: String): String =
    "It has to be non-empty String but got \"" + a + "\""

  override inline def predicate(a: String): Boolean = a != ""
}
import refined4s.syntax.*

"blah".refinedTo[MyType]
// Either[String, MyType#Type] = blah

"".refinedTo[MyType]
// Either[String, MyType#Type] = Invalid value: []. It has to be a non-empty String but got ""

Add T.coerce[A] syntax to easily get the value A from Refined[A]#Type and InlinedRefined[A]#Type (#55)

type MyType = MyType.Type
object MyType extends Refined[String] {

  override inline def invalidReason(a: String): String =
    "It has to be a non-empty String but got \"" + a + "\""

  override inline def predicate(a: String): Boolean = a != ""
}
import refined4s.syntax.*

val myType = MyType("blah")
myType.coerce[String]
// String = blah

myType.coerce[Int]
// Compile-time error:
// no given instance of type refined4s.Coercible[MyType.Type, Int] was found
// for parameter coercible of method coerce in object syntax

Add refinedNewtype syntax to create Newtype containing Refined[A] or InlinedRefined[A] (#57)

type MyType = MyType.Type
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
object MyType extends Refined[String] {

  override inline def invalidReason(a: String): String =
    "It has to be a non-empty String but got \"" + a + "\""

  override inline def predicate(a: String): Boolean = a != ""
}

type NewMyType = NewMyType.Type
object NewMyType extends Newtype[MyType]
import refined4s.syntax.*

NewMyType(MyType("blah"))
// Either[String, NewMyType#Type] = Right(NewType#Type(MyType("blah")))

NewMyType(MyType(""))
// Either[String, NewMyType#Type] =
//   Left("Failed to create NewMyType: Invalid value: []. It has to be a non-empty String but got \"\"")

Add refined4s-cats module and add validateAs syntax to create Newtype containing Refined[A] or InlinedRefined[A] (#59)

type MyType = MyType.Type
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
object MyType extends Refined[String] {

  override inline def invalidReason(a: String): String =
    "It has to be a non-empty String but got \"" + a + "\""

  override inline def predicate(a: String): Boolean = a != ""

  given eqMyType: Eq[MyType] = deriving[Eq]

  given showMyType: Show[MyType] = deriving[Show]
}

type NewMyType = NewMyType.Type
object NewMyType extends Newtype[MyType]
import refined4s.cats.syntax.*

"blah".validateAs[NewMyType]
// EitherNec[String, NewType#Type] = Right(NewMyType(NewMyType("blah")))

validateAs("blah")[NewMyType]
// EitherNec[String, NewType#Type] = Right(NewMyType(NewMyType("blah")))

"".validateAs[NewMyType]
// EitherNec[String, NewType#Type] =
//   Left("Failed to create NewMyType: Invalid value: []. It has to be a non-empty String but got \"\"")

validateAs("")[NewMyType]
// EitherNec[String, NewType#Type] =
//   Left("Failed to create NewMyType: Invalid value: []. It has to be a non-empty String but got \"\"")

Add toValue syntax to get the value from Newtype containing Refined[A] or InlinedRefined[A] (#61)

type MyType = MyType.Type
object MyType extends Refined[String] {

  override inline def invalidReason(a: String): String =
    "It has to be a non-empty String but got \"" + a + "\""

  override inline def predicate(a: String): Boolean = a != ""
}

type NewMyType = NewMyType.Type
object NewMyType extends Newtype[MyType]
import refined4s.syntax.*

val newMyType = NewMyType(MyType("blah"))
newMyType.toValue
// String = "blah"

[refined4s-cats] Add CatsEq, CatsShow and CatsEqShow to derive Eq and Show from the actual type's Eq and Show (#63)

type MyNewtype = MyNewtype.Type
object MyNewtype extends Newtype[String] with CatsEq[String]

type MyRefinedType = MyRefinedType.Type
object MyRefinedType extends Refined[String] with CatsEq[String] {
  override inline def invalidReason(a: String): String =
    "It has to be a non-empty String but got \"" + a + "\""

  override inline def predicate(a: String): Boolean = a != ""
}

type MyRefinedNewtype = MyRefinedNewtype.Type
object MyRefinedNewtype extends Newtype[MyRefinedType] with CatsEq[MyRefinedType]
type MyNewtype = MyNewtype.Type
object MyNewtype extends Newtype[String] with CatsShow[String]

type MyRefinedType = MyRefinedType.Type
object MyRefinedType extends Refined[String] with CatsShow[String] {
  override inline def invalidReason(a: String): String =
    "It has to be a non-empty String but got \"" + a + "\""

  override inline def predicate(a: String): Boolean = a != ""
}

type MyRefinedNewtype = MyRefinedNewtype.Type
object MyRefinedNewtype extends Newtype[MyRefinedType] with CatsShow[MyRefinedType]
type MyNewtype = MyNewtype.Type
object MyNewtype extends Newtype[String] with CatsEqShow[String]

type MyRefinedType = MyRefinedType.Type
object MyRefinedType extends Refined[String] with CatsEqShow[String] {
  override inline def invalidReason(a: String): String =
    "It has to be a non-empty String but got \"" + a + "\""

  override inline def predicate(a: String): Boolean = a != ""
}

type MyRefinedNewtype = MyRefinedNewtype.Type
object MyRefinedNewtype extends Newtype[MyRefinedType] with CatsEqShow[MyRefinedType]

Use deriving for numericOrdering: Ordering[Type] in Numeric[A] (#65)

trait Numeric[A: math.Ordering] extends Refined[A] {
  given numericOrdering: Ordering[Type]

  // ...
}

can be

trait Numeric[A: math.Ordering] extends Refined[A] {
  given numericOrdering: Ordering[Type] = deriving[Ordering]

  // ...
}

so Numbers don't have to be like this

type NegInt = NegInt.Type
object NegInt extends Numeric[Int] {
  override inline def invalidReason(a: Int): String = expectedMessage("a negative Int")

  override inline def predicate(a: Int): Boolean = a < 0

  override given numericOrdering: Ordering[NegInt] =
    (x, y) => scala.math.Numeric.IntIsIntegral.compare(x.value, y.value)
}

but it can be

type NegInt = NegInt.Type
object NegInt extends Numeric[Int] {
  override inline def invalidReason(a: Int): String = expectedMessage("a negative Int")

  override inline def predicate(a: Int): Boolean = a < 0
}

[core] Add NegShort, NonNegShort, PosShort and NonPosShort to numeric package (#67)


[core] Add NegByte, NonNegByte, PosByte and NonPosByte to numeric package (#70)


[core] Add NegFloat, NonNegFloat, PosFloat and NonPosFloat to numeric package (#72)


[core] Add NegDouble, NonNegDouble, PosDouble and NonPosDouble to numeric package (#76)


[core] Add NegBigInt, NonNegBigInt, PosBigInt and NonPosBigInt to numeric package (#79)

[core] Add NegBigDecimal, NonNegBigDecimal, PosBigDecimal and NonPosBigDecimal to numeric package (#83)

NOTE: Also InlinedNumeric was added to support compile-time validation for the refined BigInt and refined BigDecimal.

InlinedNumeric[A: math.Ordering] provides Ordering[InlinedNumeric[A]#Type] derived from A and Ordered[InlinedNumeric[A]#Type] converted from Ordering[InlinedNumeric[A]#Type].


[core] Add Uri to network package (#85)

import refined4s.types.network.*

Uri("https://github.com/kevin-lee/refined4s")
// InlinedRefined[String]#Type = "https://github.com/kevin-lee/refined4s"

Uri("%^<>[]`{}")
// Compile-time error
import refined4s.types.network.*

val uri = Uri("https://github.com/kevin-lee/refined4s")
uri.value
// String = "https://github.com/kevin-lee/refined4s"

uri.toURI
// java.net.URI = URI("https://github.com/kevin-lee/refined4s")

[core] Add types package and move numeric, strings and network to it (#87)

import refined4s.types.numeric.*
import refined4s.types.strings.*
import refined4s.types.network.*

[core] Add all package to refined4s.types and make it contain all packages in the types package (#89)

So the following

import refined4s.types.all.*

is equivalent to

import refined4s.types.numeric.*
import refined4s.types.strings.*
import refined4s.types.network.*