newtype and refinement (refined) type for Scala 3
MIT License
Bot releases are visible (Hide)
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
Published by kevin-lee 6 months ago
[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)
Published by kevin-lee 7 months ago
refined4s-tapir
to support tapir
(#272)refined4s-tapir
] Add TapirNewtypeSchema
and TapirRefinedSchema
to support sttp.tapir.Schema
for refined4s
(#273)refined4s-tapir
] Add Schema
s for pre-defined refined types (#276)refined4s-tapir
] Add Schema
type-class instances with auto deriving (#278)Published by kevin-lee 9 months ago
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)Published by kevin-lee 9 months ago
[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
.
refined4s-refined-compat
modules for compatibility with the refined
library in Scala 2 (#241)
refined4s-refined-compat-scala2
for compatibility with refined
in Scala 2refined4s-refined-compat-scala3
for just using refined4s
in Scala 3RefinedCompatAllTypes
to refined4s-refined-compat-scala2
and refined4s-refined-compat-scala3
(#243)[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
}
Published by kevin-lee 10 months ago
[refined4s-core
] Add MinValue
and MaxValue
to refined basic numeric types (#223)
Min
traitMax
traitMinMax
traitAdd 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
refined4s-core
] Move Numeric
and InlinedNumeric
from numeric
trait to numeric
object (#225)Published by kevin-lee 10 months ago
refined4s-extras-render
module (#220)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
Published by kevin-lee 10 months ago
NOTE:
refined4s-extras-render
was not released by mistake, and it will be released inv0.10.0
.
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
Published by kevin-lee 10 months ago
[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 theunapply
methods can be found at https://github.com/scala/bug/issues/12232. 😔
Published by kevin-lee 10 months ago
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"
[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
refined4s-core
] Replace Ordering
and Conversion[Type, Ordered[Type]]
for numeric types with CanBeOrdered
(#199)Published by kevin-lee 10 months ago
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)
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.*
Published by kevin-lee 10 months ago
[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.
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
[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.
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]]
Published by kevin-lee 10 months ago
refined4s-doobie-ce2
and refined4s-doobie-ce3
modules to support doobie (#123)refined4s-doobie
] Add Get
s 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)Published by kevin-lee 10 months ago
refined4s-cats
] Replace Eq
and Show
instances for the existing refined types with the ones derived from the actual types using Coercible
(#107)Add refined4s-circe
module to support circe (#101)
[refined4s-circe
] Add Encoder
and Decoder
s 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 ConfigWriter
s 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)
Published by kevin-lee 10 months ago
refined4s.cats
to refined4s.modules.cats
(#94)
import refined4s.modules.cats.derivation.*
import refined4s.modules.cats.syntax.*
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
Published by kevin-lee 10 months ago
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
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
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
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
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
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
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]
.
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
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
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
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
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.
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
}
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
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
.
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
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
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.
RefinedCtor
type-class instance for RefinedBase
(#51)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 ""
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
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 \"\"")
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 \"\"")
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]
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 Number
s 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
}
NegShort
, NonNegShort
, PosShort
and NonPosShort
to numeric
package (#67)NegByte
, NonNegByte
, PosByte
and NonPosByte
to numeric
package (#70)NegFloat
, NonNegFloat
, PosFloat
and NonPosFloat
to numeric
package (#72)NegDouble
, NonNegDouble
, PosDouble
and NonPosDouble
to numeric
package (#76)NegBigInt
, NonNegBigInt
, PosBigInt
and NonPosBigInt
to numeric
package (#79)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]
.
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")
types
package and move numeric
, strings
and network
to it (#87)import refined4s.types.numeric.*
import refined4s.types.strings.*
import refined4s.types.network.*
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.*