Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FiniteString[N] #479

Merged
merged 8 commits into from
Apr 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,11 @@ lazy val moduleJvmSettings = Def.settings(
"eu.timepit.refined.scalacheck.NumericInstances.*"),
ProblemFilters.exclude[ReversedMissingMethodProblem](
"eu.timepit.refined.scalacheck.NumericInstances.*"),
ProblemFilters.exclude[InheritedNewAbstractMethodProblem]("eu.timepit.refined.types.*")
ProblemFilters.exclude[InheritedNewAbstractMethodProblem]("eu.timepit.refined.types.*"),
ProblemFilters.exclude[ReversedMissingMethodProblem](
"eu.timepit.refined.api.RefinedType.dealias"),
ProblemFilters.exclude[ReversedMissingMethodProblem](
"eu.timepit.refined.scalacheck.RefTypeInstances.checkArbitraryRefinedType")
)
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ trait RefinedType[FTP] extends Serializable {

val alias: F[T, P] =:= FTP

val dealias: FTP =:= F[T, P]

final def refine(t: T): Either[String, FTP] = {
val res = validate.validate(t)
if (res.isPassed) Right(alias(refType.unsafeWrap(t)))
Expand Down Expand Up @@ -55,5 +57,6 @@ object RefinedType {
override val refType: RefType[F] = rt
override val validate: Validate[T, P] = v
override val alias: F[T, P] =:= F0[T0, P0] = implicitly
override val dealias: F0[T0, P0] =:= F[T, P] = implicitly
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,44 @@
package eu.timepit.refined.types

import eu.timepit.refined.W
import eu.timepit.refined.api.{Refined, RefinedTypeOps}
import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.api.{Refined, RefinedType, RefinedTypeOps}
import eu.timepit.refined.collection.{MaxSize, NonEmpty}
import eu.timepit.refined.string.MatchesRegex
import shapeless.Witness

/** Module for `String` refined types. */
object string {

/** A `String` with length less than or equal to `N`. */
type FiniteString[N] = String Refined MaxSize[N]

object FiniteString {
class FiniteStringOps[N <: Int](
implicit
rt: RefinedType.AuxT[FiniteString[N], String],
wn: Witness.Aux[N]
) extends RefinedTypeOps[FiniteString[N], String] {

/** The maximum length of a `FiniteString[N]`. */
final val maxLength: N =
wn.value

/**
* Creates a `FiniteString[N]` from `t` by truncating it
* if it is longer than `N`.
*/
def truncate(t: String): FiniteString[N] =
Refined.unsafeApply(t.substring(0, math.min(t.length, maxLength)))
}

/** Creates a "companion object" for `FiniteString[N]` with a fixed `N`. */
def apply[N <: Int](
implicit
rt: RefinedType.AuxT[FiniteString[N], String],
wn: Witness.Aux[N]
): FiniteStringOps[N] = new FiniteStringOps[N]
}

/** A `String` that is not empty. */
type NonEmptyString = String Refined NonEmpty

Expand All @@ -25,6 +56,9 @@ object string {
}

trait StringTypes {
final type FiniteString[N] = string.FiniteString[N]
final val FiniteString = string.FiniteString

final type NonEmptyString = string.NonEmptyString
final val NonEmptyString = string.NonEmptyString

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,40 @@
package eu.timepit.refined.types

import eu.timepit.refined.W
import eu.timepit.refined.types.all._
import org.scalacheck.Prop._
import org.scalacheck.Properties

class StringTypesSpec extends Properties("StringTypes") {

final val FString3 = FiniteString[W.`3`.T]

property("FString3.from(str)") = forAll { (str: String) =>
FString3.from(str).isRight ?= (str.length <= FString3.maxLength)
}

property("""FString3.from("")""") = secure {
val str = ""
FString3.from(str).right.map(_.value) ?= Right(str)
}

property("""FString3.from("abc")""") = secure {
val str = "abc"
FString3.from(str).right.map(_.value) ?= Right(str)
}

property("""FString3.from("abcd")""") = secure {
val str = "abcd"
FString3.from(str) ?= Left(
"Predicate taking size(abcd) = 4 failed: Predicate (4 > 3) did not fail.")
}

property("""FString3.truncate(str)""") = forAll { (str: String) =>
val truncated = FString3.truncate(str)
truncated.value.length <= FString3.maxLength &&
(truncated.value ?= str.take(FString3.maxLength))
}

// Hashes for ""
object EmptyString {
val md5 = "d41d8cd98f00b204e9800998ecf8427e"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package eu.timepit.refined.scalacheck

import eu.timepit.refined.api.{RefType, Validate}
import eu.timepit.refined.api.{RefinedType, RefType, Validate}
import org.scalacheck.{Arbitrary, Cogen, Gen, Prop}

object reftype extends RefTypeInstances
Expand All @@ -15,6 +15,9 @@ trait RefTypeInstances {
v: Validate[T, P]): Prop =
Prop.forAll((tp: F[T, P]) => v.isValid(rt.unwrap(tp)))

def checkArbitraryRefinedType[FTP](implicit arb: Arbitrary[FTP], rt: RefinedType[FTP]): Prop =
Prop.forAll((tp: FTP) => rt.validate.isValid(rt.refType.unwrap(rt.dealias(tp))))

implicit def refTypeCogen[F[_, _], T: Cogen, P](implicit rt: RefType[F]): Cogen[F[T, P]] =
Cogen[T].contramap(tp => rt.unwrap(tp))
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import eu.timepit.refined.scalacheck.generic._
import eu.timepit.refined.scalacheck.numeric._
import eu.timepit.refined.scalacheck.string._
import eu.timepit.refined.string._
import eu.timepit.refined.types.string.FiniteString
import org.scalacheck.Properties

class StringArbitrarySpec extends Properties("StringArbitrary") {
Expand All @@ -20,5 +21,7 @@ class StringArbitrarySpec extends Properties("StringArbitrary") {

property("MaxSize") = checkArbitraryRefType[Refined, String, MaxSize[W.`16`.T]]

property("FiniteString[N]") = checkArbitraryRefinedType[FiniteString[W.`10`.T]]

property("Size[Equal]") = checkArbitraryRefType[Refined, String, Size[Equal[W.`8`.T]]]
}
4 changes: 4 additions & 0 deletions notes/0.9.0.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
([#457][#457] by [@nrinaudo][@nrinaudo])
* Add `HexString`, `MD5`, `SHA1`, `SHA224`, `SHA256`, `SHA384`,
and `SHA512` types ([#453][#453] by [@NeQuissimus][@NeQuissimus])
* Add `FiniteString[N]` type for `String`s with length less than or
equal to `N`. ([#437][#437], [#479][#479])

### Changes

Expand Down Expand Up @@ -71,6 +73,7 @@
[#433]: https://github.com/fthomas/refined/pull/433
[#434]: https://github.com/fthomas/refined/pull/434
[#435]: https://github.com/fthomas/refined/pull/435
[#437]: https://github.com/fthomas/refined/issues/437
[#438]: https://github.com/fthomas/refined/pull/438
[#447]: https://github.com/fthomas/refined/pull/447
[#449]: https://github.com/fthomas/refined/pull/449
Expand All @@ -86,6 +89,7 @@
[#471]: https://github.com/fthomas/refined/pull/471
[#475]: https://github.com/fthomas/refined/pull/475
[#478]: https://github.com/fthomas/refined/pull/478
[#479]: https://github.com/fthomas/refined/pull/479
[scala-dev/#446]: https://github.com/scala/scala-dev/issues/446

[@densh]: https://github.com/densh
Expand Down