memeid
is a JVM library for generating RFC-compliant Universal Unique Identifiers (UUIDs).
A universally unique identifier (UUID) is a 128-bit number used to identify information in computer systems.
When generated according to the standard methods, UUIDs are for practical purposes unique. Their uniqueness does not depend on a central registration authority or coordination between the parties generating them, unlike most other numbering schemes. -- Wikipedia article on UUIDs
The UUID type that ships with the JVM java.util.UUID
has a number of problems, namely:
- A bug in the comparison function that will never be fixed
- Only provides UUID generation for random (V4) and non-namespaced pseudo-V3 UUIDs
This library aims to solve the aforementioned issues and provide an RFC-compliant UUID
type with coherent comparison, a rich API to get its different fields and constructors for the UUID variants.
<dependency>
<groupId>com.47deg</groupId>
<artifactId>memeid</artifactId>
<version>0.1</version>
</dependency>
compile group: 'com.47deg', name: 'memeid', version: '0.1'
Add this to your build.sbt
file:
libraryDependencies += "com.47deg" %% "memeid4s" % "0.1"
The time-based (V1) variant of UUIDs is the fastest to generate. It uses a monotonic clock and node information to generate UUIDs.
import memeid4s.UUID
UUID.V1.next
The cryptographically random variant, equivalent to java.util.UUID/randomUUID
.
UUID.V4.random
Namespaced UUIDs are generated from a UUID (namespace) and a hashed value (name), V3 uses MD5 and V5 uses SHA1 hash.
val namespace = UUID.V1.next
We can now create UUIDs with the namespace and an arbitrary value as the name. It automatically works with Strings and UUIDs:
UUID.V3(namespace, "my-secret-code")
If you want to hash a custom type, you must provide an implicit memeid4s.digest.Digestible
instance.
import memeid4s.digest.Digestible
case class User(firstName: String, lastName: String)
implicit val digestibleUser: Digestible[User] =
(u: User) => u.firstName.getBytes ++ u.lastName.getBytes
The implicit instance is used to convert your type into a byte array for hashing:
UUID.V3(namespace, User("Federico", "García Lorca"))
SQUUIDs are a non-standard variaton of V4 UUIDs that are semi-sequential. They incorporate a time-component in their 32 most significant bits to generate UUIDs that don't fragment DB indexes.
UUID.V4.squuid
memeid
provides conversion method between UUID
and java.util.UUID
through:
val j = java.util.UUID.fromString("a5fa7934-501c-46eb-9ea7-16de3086e6d8")
val u = memeid.UUID.fromString("8b4d1529-5fd0-4a91-8f4f-ceee10d1c060")
UUID.fromUUID(j)
// res5: UUID = a5fa7934-501c-46eb-9ea7-16de3086e6d8
u.asJava
// res6: java.util.UUID = 8b4d1529-5fd0-4a91-8f4f-ceee10d1c060
memeid
provides literal syntax with compile-time verification for UUIDs with the uuid
interpolator. To use it, add this to your build.sbt
:
libraryDependencies += "com.47deg" %% "memeid4s-literal" % "0.1"
We can now create UUIDs with literal syntax by importing memeid.literal._
import memeid4s.literal._
uuid"cb096727-6a82-4abd-bc79-fc92be8c5d88"
// res7: UUID = cb096727-6a82-4abd-bc79-fc92be8c5d88
Invalid UUID literals will fail at compile time:
uuid"not-a-uuid"
// error: invalid UUID: not-a-uuid
// uuid"not-a-uuid"
// ^^^^^^^^^^^^^^^^
memeid
provides several modules which integrate with popular third-party libraries. If you see something missing don't hesitate to open an issue or send a patch.
The Doobie integration allows you to use the UUID
type mapped to your database's UUID type.
libraryDependencies += "com.47deg" %% "memeid4s-doobie" % "0.1"
To have the UUID mappings available in scope you can import memeid.doobie.implicits
.
import memeid4s.doobie.implicits._
def select(uuid: UUID): Query0[UUID] =
sql"""SELECT id from test where id = ${uuid}""".query[UUID]
def insert(uuid: UUID): Update0 =
sql"""insert into test (id) values ($uuid)""".update
val example = uuid"58d61328-1b08-1171-1ee7-1283ed639e77"
{
for {
_ <- insert(example).run.transact(transactor)
u <- select(example).unique.transact(transactor)
} yield u
}.unsafeRunSync()
// res10: UUID = 58d61328-1b08-1171-1ee7-1283ed639e77
libraryDependencies += "com.47deg" %% "memeid4s-circe" % "0.1"
You can import memeid.circe.implicits
to have the Encoder
and Decoder
instances for UUID
in scope.
import io.circe.{ Json, Encoder, Decoder }
import memeid4s.circe.implicits._
val uuid = uuid"58d61328-1b08-1171-1ee7-1283ed639e77"
val json = Json.fromString(uuid.toString)
Encoder[UUID].apply(uuid)
// res11: Json = JString("58d61328-1b08-1171-1ee7-1283ed639e77")
Decoder[UUID].decodeJson(json)
// res12: Decoder.Result[UUID] = Right(58d61328-1b08-1171-1ee7-1283ed639e77)
libraryDependencies += "com.47deg" %% "memeid4s-http4s" % "0.1"
Using UUID
companion object we can extract UUIDs from path parameters in URLs:
import cats.effect._
import org.http4s._
import org.http4s.dsl.io._
HttpRoutes.of[IO] {
case GET -> Root / "user" / UUID(uuid) => Ok(s"Hello, ${uuid}!")
}
The http4s integrations provides implicit instances for QueryParamDecoder[UUID]
and QueryParamEncoder[UUID]
, which you can use to derive matchers for query parameters or send UUID in request query parameters.
import cats.effect._
import org.http4s._
import org.http4s.dsl.io._
import memeid4s.http4s.implicits._
object UUIDParamDecoder extends QueryParamDecoderMatcher[UUID]("uuid")
HttpRoutes.of[IO] {
case GET -> Root / "user" :? UUIDParamDecoder(uuid) => Ok(s"Hello, ${uuid}!")
}
libraryDependencies += "com.47deg" %% "memeid4s-cats" % "0.1"
The cats integration provides typeclass implementation for UUID
, as well as effectful constructors for UUIDs for integration with programs that use cats-effect
.
import cats._
import memeid4s.cats.implicits._
Order[UUID]
Hash[UUID]
Eq[UUID]
Show[UUID]
UUID.random[IO]
UUID.v3[IO, String](namespace, "my-secret-code")
UUID.v5[IO, String](namespace, "my-secret-code")
libraryDependencies += "com.47deg" %% "memeid4s-scalacheck" % "0.1"
The scalacheck integration provides Arbitrary
instances for the UUID
, as well as for the different version classes.
import org.scalacheck.Arbitrary.arbitrary
import memeid4s.scalacheck.arbitrary.instances._
arbitrary[UUID]
arbitrary[UUID.V1]
arbitrary[UUID.V2]
arbitrary[UUID.V3]
arbitrary[UUID.V4]
arbitrary[UUID.V5]
sbt-jmh
is used for executing the benchmarking tests.
There are 2 kind of benchmarking:
runAvgtime
: Measures the average time it takes for the benchmark method to execute (a single execution). Generates themaster.avgtime.csv
file in thebench
folder.runThroughput
: Measures the number of operations per second, meaning the number of times per second your benchmark method could be executed. Generates themaster.throughput.csv
file in thebench
folder.
- RFC 4122 - A Universally Unique Identifier
- JVM UUID type
- clj-uuid Clojure implementation