mirror of
https://github.com/Yu-Vitaqua-fer-Chronos/NULID.git
synced 2024-11-21 22:12:46 +00:00
Add some documentation for NULID as well as minor tweaks.
This commit is contained in:
parent
14433a793b
commit
61b4312bad
0
config.nims
Normal file
0
config.nims
Normal file
|
@ -1,6 +1,6 @@
|
|||
# Package
|
||||
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
author = "Yu Vitaqua fer Chronos"
|
||||
description = "An implementation of ULID!"
|
||||
license = "CC0"
|
||||
|
|
106
src/nulid.nim
106
src/nulid.nim
|
@ -1,6 +1,5 @@
|
|||
import std/[
|
||||
asyncdispatch,
|
||||
sysrand,
|
||||
times
|
||||
]
|
||||
|
||||
|
@ -9,30 +8,72 @@ import pkg/[
|
|||
nint128
|
||||
]
|
||||
|
||||
const insecureRandom = defined(nulidInsecureRandom) or defined(js) # No sysrand on the JS backend
|
||||
|
||||
when insecureRandom:
|
||||
import std/random
|
||||
|
||||
else:
|
||||
import std/sysrand
|
||||
|
||||
# Relying on internal implementation details isn't great, but it's unlikely to change so...
|
||||
import pkg/nint128/vendor/stew/endians2
|
||||
|
||||
const HighUint80 = u128("1208925819614629174706176")
|
||||
|
||||
type
|
||||
NULID* = object
|
||||
## An object representing a ULID.
|
||||
timestamp*: int64 = 0
|
||||
randomness*: UInt128 = 0.u128
|
||||
|
||||
NULIDGenerator* = ref object
|
||||
lastTime: int64 = 0
|
||||
random: UInt128 = u128(0)
|
||||
## A NULID generator object, contains details needed to follow the spec.
|
||||
## A generator was made to be compliant with the NULID spec and also to be
|
||||
## threadsafe not use globals that could change.
|
||||
lastTime: int64 ## Timestamp of last ULID
|
||||
random: UInt128 ## A random number
|
||||
|
||||
when insecureRandom:
|
||||
rand: Rand ## Random generator when using insecure random
|
||||
|
||||
proc initNulidGenerator*(): NULIDGenerator =
|
||||
## Initialises a `NULIDGenerator` for use.
|
||||
result = NULIDGenerator(lastTime: 0, random: 0.u128)
|
||||
|
||||
when insecureRandom:
|
||||
result.rand = initRand()
|
||||
|
||||
let globalGen = initNulidGenerator()
|
||||
|
||||
func swapBytes(x: Int128): Int128 =
|
||||
result.lo = swapBytes(cast[uint64](x.hi))
|
||||
result.hi = cast[int64](swapBytes(x.lo))
|
||||
|
||||
proc toArray*[T](oa: openArray[T], size: static Slice[int]): array[size.len, T] =
|
||||
func toArray[T](oa: openArray[T], size: static Slice[int]): array[size.len, T] =
|
||||
result[0..<size.len] = oa[size]
|
||||
|
||||
proc randomBits(): UInt128 =
|
||||
let rnd = urandom(10)
|
||||
proc randomBits(n: NULIDGenerator): UInt128 =
|
||||
var arr: array[16, byte]
|
||||
|
||||
result = UInt128.fromBytesBE(@[0.byte, 0, 0, 0, 0, 0] & rnd)
|
||||
when insecureRandom:
|
||||
var rnd: array[10, byte]
|
||||
|
||||
rnd[0..7] = cast[array[8, byte]](n.rand.next())
|
||||
rnd[8..9] = cast[array[2, byte]](n.rand.rand(high(int16)).int16)
|
||||
|
||||
arr[6..15] = rnd
|
||||
|
||||
else:
|
||||
var rnd: array[10, byte]
|
||||
|
||||
if not urandom(rnd):
|
||||
raise newException(OSError, "Was unable to use a secure source of randomness! " &
|
||||
"Please either compile with `--define:nulidInsecureRandom` or fix this!")
|
||||
|
||||
arr[6..15] = rnd
|
||||
|
||||
result = UInt128.fromBytesBE(arr)
|
||||
|
||||
template getTime: int64 = (epochTime() * 1000).int64
|
||||
|
||||
|
@ -44,6 +85,7 @@ proc wait(gen: NULIDGenerator): Future[int64] {.async.} =
|
|||
result = getTime()
|
||||
|
||||
proc nulid*(gen: NULIDGenerator, timestamp: int64 = 0): Future[NULID] {.async.} =
|
||||
## Asynchronously generate a `NULID`.
|
||||
if timestamp == 0:
|
||||
var now = getTime()
|
||||
|
||||
|
@ -57,7 +99,7 @@ proc nulid*(gen: NULIDGenerator, timestamp: int64 = 0): Future[NULID] {.async.}
|
|||
now = await gen.wait()
|
||||
|
||||
else:
|
||||
gen.random = randomBits()
|
||||
gen.random = gen.randomBits()
|
||||
|
||||
result.timestamp = now
|
||||
|
||||
|
@ -66,29 +108,55 @@ proc nulid*(gen: NULIDGenerator, timestamp: int64 = 0): Future[NULID] {.async.}
|
|||
result.randomness = gen.random
|
||||
|
||||
proc nulidSync*(gen: NULIDGenerator, timestamp: int64 = 0): NULID =
|
||||
## Synchronously generate a `NULID`.
|
||||
result = waitFor gen.nulid(timestamp)
|
||||
|
||||
proc toInt128*(ulid: NULID): Int128 =
|
||||
proc nulid*(timestamp: int64 = 0): Future[NULID] =
|
||||
## Asynchronously generate a `NULID` using the global generator.
|
||||
result = nulid(globalGen, timestamp)
|
||||
|
||||
proc nulidSync*(timestamp: int64 = 0): NULID =
|
||||
## Synchronously generate a `NULID` using the global generator.
|
||||
runnableExamples:
|
||||
echo nulidSync()
|
||||
|
||||
result = waitFor nulid(timestamp)
|
||||
|
||||
func toInt128*(ulid: NULID): Int128 =
|
||||
## Allows for a ULID to be converted to an Int128.
|
||||
runnableExamples:
|
||||
echo nulidSync().toInt128()
|
||||
|
||||
result = i128(ulid.timestamp) shl 80
|
||||
|
||||
result.hi += cast[int64](ulid.randomness.hi)
|
||||
result.lo += ulid.randomness.lo
|
||||
|
||||
proc fromInt128*(_: typedesc[NULID], val: Int128): NULID =
|
||||
func fromInt128*(_: typedesc[NULID], val: Int128): NULID =
|
||||
## Parses an Int128 to a NULID.
|
||||
result.timestamp = (val shr 16).hi
|
||||
result.randomness = UInt128(
|
||||
hi: cast[uint64]((val.hi shl 48) shr 48),
|
||||
lo: val.lo
|
||||
)
|
||||
|
||||
proc toBytes*(ulid: NULID): array[16, byte] =
|
||||
func toBytes*(ulid: NULID): array[16, byte] =
|
||||
## Allows for a NULID to be converted to a byte array.
|
||||
runnableExamples:
|
||||
let
|
||||
nulid = parseNulid("01H999MBGTEA8BDS0M5AWEBB1A")
|
||||
nulidBytes = [1.byte, 138, 82, 154, 46, 26, 114, 144, 182, 228, 20, 42, 184, 229, 172, 42]
|
||||
|
||||
echo nulid == NULID.fromBytes(nulidBytes)
|
||||
|
||||
when cpuEndian == littleEndian:
|
||||
return cast[array[16, byte]](ulid.toInt128().swapBytes())
|
||||
|
||||
else:
|
||||
return cast[array[16, byte]](ulid.toInt128())
|
||||
|
||||
proc fromBytes*(_: typedesc[NULID], ulidBytes: openArray[byte]): NULID =
|
||||
func fromBytes*(_: typedesc[NULID], ulidBytes: openArray[byte]): NULID =
|
||||
## Parses a byte array to a NULID.
|
||||
if ulidBytes.len != 16:
|
||||
raise newException(RangeDefect, "Given byte array must be 16 bytes long!")
|
||||
|
||||
|
@ -98,15 +166,23 @@ proc fromBytes*(_: typedesc[NULID], ulidBytes: openArray[byte]): NULID =
|
|||
else:
|
||||
return NULID.fromInt128(cast[Int128](ulidBytes.toArray(0..15)))
|
||||
|
||||
proc parseNulid*(ulidStr: string): NULID =
|
||||
func parseNulid*(ulidStr: string): NULID =
|
||||
## Parses a string to a NULID.
|
||||
runnableExamples:
|
||||
echo parseNulid("01H999MBGTEA8BDS0M5AWEBB1A")
|
||||
|
||||
if ulidStr.len != 26:
|
||||
raise newException(RangeDefect, "Invalid ULID! Must be 26 characters long!")
|
||||
|
||||
result.timestamp = int64.decode(ulidStr[0..9])
|
||||
result.randomness = UInt128.decode(ulidStr[10..25])
|
||||
|
||||
proc `$`*(ulid: NULID): string =
|
||||
func `$`*(ulid: NULID): string =
|
||||
## Returns the string representation of a ULID.
|
||||
runnableExamples:
|
||||
echo nulidSync()
|
||||
|
||||
result = Int128.encode(ulid.toInt128(), 26)
|
||||
|
||||
if result.len < 26:
|
||||
if result.len < 26: # Crappy fix
|
||||
result.insert("0")
|
||||
|
|
|
@ -11,7 +11,7 @@ import pkg/nint128
|
|||
|
||||
import nulid
|
||||
|
||||
let gen = NULIDGenerator()
|
||||
let gen = initNulidGenerator()
|
||||
|
||||
test "NULID Generation":
|
||||
for _ in 0..5:
|
||||
|
|
Loading…
Reference in a new issue