diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 6ba295a..3feea60 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -14,7 +14,7 @@ [data-md-color-scheme="slate"] { /* Background */ - --md-default-bg-color: #2b3a4b; + --md-default-bg-color: #1f2124; /* Primary color shades */ --md-primary-fg-color: #843d57; @@ -29,6 +29,10 @@ --md-footer-bg-color--dark: #232323; } +[data-md-color-scheme="slate"] .md-typeset code { + background-color: #13141b; +} + [data-md-color-scheme="slate"] .md-button { border-color: var(--md-accent-fg-color); color: var(--md-accent-fg-color); diff --git a/src/main/scala/com/bilalfazlani/zioUlid/BinaryULID.scala b/src/main/scala/com/bilalfazlani/zioUlid/BinaryULID.scala index e45ab84..3759201 100644 --- a/src/main/scala/com/bilalfazlani/zioUlid/BinaryULID.scala +++ b/src/main/scala/com/bilalfazlani/zioUlid/BinaryULID.scala @@ -116,7 +116,7 @@ object BinaryULID { } // https://stackoverflow.com/questions/10813154/how-do-i-convert-a-number-to-a-letter-in-java - private val decodingChars: Array[Byte] = Array[Byte]( + private val decodingChars = Array[Byte]( -1, -1, -1, -1, -1, -1, -1, -1, // 0 -1, -1, -1, -1, -1, -1, -1, -1, // 8 -1, -1, -1, -1, -1, -1, -1, -1, // 16 @@ -126,16 +126,19 @@ object BinaryULID { 0, 1, 2, 3, 4, 5, 6, 7, // 48 8, 9, -1, -1, -1, -1, -1, -1, // 56 -1, 10, 11, 12, 13, 14, 15, 16, // 64 - 17, -1, 18, 19, -1, 20, 21, -1, // 72 + 17, 1, 18, 19, 1, 20, 21, 0, // 72 22, 23, 24, 25, 26, -1, 27, 28, // 80 29, 30, 31, -1, -1, -1, -1, -1, // 88 -1, 10, 11, 12, 13, 14, 15, 16, // 96 - 17, -1, 18, 19, -1, 20, 21, -1, // 104 + 17, 1, 18, 19, 1, 20, 21, 0, // 104 22, 23, 24, 25, 26, -1, 27, 28, // 112 29, 30, 31 // 120 ) - inline private def decode(c: Char): Byte = decodingChars(c & 0x7f) + inline private def decode(c: Char): Byte = + val index = c & 0x7f + if(index > -1 && index < 123) decodingChars(index) + else -1 private val encodingChars = Chunk( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', @@ -158,7 +161,9 @@ object BinaryULID { s.reverseInPlace().toString() } - private def isValidBase32(s: String): Boolean = s.forall { decode(_) != -1 } + private[zioUlid] def isValidBase32(s: String): Boolean = s.forall { c => + decode(c) != -1 + } private def validateInput(s: String): Either[ULIDStringParsingError, Unit] = if s.length != 26 then Left(InvalidLength(s)) diff --git a/src/main/scala/com/bilalfazlani/zioUlid/ULID.scala b/src/main/scala/com/bilalfazlani/zioUlid/ULID.scala index c553c0d..660ce7d 100644 --- a/src/main/scala/com/bilalfazlani/zioUlid/ULID.scala +++ b/src/main/scala/com/bilalfazlani/zioUlid/ULID.scala @@ -2,11 +2,12 @@ package com.bilalfazlani.zioUlid import zio.{URIO, Chunk, ZIO} import com.bilalfazlani.zioUlid.ULIDStringParsingError +import math.Ordered.orderingToOrdered final class ULID private[zioUlid] (private val ulidString: String) extends Ordered[ULID] { - override val toString: String = ulidString + override val toString: String = binary.encode lazy val timestamp: Long = binary.timestamp @@ -17,7 +18,7 @@ final class ULID private[zioUlid] (private val ulidString: String) private lazy val binary: BinaryULID = BinaryULID.decodeUnsafe(ulidString) override def equals(other: Any): Boolean = other match { - case u: ULID => (u.ulidString compare ulidString) == 0 + case u: ULID => (u.tuple compare binary.tuple) == 0 case _ => false } diff --git a/src/test/scala/com/bilalfazlani/zioUlid/BinaryULIDTest.scala b/src/test/scala/com/bilalfazlani/zioUlid/BinaryULIDTest.scala index 472eff6..8cb3279 100644 --- a/src/test/scala/com/bilalfazlani/zioUlid/BinaryULIDTest.scala +++ b/src/test/scala/com/bilalfazlani/zioUlid/BinaryULIDTest.scala @@ -33,6 +33,12 @@ object BinaryULIDTest extends ZIOSpecDefault { val bytes = Chunk.fill(16)(0xff.toByte) val binaryUlid = BinaryULID.fromBytes(bytes) assert(binaryUlid)(isRight(equalTo(BinaryULID(-1L, -1L)))) + }, + test("isValidBase32 should validate all characters without throwing exception"){ + check(Gen.char) { char => + val isValid = BinaryULID.isValidBase32(char.toString()) + assertCompletes + } } ) } diff --git a/src/test/scala/com/bilalfazlani/zioUlid/ULIDTest.scala b/src/test/scala/com/bilalfazlani/zioUlid/ULIDTest.scala index d2c1072..237d4b2 100644 --- a/src/test/scala/com/bilalfazlani/zioUlid/ULIDTest.scala +++ b/src/test/scala/com/bilalfazlani/zioUlid/ULIDTest.scala @@ -4,16 +4,21 @@ import zio.test.* import zio.test.Assertion.* import zio.test.TestAspect.* import zio.Chunk +import zio.ZIO object ULIDTest extends ZIOSpecDefault { - private val validChars = - "0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz" + private val validDecodingChars = + "0123456789" + + "ABCDEFGHIJKLMNOPQRSTVWXYZ" + + "abcdefghijklmnopqrstvwxyz" + + val firstValidChars = "01234567" val validStringGen = { - val firstValidChars = "01234567" val headGen = Gen.elements(firstValidChars.toCharArray()*) - val tailGen = Gen.stringN(25)(Gen.elements(validChars.toCharArray()*)) + val tailGen = + Gen.stringN(25)(Gen.elements(validDecodingChars.toCharArray()*)) (headGen zip tailGen).map(x => x._1 + x._2) } val timestampGen = Gen.long(0L, 281474976710655L) @@ -55,9 +60,10 @@ object ULIDTest extends ZIOSpecDefault { ) }, test("report invalid ULID characters") { - val stringGen = Gen.stringN(1)( - Gen.elements("OoIiUuLl".toCharArray()*) - ) zip Gen.stringN(25)(Gen.elements(validChars.toCharArray()*)) + val tailGen = + Gen.stringN(25)(Gen.char.filter(c => !validDecodingChars.contains(c))) + val headGen = Gen.elements(firstValidChars.toCharArray()*) + val stringGen = headGen zip tailGen check(stringGen) { case (a, b) => val str = a + b val ulid = ULID(str) @@ -146,11 +152,27 @@ object ULIDTest extends ZIOSpecDefault { }, test("create ULID from bytes") { check(validStringGen) { str => - val test = for - stringEncodedULID <- ULID(str) - bytesDecodedULID <- ULID(stringEncodedULID.bytes) - yield stringEncodedULID == bytesDecodedULID - assert(test)(isRight(isTrue)) + for + stringEncodedULID <- ZIO + .fromEither(ULID(str)) + bytesDecodedULID <- ZIO.fromEither(ULID(stringEncodedULID.bytes)) + yield assertTrue(stringEncodedULID == bytesDecodedULID) // + } + }, + test("i,l & o should be transalted to 1, 1 & 0 respectively") { + check(validStringGen) { str => + val newStr = str + .replaceAll("i", "1") + .replaceAll("l", "1") + .replaceAll("o", "0") + .replaceAll("I", "1") + .replaceAll("L", "1") + .replaceAll("O", "0") + .toUpperCase() + + val ulid = ULID(newStr).map(_.toString) + + assert(ulid)(isRight(equalTo(newStr))) } } )