Skip to content

Commit

Permalink
fix IndexOutOfBoundException when parsing invalid string
Browse files Browse the repository at this point in the history
also allow parsing of chars i, l and o
  • Loading branch information
bilal-fazlani committed Jan 12, 2023
1 parent 5eb6cad commit 81b4e58
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 20 deletions.
6 changes: 5 additions & 1 deletion docs/stylesheets/extra.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
15 changes: 10 additions & 5 deletions src/main/scala/com/bilalfazlani/zioUlid/BinaryULID.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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',
Expand All @@ -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))
Expand Down
5 changes: 3 additions & 2 deletions src/main/scala/com/bilalfazlani/zioUlid/ULID.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
}

Expand Down
6 changes: 6 additions & 0 deletions src/test/scala/com/bilalfazlani/zioUlid/BinaryULIDTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
)
}
46 changes: 34 additions & 12 deletions src/test/scala/com/bilalfazlani/zioUlid/ULIDTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)))
}
}
)
Expand Down

0 comments on commit 81b4e58

Please sign in to comment.