Compare commits

...

3 commits
0.2.4 ... main

Author SHA1 Message Date
5f6d9086bc (minor) Interface improvements, more functions, general version updates. (#14)
All checks were successful
/ Build and Release Library (push) Successful in 1m46s
Reviewed-on: #14
2025-03-22 16:29:43 +00:00
0ef5e593a3 (minor) Bumping version, forgot on previous commit. (#13)
All checks were successful
/ Build and Release Library (push) Successful in 1m48s
Reviewed-on: #13
2024-08-03 14:20:16 +00:00
d0dd5c3833 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
2024-08-01 13:58:16 +00:00
8 changed files with 180 additions and 40 deletions

View file

@ -1,5 +1,5 @@
// See: https://github.com/scalameta/scalafmt/tags for the latest tags.
version = 3.7.11
version = 3.9.4
runner.dialect = scala3
maxColumn = 80

View file

@ -1,12 +1,15 @@
# 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).
This project uses the Apache 2.0 License due to the use of Jackson Databind
code.
- [Usage](#usage)
- [Dependency](#dependency)
- [Donate](#donate)
@ -30,7 +33,7 @@ val GsUuid: ModuleID =
```scala
import gs.uuid.v0.UUID
given UUID.Generator = UUID.Generator.version7
given UUID.Generator = UUID.Generator.version7()
val id = UUID.generate()

View file

@ -1,9 +1,15 @@
val scala3: String = "3.4.1"
val scala3: String = "3.6.4"
ThisBuild / scalaVersion := scala3
ThisBuild / versionScheme := Some("semver-spec")
ThisBuild / gsProjectName := "gs-uuid"
ThisBuild / licenses := Seq(
"Apache 2.0" -> url(
"https://git.garrity.co/garrity-software/gs-uuid/src/branch/main/LICENSE"
)
)
val sharedSettings = Seq(
scalaVersion := scala3,
version := semVerSelected.value,
@ -14,7 +20,7 @@ val sharedSettings = Seq(
lazy val testSettings = Seq(
libraryDependencies ++= Seq(
"org.scalameta" %% "munit" % "1.0.0-M10" % Test
"org.scalameta" %% "munit" % "1.1.0" % Test
)
)
@ -25,6 +31,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"
)
)

View file

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

View file

@ -28,6 +28,6 @@ externalResolvers := Seq(
"Garrity Software Releases" at "https://maven.garrity.co/gs"
)
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.11")
addSbtPlugin("gs" % "sbt-garrity-software" % "0.3.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.1.0")
addSbtPlugin("gs" % "sbt-garrity-software" % "0.5.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
* 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() {}
@ -33,7 +33,7 @@ public final class UUIDFormat {
* @param uuid The UUID to render.
* @return Hexadecimal representation of the UUID.
*/
public static String toHex(final UUID uuid) {
public static String toHexWithoutDashes(final UUID uuid) {
final char[] ch = new char[32];
// Example:
@ -64,6 +64,34 @@ public final class UUIDFormat {
return new String(ch, 0, 32);
}
/**
* <p>Render the given UUID as a 36-character string using lowercase
* hexadecimal with dashes.</p>
*
* @param uuid The UUID to render.
* @return Hexadecimal representation of the UUID.
*/
public static String toHexWithDashes(final UUID uuid) {
final char[] ch = new char[36];
final long msb = uuid.getMostSignificantBits();
_appendInt((int) (msb >> 32), ch, 0);
ch[8] = '-';
int i = (int) msb;
_appendShort(i >>> 16, ch, 9);
ch[13] = '-';
_appendShort(i, ch, 14);
ch[18] = '-';
final long lsb = uuid.getLeastSignificantBits();
_appendShort((int) (lsb >>> 48), ch, 19);
ch[23] = '-';
_appendShort((int) (lsb >>> 32), ch, 24);
_appendInt((int) lsb, ch, 28);
return new String(ch, 0, 36);
}
/**
* <p>Render the given UUID as a 16-byte array.</p>
*

View file

@ -2,7 +2,8 @@ package gs.uuid.v0
import com.fasterxml.uuid.Generators
/** Alias for the `java.util.UUID` type, which represents a 128-bit value.
/** Alias for the `java.util.UUID` type, which represents a 128-bit (16-byte)
* value.
*
* ## ID Generation
*
@ -16,19 +17,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 +36,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.
@ -52,6 +66,16 @@ object UUID:
G: Generator
): UUID = G.next()
/** @return
* New v4 UUID (Random).
*/
def v4(): UUID = Generator.version4.next()
/** @return
* New v7 UUID (Epoch + Counter + Random).
*/
def v7(): UUID = Generator.version7.next()
/** Parse the given string as a UUID.
*
* @param str
@ -62,6 +86,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 +101,48 @@ 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
def str(): String = withoutDashes()
/** @return
* The byte array representation of this UUID.
*/
def toBytes(): Array[Byte] = UUIDFormat.toBytes(uid)
def withoutDashes(): String = UUIDFormat.toHex(uid)
/** Serialize this UUID.
*
* @param dashes
* Whether to use dashes in the output (default = false).
* @return
* Hexadecimal string representation of this UUID.
*/
def str(dashes: Boolean = false): String = withoutDashes()
/** @return
* Hexadecimal string representation of this UUID, without dashes.
*/
def withoutDashes(): String = UUIDFormat.toHexWithoutDashes(uid)
/** @return
* Hexadecimal string representation of this UUID, with dashes.
*/
def withDashes(): String = UUIDFormat.toHexWithDashes(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 +155,11 @@ object UUID:
object Generator:
/** Instantiate a new Type 4 generator.
*/
def version4: Generator = new Version4
lazy val version4: Generator = new Version4
/** Instantiate a new Type 7 generator.
*/
def version7: Generator = new Version7
lazy val version7: Generator = new Version7
/** Type 4 (Random) implementation of a UUID generator.
*/
@ -103,8 +167,8 @@ object UUID:
private val gen = Generators.randomBasedGenerator()
override def next(): UUID = gen.generate()
/** Type 7 (Unix Epoch Time + Random) implementation of a UUID generator.
* Consider using this rather than Type 1 or Type 6.
/** Type 7 (Unix Epoch Time + Counter + Random) implementation of a UUID
* generator. Consider using this rather than Type 1 or Type 6.
*
* This type is defined in [IETF New UUID Formats
* Draft](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-uuid-version-7)

View file

@ -11,7 +11,16 @@ 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(
"should instantiate a type 4 UUID, serialize it, and parse the result (helper function)"
) {
val base = UUID.v4()
val str = base.str()
val parsed = UUID.parse(str)
assertEquals(parsed, Some(base))
}
test(
@ -20,7 +29,16 @@ 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 a type 7 UUID, serialize it, and parse the result (helper function)"
) {
val base = UUID.v7()
val str = base.str()
val parsed = UUID.parse(str)
assertEquals(parsed, Some(base))
}
test("should instantiate from any java.util.UUID") {
@ -28,18 +46,39 @@ 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 raw = java.util.UUID.randomUUID()
val base = UUID(raw)
assertEquals(UUID.parse(base.withDashes()), Some(base))
}
test(
"should successfully parse a UUID with dashes, generated with this library"
) {
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 +86,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(