Skip to content

Instantly share code, notes, and snippets.

View supermanue's full-sized avatar

Manuel Rodríguez Pascual supermanue

View GitHub Profile
type NameRestrictions = MatchesRegex[W.`"[a-zA-Z]+[a-zA-Z0-9]*(?:[_-][a-zA-Z0-9]+)*"`.T]
type NamespaceRestrictions = MatchesRegex[W.`"[a-zA-Z]+[a-zA-Z0-9]*(?:[._-][a-zA-Z0-9]+)*"`.T]
case class UserInput(name: String, namespace: String)
def updateVersion(input: UserInput): UserInput = {
val lastVersion: Int = getLatestVersion(name, namespace)
val schemaToStore: Schema = InputToSchemaIso.get((userInput, lastVersion + 1))
val result: Schema = store(schemaToStore)
return InputToSchemaIso.reverseGet(result)
}
def updateVersion(input: UserInput): Either[String, UserInput] =
for {
name <- refineV[NameRestrictions](input.name)
namespace <- refineV[NamespaceRestrictions](input.namespace)
version = getLatestVersion(name, namespace)
updatedVersion <- refineV[VersionRestrictions](version.value +1)
result = store(Schema(name, namespace, version))
} yield (UserInput(schema.name.value, schema.namespace.value))
sealed trait BusinessLogicError {val msg: String}
case class NonExistingSchema(name: String, namespace: String) extends BusinessLogicError {
override val msg: String = s"schema with name $name and namespace $namespace does not exist"
}
case class StorageError(schema: Schema) extends BusinessLogicError {
override val msg: String = s"schema ${schema.toString} could not be stored for whatever reason"
}
trait AppError {
val message: String
}
final case class DBError(error: String) extends AppError {
val message = s"DBError: $error"
}
final case class UserNotFound(id: Int) extends AppError {
val message = s"User with id $id not found"
@supermanue
supermanue / ErrorHandling.scala
Created March 20, 2022 15:55
Better error handling
#BEFORE
def get(id: Int): Task[User] =
SQL
.get(id)
.option
.transact(tnx)
.foldM(
err => Task.fail(err),
maybeUser => Task.require(UserNotFound(id))(Task.succeed(maybeUser))
)
@supermanue
supermanue / usingUsers.scala
Last active March 22, 2022 07:37
Using users
//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] =
@supermanue
supermanue / userWithRefinedTypes.scala
Created March 22, 2022 07:28
User with Refined types
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
@supermanue
supermanue / refinedTypesForUsers.scala
Created March 22, 2022 07:35
Refined Types for users
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))
@supermanue
supermanue / typeGenerators.scala
Created March 26, 2022 17:31
type generators
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"))
)
}