From d5a0c7a2c714b2a0354bcc1f9a396ec4078256c2 Mon Sep 17 00:00:00 2001 From: nmcb Date: Tue, 7 Apr 2026 21:47:24 +0200 Subject: [PATCH 1/4] cleanup --- src/main/scala/{uuid.scala => id/UUID.scala} | 11 ++--- .../{ => id}/JavaUUIDCompatibilityProps.scala | 41 +++++++++---------- src/test/scala/{ => id}/UUIDProps.scala | 24 ++++------- 3 files changed, 34 insertions(+), 42 deletions(-) rename src/main/scala/{uuid.scala => id/UUID.scala} (93%) rename src/test/scala/{ => id}/JavaUUIDCompatibilityProps.scala (64%) rename src/test/scala/{ => id}/UUIDProps.scala (79%) diff --git a/src/main/scala/uuid.scala b/src/main/scala/id/UUID.scala similarity index 93% rename from src/main/scala/uuid.scala rename to src/main/scala/id/UUID.scala index 190c329..a778e67 100644 --- a/src/main/scala/uuid.scala +++ b/src/main/scala/id/UUID.scala @@ -54,21 +54,21 @@ object UUID: def apply(msb: Byte, lsb: Byte): CountryCode = CountryCode(String(Array(msb, lsb), "US-ASCII")) - def decodeTarget(lsb: Long): CountryCode = + private def decodeTarget(lsb: Long): CountryCode = val node5 = (lsb & 0x0000_0000_0000_001f) + 0x41 val node4 = ((lsb >>> 5) & 0x0000_0000_0000_001f) + 0x41 CountryCode(node4.toByte, node5.toByte) - def decodeSource(lsb: Long): CountryCode = + private def decodeSource(lsb: Long): CountryCode = val node3 = ((lsb >>> 10) & 0x0000_0000_0000_001f) + 0x41 val node2 = ((lsb >>> 15) & 0x0000_0000_0000_001f) + 0x41 CountryCode(node2.toByte, node3.toByte) private def encode(source: CountryCode, target: CountryCode)(lsb: Long): Long = def extractor(accumulator: Long, byte: Byte): Long = (accumulator << 5) + ((byte - 0x41) & 0x1f) - val scode = source.asBytes.foldLeft(0L)(extractor) - val tcode = target.asBytes.foldLeft(0L)(extractor) - (lsb & 0xffff_ffff_fff0_0000L) + (scode << 10) + tcode + val sourceCode = source.asBytes.foldLeft(0L)(extractor) + val targetCode = target.asBytes.foldLeft(0L)(extractor) + (lsb & 0xffff_ffff_fff0_0000L) + (sourceCode << 10) + targetCode def iso3166(source: CountryCode, target: CountryCode, from: JavaUUID = JavaUUID.randomUUID): UUID = assert(from.asScala.version.contains(Version.RandomBased), s"invalid java uuid version: ${from.asScala.version}") @@ -102,6 +102,7 @@ object compat: type JavaUUID = java.util.UUID object JavaUUID: + def apply(msb: Long, lsb: Long): JavaUUID = java.util.UUID.apply(msb, lsb) diff --git a/src/test/scala/JavaUUIDCompatibilityProps.scala b/src/test/scala/id/JavaUUIDCompatibilityProps.scala similarity index 64% rename from src/test/scala/JavaUUIDCompatibilityProps.scala rename to src/test/scala/id/JavaUUIDCompatibilityProps.scala index 53b76f9..55c6e96 100644 --- a/src/test/scala/JavaUUIDCompatibilityProps.scala +++ b/src/test/scala/id/JavaUUIDCompatibilityProps.scala @@ -1,13 +1,11 @@ package id -package test import org.scalacheck.* -import id.compat.JavaUUID object JavaUUIDCompatibilityProps extends Properties("uuid.compat"): - import generators.* import Prop.* + import generators.* property("applyIsJavaUUIDCompatible") = forAll(javaApplyUUIDs)(isJavaUUIDCompatible) @@ -18,11 +16,9 @@ object JavaUUIDCompatibilityProps extends Properties("uuid.compat"): property("v3IsJavaUUIDCompatible - MD5HashBased") = forAll(javaVersion3UUIDs)(isJavaUUIDVersion3Compatible) - import id.* + import compat.* import Variant.* import Version.* - - import compat.* import compat.JavaUUID.* def isJavaUUIDCompatible(javaUUID: JavaUUID): Boolean = @@ -39,39 +35,40 @@ object JavaUUIDCompatibilityProps extends Properties("uuid.compat"): def isJavaUUIDVersionCompatible(javaUUID: JavaUUID): Boolean = javaUUID.version match - case 1 => javaUUID.asScala.version == Some(TimeBased) - case 2 => javaUUID.asScala.version == Some(DCESecurityBased) - case 3 => javaUUID.asScala.version == Some(MD5HashBased) - case 4 => javaUUID.asScala.version == Some(RandomBased) - case 5 => javaUUID.asScala.version == Some(SHA1HashBased) - case 6 => javaUUID.asScala.version == Some(Version6) - case 7 => javaUUID.asScala.version == Some(Version7) - case 8 => javaUUID.asScala.version == Some(ISO3166Based) - case _ => javaUUID.asScala.version == None + case 1 => javaUUID.asScala.version.contains(TimeBased) + case 2 => javaUUID.asScala.version.contains(DCESecurityBased) + case 3 => javaUUID.asScala.version.contains(MD5HashBased) + case 4 => javaUUID.asScala.version.contains(RandomBased) + case 5 => javaUUID.asScala.version.contains(SHA1HashBased) + case 6 => javaUUID.asScala.version.contains(Version6) + case 7 => javaUUID.asScala.version.contains(Version7) + case 8 => javaUUID.asScala.version.contains(ISO3166Based) + case _ => javaUUID.asScala.version.isEmpty def isJavaUUIDVersion4Compatible(javaUUID: JavaUUID): Boolean = - val isRandomBased = javaUUID.asScala.version == Some(RandomBased) + val isRandomBased = javaUUID.asScala.version.contains(RandomBased) isRandomBased && isJavaUUIDVariantCompatible(javaUUID) def isJavaUUIDVersion3Compatible(javaUUID: JavaUUID, name: Array[Byte]): Boolean = val isNameBased = java.util.UUID.nameUUIDFromBytes(name) == javaUUID - val isMD5Hased = javaUUID.asScala.version == Some(MD5HashBased) - isNameBased && isMD5Hased && isJavaUUIDVariantCompatible(javaUUID) + val isMD5Hashed = javaUUID.asScala.version.contains(MD5HashBased) + isNameBased && isMD5Hashed && isJavaUUIDVariantCompatible(javaUUID) object generators: import Arbitrary.* val javaApplyUUIDs: Gen[JavaUUID] = - for { + for msb <- arbitrary[Long] lsb <- arbitrary[Long] - } yield JavaUUID(msb, lsb) + yield + JavaUUID(msb, lsb) val javaVersion4UUIDs: Gen[JavaUUID] = Gen.map(_ => JavaUUID.randomUUID) - val javaVersion3UUIDs: Gen[(JavaUUID,Array[Byte])] = + val javaVersion3UUIDs: Gen[(JavaUUID, Array[Byte])] = Gen - .containerOf[Array,Byte](arbitrary[Byte]) + .containerOf[Array, Byte](arbitrary[Byte]) .map(bytes => (JavaUUID.nameUUIDFromBytes(bytes), bytes)) diff --git a/src/test/scala/UUIDProps.scala b/src/test/scala/id/UUIDProps.scala similarity index 79% rename from src/test/scala/UUIDProps.scala rename to src/test/scala/id/UUIDProps.scala index d3a66d6..d556214 100644 --- a/src/test/scala/UUIDProps.scala +++ b/src/test/scala/id/UUIDProps.scala @@ -1,27 +1,22 @@ package id import org.scalacheck.* -import id.UUID.CountryCode object UUIDProps extends Properties("uuid.UUID"): - import id.* - - import util.* - import util.given import Prop.* + import UUID.* import Variant.* import Version.* - - property("variant") = forAll { (msb: Long, lsb: Long) => + + property("variant") = forAll: (msb: Long, lsb: Long) => UUID(msb, lsb).variant match case Reserved => lsb.toBinaryString.startsWith("111") case MicrosoftBackwardsCompatible => lsb.toBinaryString.startsWith("110") case LeachSalz => lsb.toBinaryString.startsWith("10") case NCSBackwardsCompatible => true - } - property("version") = forAll { (msb: Long, lsb: Long) => + property("version") = forAll: (msb: Long, lsb: Long) => UUID(msb, lsb).version match case Some(TimeBased) => (msb & 0xF000L) == 0x1000L case Some(DCESecurityBased) => (msb & 0xF000L) == 0x2000L @@ -32,18 +27,17 @@ object UUIDProps extends Properties("uuid.UUID"): case Some(Version7) => (msb & 0xF000L) == 0x7000L case Some(ISO3166Based) => (msb & 0xF000L) == 0x8000L case None => ((msb & 0xF000L) == 0x0000L) || ((msb & 0xF000L) >= 0x7000L) - } - property("iso3166") = forAll(isoSourcesAndTargets) { (source: CountryCode, target: CountryCode) => + property("iso3166") = forAll(isoSourcesAndTargets): (source: CountryCode, target: CountryCode) => val uuid = UUID.iso3166(source, target) val correctType = (uuid.variant == LeachSalz) && (uuid.version.get == ISO3166Based) val correctData = (uuid.sourceCountryCode.get == source) && (uuid.targetCountryCode.get == target) correctType && correctData - } - val isoSourcesAndTargets: Gen[(CountryCode, CountryCode)] = + def isoSourcesAndTargets: Gen[(CountryCode, CountryCode)] = import compat.JavaCountryCodes - for { + for source <- Gen.oneOf(JavaCountryCodes).map(CountryCode.apply) target <- Gen.oneOf(JavaCountryCodes).map(CountryCode.apply) - } yield (source, target) + yield + (source, target) From c09c16cca1a72dd337b6e4eaafbd4a2aee1b634b Mon Sep 17 00:00:00 2001 From: nmcb Date: Wed, 8 Apr 2026 09:57:27 +0200 Subject: [PATCH 2/4] upgrade to java 23 --- .github/workflows/scala-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scala-build.yml b/.github/workflows/scala-build.yml index b14feee..e2f53d4 100644 --- a/.github/workflows/scala-build.yml +++ b/.github/workflows/scala-build.yml @@ -18,7 +18,7 @@ jobs: - name: setup-java uses: actions/setup-java@v5 with: - java-version: '21' + java-version: '23' distribution: 'temurin' cache: sbt - uses: sbt/setup-sbt@v1 From b662f303f15d40b411b0e1d2de7eec037eaebc31 Mon Sep 17 00:00:00 2001 From: nmcb Date: Thu, 9 Apr 2026 16:10:29 +0200 Subject: [PATCH 3/4] migrate to scala 3.8.3 warn-as-error --- build.sbt | 11 +++++++++++ src/main/scala/id/UUID.scala | 8 +++++--- src/test/scala/id/JavaUUIDCompatibilityProps.scala | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 0869d8b..71c50d6 100644 --- a/build.sbt +++ b/build.sbt @@ -8,3 +8,14 @@ lazy val root = project , libraryDependencies ++= Seq("org.scalacheck" %% "scalacheck" % "1.19.0" % "test") ) + +ThisBuild / scalacOptions ++= Seq( + "-encoding", "utf8", + "-feature", + "-language:implicitConversions", + "-language:existentials", + "-language:strictEquality", + "-unchecked", + "-Werror", + "-deprecation" +) diff --git a/src/main/scala/id/UUID.scala b/src/main/scala/id/UUID.scala index a778e67..678b34f 100644 --- a/src/main/scala/id/UUID.scala +++ b/src/main/scala/id/UUID.scala @@ -41,7 +41,7 @@ object UUID: import JavaUUID.* /** wrapper for a two character upper case country code */ - case class CountryCode(underlying: String): + case class CountryCode(underlying: String) derives CanEqual: assert(underlying.matches("[A-Z][A-Z]"), s"not a two character upper case string: $underlying") def asBytes: Array[Byte] = @@ -78,7 +78,7 @@ object UUID: val msb = from.getMostSignificantBits & ISO3166Based.mask UUID(msb + ISO3166Based.bits, encode(source, target)(from.getLeastSignificantBits)) -enum Variant(val bits: Long): +enum Variant(val bits: Long) derives CanEqual: val mask: Long = 0xeffff_ffff_ffff_fffL case NCSBackwardsCompatible extends Variant(0x2111_1111_1111_1111L) case LeachSalz extends Variant(0x5111_1111_1111_1111L) @@ -86,7 +86,7 @@ enum Variant(val bits: Long): case Reserved extends Variant(0xF111_1111_1111_1111L) -enum Version(val bits: Long): +enum Version(val bits: Long) derives CanEqual: val mask: Long = 0xffff_ffff_ffff_0fffL case TimeBased extends Version(0x0000_0000_0000_1000L) case DCESecurityBased extends Version(0x0000_0000_0000_2000L) @@ -101,6 +101,8 @@ object compat: type JavaUUID = java.util.UUID + given CanEqual[JavaUUID, JavaUUID] = CanEqual.derived + object JavaUUID: def apply(msb: Long, lsb: Long): JavaUUID = diff --git a/src/test/scala/id/JavaUUIDCompatibilityProps.scala b/src/test/scala/id/JavaUUIDCompatibilityProps.scala index 55c6e96..f0e16d8 100644 --- a/src/test/scala/id/JavaUUIDCompatibilityProps.scala +++ b/src/test/scala/id/JavaUUIDCompatibilityProps.scala @@ -16,7 +16,7 @@ object JavaUUIDCompatibilityProps extends Properties("uuid.compat"): property("v3IsJavaUUIDCompatible - MD5HashBased") = forAll(javaVersion3UUIDs)(isJavaUUIDVersion3Compatible) - import compat.* + import compat.{*, given} import Variant.* import Version.* import compat.JavaUUID.* From 3a04ded2eb11c60be8fa7fb0d0fedd490c0c940e Mon Sep 17 00:00:00 2001 From: nmcb Date: Mon, 29 Jun 2026 18:20:44 +0200 Subject: [PATCH 4/4] add osv scan --- .github/workflows/scala-build.yml | 2 +- project/plugins.sbt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 project/plugins.sbt diff --git a/.github/workflows/scala-build.yml b/.github/workflows/scala-build.yml index e2f53d4..0561afb 100644 --- a/.github/workflows/scala-build.yml +++ b/.github/workflows/scala-build.yml @@ -24,4 +24,4 @@ jobs: - uses: sbt/setup-sbt@v1 - name: sbt-test shell: bash - run: sbt -v +test + run: sbt -v +test osvScan all-projects single-report diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..d4d451d --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("net.nmoncho" % "sbt-osv" % "0.2.0")