Getting started#
Generating new ULIDs#
Here is an example of generating a ULID. Generating ULIDs require a ULIDGen
which is a ZLayer
. You can create a ULIDGen
using ULIDGen.live
.
import com.bilalfazlani.zioUlid._
val program = for {
// make a new ULID
ulid <- ULID.nextULID
// .toString gives a string representation of the ULID
_ <- Console.printLine(ulid.toString)
} yield ()
val run = program.provide(ULIDGen.live)
.toString
gives us the string representation of ULID
Warning
If system datetime is set to more than August 10,889, then fiber will die with UnsupportedSystemDateTime
exception
Important
Since ULIDGen
is a stateful layer, you should have only one instance of ULIDGen
in your application. You can ensure that by using ULIDGen.live
only once in your application, ideally at at the start/root of your application.
Timestamp#
To know when a ULID was generated, we can get the timestamp from a ULID. Its a Long
value representing Unix epoch milliseconds.
// get the timestamp of a ULID
val timestampMillis: Long = ulid.timestamp
// convert the millis to a isntant or datetime
val datetime = ZonedDateTime.ofInstant(
Instant.ofEpochMilli(timestampMillis),
ZoneId.of("Z")
)
Comparing and sorting ULIDs#
Since ULIDs are lexicographically sortable, we can compare them with each other and sort them.
import com.bilalfazlani.zioUlid.ULID
val program =
for {
// make new ULIDs
ulid1 <- ULID.nextULID
ulid2 <- ULID.nextULID
// compare two ULIDs
equals = ulid1 == ulid2
lessThan = ulid1 < ulid2
greaterThan = ulid1 > ulid2
// sort ULIDs
sortedUlids = List(ulid2, ulid1).sorted
} yield ()
Binary encoding#
There are two binary representations of ULIDs. One is a Chunk[Byte]
of size 16 and the other a tuple (pair) of two 64 bit Long
values.
import com.bilalfazlani.zioUlid.ULID
val program = for {
// make a new ULID
ulid <- ULID.nextULID
// get a bytes representation of a ULID
bytes: Chunk[Byte] = ulid.bytes
// get a tuple (high, low) representation of a ULID
tuple: (Long, Long) = ulid.tuple
} yield ()
Converting a Tuple[Long, Long]
to ULID#
You can easily convert a tuple of two Long
s into a ULID
// decode a ULID from a tuple
val ulid: ULID = ULID(123412312L, 2134234423L)
This is a direct operation and does not need any validations as any two Longs are a valid ULID
Parsing a String#
Parsing a string into a ULID returns Either[ULIDStringParsingError, ULID]
. It can fail with following errors
ULIDStringParsingError.InvalidLength
when string length is not 26ULIDStringParsingError.InvalidCharacters
when string contains characters which are not supported by encodingULIDStringParsingError.OverflowValue
when string contains a value which is greater than 128 bits
import com.bilalfazlani.zioUlid.{ULID, ULIDStringParsingError}
import ULIDStringParsingError._
private def getInputString: String = ???
// decode a ULID from a string
val ulid: Either[ULIDStringParsingError, ULID] = ULID(getInputString)
ulid.fold(
{
case InvalidCharacters(_) =>
println("Invalid characters in the string")
case InvalidLength(_) =>
println(s"Invalid length of string")
case OverflowValue(string) =>
println(s"Overflow value $string")
},
ulid => println(ulid.toString)
)
It is also possible to validate a string using pattern matching
// validate a string as a ULID using pattern matching
getInputString match {
case ULID(string) => println(s"Valid ULID: $string")
case invalidString => println(s"Invalid ULID: $invalidString")
}
Parsing chunk of bytes#
When parsing bytes into a ULID, validation of chunk size is performed. If size is not 16, then Left[ULIDBytesParsingError.InvalidBytesLength]
is returned.
import com.bilalfazlani.zioUlid._
private def getInputBytes: Chunk[Byte] = ???
// decode a ULID from bytes
val ulid: Either[ULIDBytesParsingError.InvalidBytesLength, ULID] = ULID(
getInputBytes
)
Similar to validation of Strings, its also possible to validate bytes using pattern matching
// validate bytes as a ULID using pattern matching
getInputBytes match {
case validBytes @ ULID(string) => println("Valid ULID: " + string)
case invalidBytes => println("bytes are invalid")
}
Parsing Chunk[Byte]
with separate timestamp#
You can create a ULID from a timestamp (Long
) and a Chunk[Byte]
. Chunk should be of size 10. Timestamp should be <= 48 bits.
import com.bilalfazlani.zioUlid._
// create a ULID from a timestamp and bytes
val ulid: Either[ULIDBytesParsingError, ULID] =
ULID(getTimestamp, getRandomBytes)
This will return an Either[ULIDBytesParsingError, ULID]
with following errors
ULIDBytesParsingError.InvalidBytesLength
when chunk size is not 10ULIDBytesParsingError.InvalidTimestamp
when timestamp is greater than 48 bits