Skip to content

Instantly share code, notes, and snippets.

@akkie
Created December 22, 2019 12:28
Show Gist options
  • Save akkie/e546ac030d47a90929dcc521cb97d0c7 to your computer and use it in GitHub Desktop.
Save akkie/e546ac030d47a90929dcc521cb97d0c7 to your computer and use it in GitHub Desktop.
/**
* Licensed to the Minutemen Group under one or more contributor license
* agreements. See the COPYRIGHT file distributed with this work for
* additional information regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package silhouette
import cats.data.EitherT
import cats.implicits._
import scala.concurrent.{ ExecutionContext, Future }
import scala.language.implicitConversions
import scala.util.Try
/**
* The type [[Maybe]] generalizes the types [[Future]], [[Try]], [[Either]] or [[Option]] so that they can be composed.
*
* The great thing about functions is that they compose. A function `A => B` can be composed with another function
* `B => C` to build a function `A => C`. To compose functions that return a monadic value we can use
* [[cats.data.Kleisli]] from Cats. The problem with [[cats.data.Kleisli]] is, that it only composes functions of the
* same monadic type. It cannot compose a function `A => Option[B]` with a function `B => Either[Error, C]`.
*
* Silhouette has a lot of functions that return a [[Future]], [[Try]], [[Either]] or [[Option]]. All of this types can
* represent a success and a failed state. So the idea was to generalize this types into the type [[Maybe]] so that they
* can be composed with [[cats.data.Kleisli]].
*
* The type [[Maybe]] is represented through the type `Future[Either[Throwable, A]]` because it combines all the
* functionality we need for our functions. It's async and it can either be an error or a successful value. We provide
* implicit converters for the types [[Future]], [[Try]], [[Either]] and [[Option]] that transform these types to
* [[Maybe]]. There is also a low priority converter that transforms any type to [[Maybe]]. This will work with types
* like [[Int]], [[String]] or other types but may not work with other monadic types. In such case an implicit converter
* should be created.
*/
object Maybe {
/**
* Option handles a missing case with [[None]] and not really an error case. This [[Throwable]] represents the
* [[None]] case for the [[Maybe]] type.
*/
case class NonException()
extends Throwable("None value detected! This indicates that a value was not found in an Fitting pipeline")
/**
* The type description of the type [[Maybe]].
*
* We use the monad transformer [[EitherT]] from the Cats library, because it allows us to easily compose [[Either]]
* and [[Future]] together without writing a lot of boilerplate.
*/
type Maybe[A] = EitherT[Future, Throwable, A]
/**
* A [[Writes]] that transform a type `A` to [[Maybe]].
*
* @tparam A The source type.
* @tparam B The target type.
*/
trait MaybeWrites[A, B] extends Writes[A, Maybe[B]]
/**
* A low priority [[Writes]] that transform any type to [[Maybe]].
*
* @param ec The implicit execution context.
* @tparam A The type to convert.
* @return The [[Maybe]] representation for type `A`.
*/
implicit def toMaybeWrites[A](
implicit
ec: ExecutionContext
): MaybeWrites[A, A] = (value: A) =>
EitherT.pure[Future, Throwable](value)
/**
* A [[Writes]] that transforms an [[Option]] to [[Maybe]].
*
* @param ec The implicit execution context.
* @tparam A The type to convert.
* @return The [[Maybe]] representation for the [[Option]] type.
*/
implicit def optionToMaybeWrites[A](
implicit
ec: ExecutionContext
): MaybeWrites[Option[A], A] = (value: Option[A]) =>
EitherT.fromEither[Future](value.toRight(NonException()))
/**
* A [[Writes]] that transforms a [[Try]] to [[Maybe]].
*
* @param ec The implicit execution context.
* @tparam A The type to convert.
* @return The [[Maybe]] representation for the [[Try]] type.
*/
implicit def tryToMaybeWrites[A](
implicit
ec: ExecutionContext
): MaybeWrites[Try[A], A] = (value: Try[A]) =>
EitherT.fromEither[Future](value.toEither)
/**
* A [[Writes]] that transforms an `Either[Throwable, A]` to [[Maybe]].
*
* @param ec The implicit execution context.
* @tparam A The type to convert.
* @return The [[Maybe]] representation for the `Either[Throwable, A]` type.
*/
implicit def eitherToMaybeWrites[A](
implicit
ec: ExecutionContext
): MaybeWrites[Either[Throwable, A], A] = (value: Either[Throwable, A]) =>
EitherT.fromEither[Future](value)
/**
* A [[Writes]] that transforms an `Either[Throwable, A]` to [[Maybe]].
*
* @param ec The implicit execution context.
* @tparam A The type to convert.
* @return The [[Maybe]] representation for the `Either[Throwable, A]` type.
*/
implicit def futureToMaybeWrites[A, B](
implicit
writes: MaybeWrites[A, B],
ec: ExecutionContext
): MaybeWrites[Future[A], B] = (value: Future[A]) =>
for {
v <- EitherT.right(value)
r <- writes.write(v)
} yield r
/**
* Converts a type with the help of a [[Writes]] into a [[Maybe]] type.
*
* @param value The value to convert.
* @param writes The [[Writes]] which converts the from `A` to [[Maybe]].
* @tparam A The source type.
* @tparam B The type of the success case [[Maybe]] handles.
* @return The [[Maybe]] representation of type `A`.
*/
implicit def toMaybe[A, B](value: A)(implicit writes: MaybeWrites[A, B]): Maybe[B] = writes.write(value)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment