Releases: kevin-lee/refined4s
v0.5.0
0.5.0 - 2023-12-23
New Features
-
[
refined4s-cats
] AddcontraCoercible
toF[B] => F[A]
withContravariant[F]
andCoercible[A, B]
(#152)This is useful to derive type-class instances for
Newtype
,Refined
andInlinedRefined
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 andA
could be a newtype or a refined type.
Improvement
- Add missing
inline
toinvalidReason
in the sub-types ofInlinedNumeric
(#146)
-
[
refined4s-pureconfig
] Add type name to the error message ofPureconfigRefinedConfigReader
andrefined4s.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 inrefined4s.modules.doobie.derivation.instances
inline
(#156)
-
[
refined4s-core
] Improve compile-time error message forinline 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
] Updaterefined4s.modules.circe.derivation.instances.derivedEncoder
to usecontraCoercible
and make itinline
(#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
andunwrap
suffixed withM
:M
toTC
(type constructor)wrap
andunwrap
suffixed withMOfM
:MOfM
toHKT
(higher-kinded type)M[*]
toF[*]
M1[*]
toF[*]
andM2[*]
toG[*]
e.g.)M1[M2[A]]
toF[G[A]]
v0.4.0
0.4.0 - 2023-12-16
New Features
- Add
refined4s-doobie-ce2
andrefined4s-doobie-ce3
modules to support doobie (#123) - [
refined4s-doobie
] AddGet
s andPut
forNewtype
,Refined
andInlinedRefined
withCoercible
andRefinedCtor
(#124) - [
refined4s-doobie
] AddDoobiePut
,DoobieNewtypeGet
,DoobieRefinedGet
,DoobieNewtypeGetPut
andDoobieRefinedGetPut
to have circeGet
andPut
derived from the actual type forNewtype
,Refined
andInlinedRefined
(#127)
v0.3.0
0.3.0 - 2023-12-15
Changes
- [
refined4s-cats
] ReplaceEq
andShow
instances for the existing refined types with the ones derived from the actual types usingCoercible
(#107)
New Features
-
[
refined4s-circe
] AddEncoder
andDecoder
s forNewtype
,Refined
andInlinedRefined
withCoercible
andRefinedCtor
(#103) -
[
refined4s-circe
] AddCirceEncoder
,CirceNewtypeDecoder
,CirceRefinedDecoder
,CirceNewtypeCodec
andCirceRefinedCodec
to have circeEncoder
andDecoder
derived from the actual type forNewtype
,Refined
andInlinedRefined
(#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
] AddPortNumber
,SystemPortNumber
,NonSystemPortNumber
,UserPortNumber
andDynamicPortNumber
(#110) -
Add
refined4s-pureconfig
module to supportpureconfig
(#112) -
[
refined4s-pureconfig
] AddConfigReader
andConfigWriter
s forNewtype
,Refined
andInlinedRefined
withCoercible
andRefinedCtor
(#113)import refined4s.modules.pureconfig.derivation.instances.given
-
[
refined4s-pureconfig
] AddPureconfigNewtypeConfigReader
andPureconfigRefinedConfigReader
to providerConfigReader
forNewtype
,Refined
andInlinedRefined
(#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
] AddPureconfigConfigWriter
to providerConfigWriter
forNewtype
,Refined
andInlinedRefined
(#118)
v0.2.0
0.2.0 - 2023-12-10
Changes
- Rename
refined4s.cats
torefined4s.modules.cats
(#94)import refined4s.modules.cats.derivation.* import refined4s.modules.cats.syntax.*
New Features
-
Add
Eq[A]
andShow[A]
instances for the existing refined types (#95)So with
import refined4s.modules.cats.derivation.instances.given
Eq
andShow
instances for the following types are availableNegInt
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
v0.1.0
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[Boolea...