All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.chrisdavenport.fuuid.FUUID.scala Maven / Gradle / Ivy

The newest version!
package io.chrisdavenport.fuuid

import cats._
import cats.data._
import cats.implicits._
import cats.effect.Sync
import java.util.UUID

import scala.reflect.macros.blackbox

final class FUUID private (private val uuid: UUID) {

  // Direct show method so people do not use toString
  def show: String = uuid.show
  // -1 less than, 0 equal to, 1 greater than
  def compare(that: FUUID): Int = this.uuid.compareTo(that.uuid)

  // Returns 0 when equal
  def eqv(that: FUUID): Boolean = compare(that) == 0

  override def equals(obj: scala.Any): Boolean = obj match {
    case that: FUUID => eqv(that)
    case _ => false
  }
  override def hashCode: Int = uuid.hashCode
  override def toString: String = uuid.toString

}

object FUUID {
  implicit val instancesFUUID: Hash[FUUID] with Order[FUUID] with Show[FUUID] =
    new Hash[FUUID] with Order[FUUID] with Show[FUUID] {
      override def show(t: FUUID): String = t.show
      override def eqv(x: FUUID, y: FUUID): Boolean = x.eqv(y)
      override def hash(x: FUUID): Int = x.hashCode
      override def compare(x: FUUID, y: FUUID): Int = x.compare(y)
    }

  def fromString(s: String): Either[Throwable, FUUID] =
    Either.catchNonFatal(new FUUID(UUID.fromString(s)))

  /**
   * Attempt to parse a UUID from a `String` accumulating errors in a
   * `cats.data.NonEmptyList` on failure.
   *
   * This is useful when you wish to parse more than one UUID at a time and
   * return all the failures, not just the first one. For example,
   *
   * {{{
   * scala> import cats._, cats.data._, cats.implicits._, io.chrisdavenport.fuuid._
   * import cats._
   * import cats.data._
   * import cats.implicits._
   * import io.chrisdavenport.fuuid._
   *
   * scala> (FUUID.fromStringVNel("a"), FUUID.fromStringVNel("b")).mapN((a, b) => println(s"\$a \$b"))
   * res0: cats.data.ValidatedNel[Throwable,Unit] = Invalid(NonEmptyList(java.lang.IllegalArgumentException: Invalid UUID string: a, java.lang.IllegalArgumentException: Invalid UUID string: b))
   * }}}
   */
  def fromStringVNel(s: String): ValidatedNel[Throwable, FUUID] =
    fromStringAccumulating[ValidatedNel[Throwable, *], NonEmptyList](s)

  /**
   * Attempt to parse a UUID from a `String` accumulating errors in a
   * `cats.data.NonEmptyChain` on failure.
   *
   * This is useful when you wish to parse more than one UUID at a time and
   * return all the failures, not just the first one. For example,
   *
   * {{{
   * scala> import cats._, cats.data._, cats.implicits._, io.chrisdavenport.fuuid._
   * import cats._
   * import cats.data._
   * import cats.implicits._
   * import io.chrisdavenport.fuuid._
   *
   * scala> (FUUID.fromStringVNec("a"), FUUID.fromStringVNec("b")).mapN((a, b) => println(s"\$a \$b"))
   * res0: cats.data.ValidatedNec[Throwable,Unit] = Invalid(NonEmptyChain(java.lang.IllegalArgumentException: Invalid UUID string: a, java.lang.IllegalArgumentException: Invalid UUID string: b))
   * }}}
   */
  def fromStringVNec(s: String): ValidatedNec[Throwable, FUUID] =
    fromStringAccumulating[ValidatedNec[Throwable, *], NonEmptyChain](s)

  def fromStringOpt(s: String): Option[FUUID] =
    fromString(s).toOption

  def fromStringF[F[_]](s: String)(implicit AE: ApplicativeError[F, Throwable]): F[FUUID] =
    fromString(s).fold(AE.raiseError, AE.pure)

  /**
   * Like [[#fromStringF]] but using an `Applicative` of `Throwable`.
   *
   * Generally this will be used with something like `cats.data.Validated` to
   * accumulate errors when parsing more than one [[FUUID]].
   *
   * See [[#fromStringVNel]] and [[#fromStringVNec]] for examples.
   */
  def fromStringAccumulating[F[_], E[_]](
      s: String
  )(implicit A: Applicative[E], AE: ApplicativeError[F, E[Throwable]]): F[FUUID] =
    fromString(s).fold(e => AE.raiseError(A.pure(e)), AE.pure)

  def fromUUID(uuid: UUID): FUUID = new FUUID(uuid)

  def randomFUUID[F[_]: Sync]: F[FUUID] = Sync[F].delay(
    new FUUID(UUID.randomUUID)
  )

  def fuuid(s: String): FUUID = macro Macros.fuuidLiteral

  private[FUUID] class Macros(val c: blackbox.Context) {
    import c.universe._
    def fuuidLiteral(s: c.Expr[String]): c.Expr[FUUID] =
      s.tree match {
        case Literal(Constant(s: String)) =>
          fromString(s)
            .fold(
              e => c.abort(c.enclosingPosition, e.getMessage.replace("UUID", "FUUID")),
              _ => c.Expr(q"""
                @SuppressWarnings(Array("org.wartremover.warts.Throw"))
                val fuuid = _root_.io.chrisdavenport.fuuid.FUUID.fromString($s).fold(throw _, _root_.scala.Predef.identity)
                fuuid
              """)
            )
        case _ =>
          c.abort(
            c.enclosingPosition,
            s"This method uses a macro to verify that a FUUID literal is valid. Use FUUID.fromString if you have a dynamic value you want to parse as an FUUID."
          )
      }
  }

  /**
   * Creates a new name-based UUIDv5.
   * NOTE: Not implemented for Scala.js!
   */
  def nameBased[F[_]](namespace: FUUID, name: String)(implicit
      AE: ApplicativeError[F, Throwable]
  ): F[FUUID] =
    PlatformSpecificMethods.nameBased[F](namespace, name, AE)

  /**
   * A Home For functions we don't trust
   * Hopefully making it very clear that this code needs
   * to be dealt with carefully.
   *
   * Likely necessary for some interop
   *
   * Please do not import directly but prefer `FUUID.Unsafe.xxx`
   */
  object Unsafe {
    def toUUID(fuuid: FUUID): UUID = fuuid.uuid
    def withUUID[A](fuuid: FUUID)(f: UUID => A): A = f(fuuid.uuid)
  }

  /**
   * The Nil UUID.
   *
   * This is a constant UUID for which all bits are 0.
   *
   * @see [[https://tools.ietf.org/html/rfc4122#section-4.1.7]]
   */
  val NilUUID: FUUID =
    FUUID.fromUUID(new UUID(0L, 0L))
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy