Consistency, Docs, and QOL Improvements (#12)
All checks were successful
/ Build and Release Library (push) Successful in 1m17s

- Fixed consistency around `()` use.
- Added `toBytes()` and `fromBytes(Array[Byte])`.
- Added missing ScalaDoc.
- Updated tests.
- Updated all dependencies to latest.

Reviewed-on: #12
This commit is contained in:
Pat Garrity 2024-08-01 13:58:16 +00:00
parent 4370ed80b2
commit d0dd5c3833
7 changed files with 90 additions and 33 deletions

View file

@ -1,9 +1,9 @@
# gs-uuid # gs-uuid
[GS Open Source](https://garrity.co/oss.html) | [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 from Jackson Databind. The only dependency is JUG, whereas the relevant Jackson
code is copied to this implementation (and slightly modified). code is copied to this implementation (and slightly modified).

View file

@ -1,4 +1,4 @@
val scala3: String = "3.4.1" val scala3: String = "3.4.2"
ThisBuild / scalaVersion := scala3 ThisBuild / scalaVersion := scala3
ThisBuild / versionScheme := Some("semver-spec") ThisBuild / versionScheme := Some("semver-spec")
@ -14,7 +14,7 @@ val sharedSettings = Seq(
lazy val testSettings = Seq( lazy val testSettings = Seq(
libraryDependencies ++= 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(name := s"${gsProjectName.value}-v${semVerMajor.value}")
.settings( .settings(
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"com.fasterxml.uuid" % "java-uuid-generator" % "4.1.1" "com.fasterxml.uuid" % "java-uuid-generator" % "5.1.0"
) )
) )

View file

@ -1 +1 @@
sbt.version=1.9.9 sbt.version=1.10.1

View file

@ -28,6 +28,6 @@ externalResolvers := Seq(
"Garrity Software Releases" at "https://maven.garrity.co/gs" "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-garrity-software" % "0.3.0")
addSbtPlugin("gs" % "sbt-gs-semver" % "0.3.0") addSbtPlugin("gs" % "sbt-gs-semver" % "0.3.0")

View file

@ -5,9 +5,9 @@ import java.util.Arrays;
/** /**
* UUID serialization and deserialization. This is a direct copy of Jackson * UUID serialization and deserialization. This is a direct copy of Jackson
* Databind (also under the Apache 2.0 license at time of writing) with * Databind (under the Apache 2.0 license at time of writing) with extremely
* extremely minor modifications to remove dashes from the output and to * minor modifications to remove dashes from the output and to likewise support
* likewise support parsing with/without dashes. * parsing with/without dashes.
*/ */
public final class UUIDFormat { public final class UUIDFormat {
private UUIDFormat() {} private UUIDFormat() {}

View file

@ -16,19 +16,17 @@ import com.fasterxml.uuid.Generators
* *
* ## Serialization * ## Serialization
* *
* This library uses a custom variant of the JDK 17 implementation that removes * This library supports the following representations:
* dashes from the output and is likewise capable of parsing those values.
* *
* {{{ * - Hexadecimal string without dashes
* val example: UUID = UUID(java.util.UUID.randomUUID()) * - Byte array
* val serialized = example.str() // or example.withoutDashes() *
* // example value = 899efa6f40ed45189efa6f40ed9518ed * Serialization code is based on Jackson Databind (Apache 2.0).
* }}}
*/ */
opaque type UUID = java.util.UUID opaque type UUID = java.util.UUID
object UUID: object UUID:
/** Express any `java.util.UUID` as a Meager UUID. /** Express any `java.util.UUID` as a GS UUID.
* *
* @param uuid * @param uuid
* The input UUID. * The input UUID.
@ -37,6 +35,21 @@ object UUID:
*/ */
def apply(uuid: java.util.UUID): UUID = 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 given CanEqual[UUID, UUID] = CanEqual.derived
/** Generate a new UUID. /** Generate a new UUID.
@ -62,6 +75,14 @@ object UUID:
*/ */
def parse(str: String): Option[UUID] = fromString(str) 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] = def fromString(str: String): Option[UUID] =
scala.util scala.util
.Try(UUIDFormat.fromHex(str)) .Try(UUIDFormat.fromHex(str))
@ -69,16 +90,39 @@ object UUID:
.getOrElse(None) .getOrElse(None)
extension (uid: UUID) 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() def str(): String = withoutDashes()
/** @return
* Hexadecimal string representation of this UUID, without dashes.
*/
def withoutDashes(): String = UUIDFormat.toHex(uid) def withoutDashes(): String = UUIDFormat.toHex(uid)
/** @return
* The least significant bits of this UUID.
*/
def lsb(): Long = uid.getLeastSignificantBits() def lsb(): Long = uid.getLeastSignificantBits()
/** @return
* The most significant bits of this UUID.
*/
def msb(): Long = uid.getMostSignificantBits() def msb(): Long = uid.getMostSignificantBits()
/** @return
* `true` if this UUID is `0`, `false` otherwise.
*/
def isZero(): Boolean = lsb() == 0L && msb() == 0L def isZero(): Boolean = lsb() == 0L && msb() == 0L
/** Type class for UUID generation. /** Type class for UUID generation.
@ -91,11 +135,11 @@ object UUID:
object Generator: object Generator:
/** Instantiate a new Type 4 generator. /** Instantiate a new Type 4 generator.
*/ */
def version4: Generator = new Version4 def version4(): Generator = new Version4
/** Instantiate a new Type 7 generator. /** Instantiate a new Type 7 generator.
*/ */
def version7: Generator = new Version7 def version7(): Generator = new Version7
/** Type 4 (Random) implementation of a UUID generator. /** Type 4 (Random) implementation of a UUID generator.
*/ */

View file

@ -1,8 +1,8 @@
package gs.uuid.v0 package gs.uuid.v0
class UUIDTests extends munit.FunSuite: class UUIDTests extends munit.FunSuite:
private val v4 = UUID.Generator.version4 private val v4 = UUID.Generator.version4()
private val v7 = UUID.Generator.version7 private val v7 = UUID.Generator.version7()
given CanEqual[java.util.UUID, java.util.UUID] = CanEqual.derived given CanEqual[java.util.UUID, java.util.UUID] = CanEqual.derived
test( test(
@ -11,7 +11,7 @@ class UUIDTests extends munit.FunSuite:
val base = v4.next() val base = v4.next()
val str = base.str() val str = base.str()
val parsed = UUID.parse(str) val parsed = UUID.parse(str)
assert(parsed == Some(base)) assertEquals(parsed, Some(base))
} }
test( test(
@ -20,7 +20,7 @@ class UUIDTests extends munit.FunSuite:
val base = v7.next() val base = v7.next()
val str = base.str() val str = base.str()
val parsed = UUID.parse(str) val parsed = UUID.parse(str)
assert(parsed == Some(base)) assertEquals(parsed, Some(base))
} }
test("should instantiate from any java.util.UUID") { test("should instantiate from any java.util.UUID") {
@ -28,18 +28,31 @@ class UUIDTests extends munit.FunSuite:
val base = UUID(raw) val base = UUID(raw)
val str = base.str() val str = base.str()
val parsed = UUID.fromString(str) val parsed = UUID.fromString(str)
assert(parsed == Some(base)) assertEquals(parsed, Some(base))
assert(parsed.map(_.toUUID) == Some(raw)) assertEquals(parsed.map(_.toUUID()), Some(raw))
} }
test("should successfully parse a UUID with dashes") { test("should successfully parse a UUID with dashes") {
val base = java.util.UUID.randomUUID() 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") { test("should fail to parse a non-hex string") {
val input = "ghijklmnoped45189efa6f40ed9518ed" 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") { test("should generate using an available type class instance") {
@ -47,14 +60,14 @@ class UUIDTests extends munit.FunSuite:
val base = doGen val base = doGen
val str = base.str() val str = base.str()
val parsed = UUID.parse(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") { test("should return lsb, msb, and if the UUID is zero") {
val uuid = UUID.parse("00000000-0000-0000-0000-000000000000") val uuid = UUID.parse("00000000-0000-0000-0000-000000000000")
assert(uuid.map(_.lsb()) == Some(0L)) assertEquals(uuid.map(_.lsb()), Some(0L))
assert(uuid.map(_.msb()) == Some(0L)) assertEquals(uuid.map(_.msb()), Some(0L))
assert(uuid.map(_.isZero()) == Some(true)) assertEquals(uuid.map(_.isZero()), Some(true))
} }
private def doGen( private def doGen(