This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val DoobieVersion = "0.12.1" | |
libraryDependencies ++= Seq( | |
"org.tpolecat" %% "doobie-core" % DoobieVersion, | |
"org.tpolecat" %% "doobie-h2" % DoobieVersion, | |
//needed for functional stuff | |
"dev.zio" %% "zio-interop-cats" % "2.0.0.0-RC12" | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object UserServiceTest extends DefaultRunnableSpec with DomainFixtures { | |
def spec: ZSpec[TestEnvironment, Failure] = | |
suite("UserService unit test")( | |
testM("get a non existing user should fail") { | |
assertM(getUser(100).run)(fails(anything)) | |
}, | |
testM("create a user then get it should return the same user ") { | |
checkM(userGen) { user => |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
case class TestDB(users: Ref[Vector[User]]) extends StoragePort { | |
def get(id: Int): IO[AppError, User] = | |
users.get.flatMap(users => IO.require(UserNotFound(id))(Task.succeed(users.find(_.id.value == id)))) | |
def create(user: User): IO[AppError, User] = | |
users.update(_ :+ user).map(_ => user) | |
def delete(id: Int): Task[Boolean] = | |
users.modify(users => true -> users.filterNot(_.id.value == id)) | |
} | |
object TestDB { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object UserService { | |
def getUser(id: Int): ZIO[UserPersistence, AppError, User] = RIO.accessM(_.get.get(id)) | |
def createUser(id: Int, name: String): ZIO[UserPersistence, AppError, User] = | |
for { | |
user <- ZIO.fromEither(User.build(id, name)) | |
stored <- RIO.accessM[UserPersistence](_.get.create(user)) | |
} yield stored | |
def deleteUser(id: Int): RIO[UserPersistence, Boolean] = RIO.accessM(_.get.delete(id)) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object UserTest extends DefaultRunnableSpec with DomainFixtures { | |
def spec: ZSpec[TestEnvironment, Failure] = | |
suite("UserTest")( | |
testM("creating a user with valid input should succeed") { | |
check(positiveIntGen, nonemptyStringGen) { (positiveInt, nonemptyString) => | |
val user = User.build(positiveInt, nonemptyString) | |
assert(user)(isRight) | |
} | |
}, | |
testM("creating a user with id=0 should fail") { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object FixturesTest extends DefaultRunnableSpec with DomainFixtures { | |
def spec: ZSpec[TestEnvironment, Failure] = | |
suite("Fixtures test")( | |
testM("positiveIntGen generates positive ints") { | |
check(positiveIntGen) { positiveInt => | |
assert(positiveInt > 0)(isTrue) | |
} | |
}, | |
testM("nonemptyStringGen generates nonempty strings") { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
trait DomainFixtures { | |
def positiveIntGen: Gen[Random, Int] = anyInt.map(num => math.abs(num) + 1) | |
def nonemptyStringGen: Gen[Random with Sized, String] = (anyASCIIString <*> anyChar).map(elems => elems._1 + elems._2) | |
def userGen: Gen[Random with Sized, User] = | |
(positiveIntGen <*> nonemptyStringGen).map(elems => | |
User.build(elems._1, elems._2).getOrElse(throw new Exception("Exception in test building customer")) | |
) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Name = String Refined NameRestrictions | |
type Id = Int Refined IdRestrictions | |
//string between 1 and 1000 characters, not all of them white | |
type NameRestrictions = Size[Interval.Closed[W.`1`.T, W.`1000`.T]] And Not[Forall[Whitespace]] | |
type IdRestrictions = Positive | |
private def toRefinedId(id: Int): Either[RefinedTypeError, Id] = | |
refineV[IdRestrictions](id).left | |
.map(_ => RefinedTypeError("must be a positive int", id.toString)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object User { | |
sealed abstract case class User private (id: Id, name: Name) | |
object User { | |
def build(id: Int, name: String): Either[AppError, User] = | |
for { | |
refinedId <- toRefinedId(id) | |
refinedName <- toRefinedName(name) | |
} yield new User(refinedId, refinedName) {} | |
} | |
// previous GIST with refined type stuff |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//creating | |
val maybeUser: Either[RefinedTypeError, User] = User.build(input.id, input.name) | |
//accesing | |
val id = user.id.value | |
val name = user.name.value | |
//create user in the UserService needs a for comprehension | |
def createUser(id: Int, name: String): ZIO[UserPersistence, AppError, User] = |