From 4c4beb8d180d701e20823e1b28851a8661dc2044 Mon Sep 17 00:00:00 2001 From: Pat Garrity Date: Thu, 1 Aug 2024 08:47:35 -0500 Subject: [PATCH] Consistency, Docs, and QOL Improvements - Fixed consistency around `()` use. - Added `toBytes()` and `fromBytes(Array[Byte])`. - Added missing ScalaDoc. - Updated tests. - Updated all dependencies to latest. --- README.md | 4 +- build.sbt | 6 +-- project/build.properties | 2 +- project/plugins.sbt | 2 +- src/main/java/gs/uuid/v0/UUIDFormat.java | 6 +-- src/main/scala/gs/uuid/v0/UUID.scala | 66 +++++++++++++++++++---- src/test/scala/gs/uuid/v0/UUIDTests.scala | 37 ++++++++----- 7 files changed, 90 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 35c4c85..169c6b8 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # gs-uuid [GS Open Source](https://garrity.co/oss.html) | -[License (MIT)](./LICENSE) +[License (Apache 2.0)](./LICENSE) -UUID's for Scala 3 with generation based on JUG, and serialization based on code +UUIDs for Scala 3 with generation based on JUG, and serialization based on code from Jackson Databind. The only dependency is JUG, whereas the relevant Jackson code is copied to this implementation (and slightly modified). diff --git a/build.sbt b/build.sbt index 5069659..f3f293d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,4 @@ -val scala3: String = "3.4.1" +val scala3: String = "3.4.2" ThisBuild / scalaVersion := scala3 ThisBuild / versionScheme := Some("semver-spec") @@ -14,7 +14,7 @@ val sharedSettings = Seq( lazy val testSettings = Seq( libraryDependencies ++= Seq( - "org.scalameta" %% "munit" % "1.0.0-M10" % Test + "org.scalameta" %% "munit" % "1.0.0" % Test ) ) @@ -25,6 +25,6 @@ lazy val `gs-uuid` = project .settings(name := s"${gsProjectName.value}-v${semVerMajor.value}") .settings( libraryDependencies ++= Seq( - "com.fasterxml.uuid" % "java-uuid-generator" % "4.1.1" + "com.fasterxml.uuid" % "java-uuid-generator" % "5.1.0" ) ) diff --git a/project/build.properties b/project/build.properties index 04267b1..ee4c672 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.9 +sbt.version=1.10.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index e897854..c708d3a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -28,6 +28,6 @@ externalResolvers := Seq( "Garrity Software Releases" at "https://maven.garrity.co/gs" ) -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.11") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.1.0") addSbtPlugin("gs" % "sbt-garrity-software" % "0.3.0") addSbtPlugin("gs" % "sbt-gs-semver" % "0.3.0") diff --git a/src/main/java/gs/uuid/v0/UUIDFormat.java b/src/main/java/gs/uuid/v0/UUIDFormat.java index a4d5a33..e82915d 100644 --- a/src/main/java/gs/uuid/v0/UUIDFormat.java +++ b/src/main/java/gs/uuid/v0/UUIDFormat.java @@ -5,9 +5,9 @@ import java.util.Arrays; /** * UUID serialization and deserialization. This is a direct copy of Jackson - * Databind (also under the Apache 2.0 license at time of writing) with - * extremely minor modifications to remove dashes from the output and to - * likewise support parsing with/without dashes. + * Databind (under the Apache 2.0 license at time of writing) with extremely + * minor modifications to remove dashes from the output and to likewise support + * parsing with/without dashes. */ public final class UUIDFormat { private UUIDFormat() {} diff --git a/src/main/scala/gs/uuid/v0/UUID.scala b/src/main/scala/gs/uuid/v0/UUID.scala index 7dca251..f4e2954 100644 --- a/src/main/scala/gs/uuid/v0/UUID.scala +++ b/src/main/scala/gs/uuid/v0/UUID.scala @@ -16,19 +16,17 @@ import com.fasterxml.uuid.Generators * * ## Serialization * - * This library uses a custom variant of the JDK 17 implementation that removes - * dashes from the output and is likewise capable of parsing those values. + * This library supports the following representations: * - * {{{ - * val example: UUID = UUID(java.util.UUID.randomUUID()) - * val serialized = example.str() // or example.withoutDashes() - * // example value = 899efa6f40ed45189efa6f40ed9518ed - * }}} + * - Hexadecimal string without dashes + * - Byte array + * + * Serialization code is based on Jackson Databind (Apache 2.0). */ opaque type UUID = java.util.UUID object UUID: - /** Express any `java.util.UUID` as a Meager UUID. + /** Express any `java.util.UUID` as a GS UUID. * * @param uuid * The input UUID. @@ -37,6 +35,21 @@ object UUID: */ def apply(uuid: java.util.UUID): UUID = uuid + /** Parse a byte array as a [[UUID]]. + * + * See `toBytes()` for the inverse of this function. + * + * @param bytes + * The array of bytes to parse. + * @return + * The new [[UUID]], or `None` if the bytes do not represent a valid UUID. + */ + def fromBytes(bytes: Array[Byte]): Option[UUID] = + scala.util + .Try(UUIDFormat.fromBytes(bytes)) + .map(uuid => Some(apply(uuid))) + .getOrElse(None) + given CanEqual[UUID, UUID] = CanEqual.derived /** Generate a new UUID. @@ -62,6 +75,14 @@ object UUID: */ def parse(str: String): Option[UUID] = fromString(str) + /** Parse the given string as a UUID. + * + * @param str + * The UUID, which is expected to be in a hexadecimal format with no + * dashes. + * @return + * The parsed UUID value, or `None` if the value does not represent a UUID. + */ def fromString(str: String): Option[UUID] = scala.util .Try(UUIDFormat.fromHex(str)) @@ -69,16 +90,39 @@ object UUID: .getOrElse(None) extension (uid: UUID) - def toUUID: java.util.UUID = uid + /** @return + * The underlying `java.util.UUID`. + */ + def toUUID(): java.util.UUID = uid + /** @return + * The byte array representation of this UUID. + */ + def toBytes(): Array[Byte] = UUIDFormat.toBytes(uid) + + /** @return + * Hexadecimal string representation of this UUID, without dashes. + */ def str(): String = withoutDashes() + /** @return + * Hexadecimal string representation of this UUID, without dashes. + */ def withoutDashes(): String = UUIDFormat.toHex(uid) + /** @return + * The least significant bits of this UUID. + */ def lsb(): Long = uid.getLeastSignificantBits() + /** @return + * The most significant bits of this UUID. + */ def msb(): Long = uid.getMostSignificantBits() + /** @return + * `true` if this UUID is `0`, `false` otherwise. + */ def isZero(): Boolean = lsb() == 0L && msb() == 0L /** Type class for UUID generation. @@ -91,11 +135,11 @@ object UUID: object Generator: /** Instantiate a new Type 4 generator. */ - def version4: Generator = new Version4 + def version4(): Generator = new Version4 /** Instantiate a new Type 7 generator. */ - def version7: Generator = new Version7 + def version7(): Generator = new Version7 /** Type 4 (Random) implementation of a UUID generator. */ diff --git a/src/test/scala/gs/uuid/v0/UUIDTests.scala b/src/test/scala/gs/uuid/v0/UUIDTests.scala index 2bda4f5..3c962ce 100644 --- a/src/test/scala/gs/uuid/v0/UUIDTests.scala +++ b/src/test/scala/gs/uuid/v0/UUIDTests.scala @@ -1,8 +1,8 @@ package gs.uuid.v0 class UUIDTests extends munit.FunSuite: - private val v4 = UUID.Generator.version4 - private val v7 = UUID.Generator.version7 + private val v4 = UUID.Generator.version4() + private val v7 = UUID.Generator.version7() given CanEqual[java.util.UUID, java.util.UUID] = CanEqual.derived test( @@ -11,7 +11,7 @@ class UUIDTests extends munit.FunSuite: val base = v4.next() val str = base.str() val parsed = UUID.parse(str) - assert(parsed == Some(base)) + assertEquals(parsed, Some(base)) } test( @@ -20,7 +20,7 @@ class UUIDTests extends munit.FunSuite: val base = v7.next() val str = base.str() val parsed = UUID.parse(str) - assert(parsed == Some(base)) + assertEquals(parsed, Some(base)) } test("should instantiate from any java.util.UUID") { @@ -28,18 +28,31 @@ class UUIDTests extends munit.FunSuite: val base = UUID(raw) val str = base.str() val parsed = UUID.fromString(str) - assert(parsed == Some(base)) - assert(parsed.map(_.toUUID) == Some(raw)) + assertEquals(parsed, Some(base)) + assertEquals(parsed.map(_.toUUID()), Some(raw)) } test("should successfully parse a UUID with dashes") { val base = java.util.UUID.randomUUID() - assert(UUID.parse(base.toString()) == Some(UUID(base))) + assertEquals(UUID.parse(base.toString()), Some(UUID(base))) } test("should fail to parse a non-hex string") { val input = "ghijklmnoped45189efa6f40ed9518ed" - assert(UUID.parse(input) == None) + assertEquals(UUID.parse(input), None) + } + + test("should round trip to/from a byte array") { + given UUID.Generator = v7 + val base = UUID.generate() + val bytes = base.toBytes() + val parsed = UUID.fromBytes(bytes) + assertEquals(parsed, Some(base)) + } + + test("should fail to parse an invalid byte array") { + val bytes = Array[Byte](1) + assertEquals(UUID.fromBytes(bytes), None) } test("should generate using an available type class instance") { @@ -47,14 +60,14 @@ class UUIDTests extends munit.FunSuite: val base = doGen val str = base.str() val parsed = UUID.parse(str) - assert(parsed == Some(base)) + assertEquals(parsed, Some(base)) } test("should return lsb, msb, and if the UUID is zero") { val uuid = UUID.parse("00000000-0000-0000-0000-000000000000") - assert(uuid.map(_.lsb()) == Some(0L)) - assert(uuid.map(_.msb()) == Some(0L)) - assert(uuid.map(_.isZero()) == Some(true)) + assertEquals(uuid.map(_.lsb()), Some(0L)) + assertEquals(uuid.map(_.msb()), Some(0L)) + assertEquals(uuid.map(_.isZero()), Some(true)) } private def doGen(