Skip to content

Commit

Permalink
Separate jvm and js implementation; fix test class
Browse files Browse the repository at this point in the history
  • Loading branch information
LukaJCB committed Dec 9, 2019
1 parent 8c1c4bf commit 194477b
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.chrisdavenport.fuuid

import cats.ApplicativeError

object PlatformSpecificMethods {
def nameBased[F[_]]: (FUUID, String, ApplicativeError[F, Throwable]) => F[FUUID] = (_, _, AE) =>
AE.raiseError(new NotImplementedError("Name based UUIDs are not supported for Scala.js"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.chrisdavenport.fuuid

import cats._
import cats.implicits._
import java.util.UUID
import java.security.MessageDigest

object PlatformSpecificMethods {
def nameBased[F[_]]: (FUUID, String, ApplicativeError[F, Throwable]) => F[FUUID] = (namespace, name, AE) =>
Either
.catchNonFatal(
MessageDigest
.getInstance("SHA-1")
.digest(
uuidBytes(namespace) ++
name.getBytes("UTF-8")
)
)
.map { bs =>
val cs = bs.take(16) // Truncate 160 bits (20 bytes) SHA-1 to fit into our 128 bits of space
cs(6) = (cs(6) & 0x0f).asInstanceOf[Byte] /* clear version */
cs(6) = (cs(6) | 0x50).asInstanceOf[Byte] /* set to version 5, SHA1 UUID */
cs(8) = (cs(8) & 0x3f).asInstanceOf[Byte] /* clear variant */
cs(8) = (cs(8) | 0x80).asInstanceOf[Byte] /* set to IETF variant */
cs
}
.flatMap(
bs =>
Either.catchNonFatal {
val bb = java.nio.ByteBuffer.allocate(java.lang.Long.BYTES)
bb.put(bs, 0, 8)
bb.flip
val most = bb.getLong
bb.flip
bb.put(bs, 8, 8)
bb.flip
val least = bb.getLong
FUUID.fromUUID(new UUID(most, least))
}
)
.liftTo[F](AE)

private def uuidBytes(fuuid: FUUID): Array[Byte] = {
val bb = java.nio.ByteBuffer.allocate(2 * java.lang.Long.BYTES)
val uuid = FUUID.Unsafe.toUUID(fuuid)
bb.putLong(uuid.getMostSignificantBits)
bb.putLong(uuid.getLeastSignificantBits)
bb.array
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.chrisdavenport.fuuid

import cats.effect.IO
import org.specs2._

class FUUIDJvmSpec extends mutable.Specification with ScalaCheck {

"FUUID.nameBased" should {
"produce a valid UUID" in {
(for {
namespace <- FUUID.randomFUUID[IO]
namebased <- FUUID.nameBased[IO](namespace, "What up yo!")
parsed <- FUUID.fromStringF[IO](namebased.toString)
} yield parsed)
.attempt
.unsafeRunSync
.isRight must_=== true
}

"conform to an example" in {
val namespace = FUUID.fromStringF[IO]("dc79a6bc-de3c-5bc7-a877-712bea708d8f").unsafeRunSync()
val name = "What up yo!"

val expected = FUUID.fromStringF[IO]("1cce4593-d217-5b33-be0d-2e81462e79d3").unsafeRunSync()

FUUID.nameBased[IO](namespace, name).unsafeRunSync must_=== expected
}
}
}
44 changes: 3 additions & 41 deletions modules/core/src/main/scala/io/chrisdavenport/fuuid/FUUID.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import cats._
import cats.implicits._
import cats.effect.Sync
import java.util.UUID
import java.security.MessageDigest

import scala.reflect.macros.blackbox

Expand Down Expand Up @@ -76,48 +75,11 @@ object FUUID {
}

/**
* Creates a new name-based UUIDv5
* 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] =
Either
.catchNonFatal(
MessageDigest
.getInstance("SHA-1")
.digest(
uuidBytes(namespace) ++
name.getBytes("UTF-8")
)
)
.map { bs =>
val cs = bs.take(16) // Truncate 160 bits (20 bytes) SHA-1 to fit into our 128 bits of space
cs(6) = (cs(6) & 0x0f).asInstanceOf[Byte] /* clear version */
cs(6) = (cs(6) | 0x50).asInstanceOf[Byte] /* set to version 5, SHA1 UUID */
cs(8) = (cs(8) & 0x3f).asInstanceOf[Byte] /* clear variant */
cs(8) = (cs(8) | 0x80).asInstanceOf[Byte] /* set to IETF variant */
cs
}
.flatMap(
bs =>
Either.catchNonFatal {
val bb = java.nio.ByteBuffer.allocate(java.lang.Long.BYTES)
bb.put(bs, 0, 8)
bb.flip
val most = bb.getLong
bb.flip
bb.put(bs, 8, 8)
bb.flip
val least = bb.getLong
new FUUID(new UUID(most, least))
}
)
.liftTo[F]

private def uuidBytes(fuuid: FUUID): Array[Byte] = {
val bb = java.nio.ByteBuffer.allocate(2 * java.lang.Long.BYTES)
bb.putLong(fuuid.uuid.getMostSignificantBits)
bb.putLong(fuuid.uuid.getLeastSignificantBits)
bb.array
}
PlatformSpecificMethods.nameBased[F](namespace, name, AE)

/**
* A Home For functions we don't trust
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import java.util.UUID
import cats.effect.IO
import org.specs2._

object FUUIDSpec extends mutable.Specification with ScalaCheck {
class FUUIDSpec extends mutable.Specification with ScalaCheck {

"FUUID.fromString" should {
"Fail when parsing an invalid string" in {
Expand All @@ -23,28 +23,6 @@ object FUUIDSpec extends mutable.Specification with ScalaCheck {
}
}

"FUUID.nameBased" should {
"produce a valid UUID" in {
(for {
namespace <- FUUID.randomFUUID[IO]
namebased <- FUUID.nameBased[IO](namespace, "What up yo!")
parsed <- FUUID.fromStringF[IO](namebased.toString)
} yield parsed)
.attempt
.unsafeRunSync
.isRight must_=== true
}

"conform to an example" in {
val namespace = FUUID.fromStringF[IO]("dc79a6bc-de3c-5bc7-a877-712bea708d8f").unsafeRunSync()
val name = "What up yo!"

val expected = FUUID.fromStringF[IO]("1cce4593-d217-5b33-be0d-2e81462e79d3").unsafeRunSync()

FUUID.nameBased[IO](namespace, name).unsafeRunSync must_=== expected
}
}

"FUUID.hashCode" should {
"have same hashcode as uuid" in {
val baseString = "00000000-075b-cd15-0000-0000075bcd15"
Expand Down

0 comments on commit 194477b

Please sign in to comment.