First run of pre-commit.
All checks were successful
/ Build and Release Library (push) Successful in 1m38s
All checks were successful
/ Build and Release Library (push) Successful in 1m38s
This commit is contained in:
parent
ea82a5ff44
commit
df41b9bf01
10 changed files with 414 additions and 388 deletions
|
|
@ -2,43 +2,48 @@ package gs.std.v0
|
|||
|
||||
import java.util.Base64
|
||||
|
||||
/**
|
||||
* Represents a blob -- some array of bytes.
|
||||
/** Represents a blob -- some array of bytes.
|
||||
*
|
||||
* @param data The underlying data.
|
||||
* @param data
|
||||
* The underlying data.
|
||||
*/
|
||||
final class Blob(private val data: Array[Byte]) extends IndexedSeq[Byte]:
|
||||
|
||||
/** @inheritDocs */
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def apply(i: Int): Byte = byteAt(i)
|
||||
|
||||
/** @inheritDocs */
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def length: Int = data.length
|
||||
|
||||
/**
|
||||
* @return The number of bytes in this blob, expressed as a count.
|
||||
*/
|
||||
/** @return
|
||||
* The number of bytes in this blob, expressed as a count.
|
||||
*/
|
||||
def numberOfBytes: ByteCount = ByteCount(data.length)
|
||||
|
||||
/** @inheritDocs */
|
||||
override def equals(obj: Any): Boolean =
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def equals(obj: Any): Boolean =
|
||||
obj match
|
||||
case other: Blob => data.sameElements(other.data)
|
||||
case other: Blob => data.sameElements(other.data)
|
||||
case other: Array[Byte] => data.sameElements(other)
|
||||
case _ => false
|
||||
case _ => false
|
||||
|
||||
/** @inheritDocs */
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def hashCode(): Int = data.hashCode()
|
||||
|
||||
/**
|
||||
* Retrieve the byte at the given index.
|
||||
*
|
||||
* @param index The index.
|
||||
* @return The byte stored at the given index.
|
||||
*/
|
||||
/** Retrieve the byte at the given index.
|
||||
*
|
||||
* @param index
|
||||
* The index.
|
||||
* @return
|
||||
* The byte stored at the given index.
|
||||
*/
|
||||
def byteAt(index: Int): Byte = data.apply(index)
|
||||
|
||||
/**
|
||||
* @return This byte array, encoded as a base64 string.
|
||||
*/
|
||||
/** @return
|
||||
* This byte array, encoded as a base64 string.
|
||||
*/
|
||||
def base64(): String = Base64.getEncoder().encodeToString(data)
|
||||
|
|
|
|||
|
|
@ -1,132 +1,138 @@
|
|||
package gs.std.v0
|
||||
|
||||
/**
|
||||
* Opaque type for some number of bytes (>= 0).
|
||||
/** Opaque type for some number of bytes (>= 0).
|
||||
*/
|
||||
opaque type ByteCount = Long
|
||||
|
||||
/**
|
||||
* Opaque type for some number of bytes (>= 0).
|
||||
/** Opaque type for some number of bytes (>= 0).
|
||||
*/
|
||||
object ByteCount:
|
||||
|
||||
/**
|
||||
* 0 bytes.
|
||||
*/
|
||||
/** 0 bytes.
|
||||
*/
|
||||
final val Zero: ByteCount = 0
|
||||
|
||||
/**
|
||||
* 1,000 bytes.
|
||||
*/
|
||||
/** 1,000 bytes.
|
||||
*/
|
||||
final val OneKilobyte: ByteCount = 1000
|
||||
|
||||
/**
|
||||
* 1,000,000 bytes.
|
||||
*/
|
||||
/** 1,000,000 bytes.
|
||||
*/
|
||||
final val OneMegabyte: ByteCount = 1000000
|
||||
|
||||
/**
|
||||
* 1,000,000,000 bytes.
|
||||
*/
|
||||
/** 1,000,000,000 bytes.
|
||||
*/
|
||||
final val OneGigabyte: ByteCount = 1000000000
|
||||
|
||||
/**
|
||||
* Express the given number as a byte count. All values are normalized to the
|
||||
* absolute value -- negative values are coerced to positive.
|
||||
*
|
||||
* @param value The input integer.
|
||||
* @return The [[ByteCount]] instance.
|
||||
*/
|
||||
/** Express the given number as a byte count. All values are normalized to the
|
||||
* absolute value -- negative values are coerced to positive.
|
||||
*
|
||||
* @param value
|
||||
* The input integer.
|
||||
* @return
|
||||
* The [[ByteCount]] instance.
|
||||
*/
|
||||
def apply(value: Long): ByteCount = Math.abs(value)
|
||||
|
||||
/**
|
||||
* 1 kilobyte = 1,000 bytes
|
||||
*
|
||||
* @param kb The number of kilobytes.
|
||||
* @return The number of bytes.
|
||||
*/
|
||||
/** 1 kilobyte = 1,000 bytes
|
||||
*
|
||||
* @param kb
|
||||
* The number of kilobytes.
|
||||
* @return
|
||||
* The number of bytes.
|
||||
*/
|
||||
def fromKilobytes(kb: Long): ByteCount =
|
||||
Math.abs(kb) * 1000L
|
||||
|
||||
/**
|
||||
* 1 megabyte = 1,000,000 bytes
|
||||
*
|
||||
* @param mb The number of megabytes.
|
||||
* @return The number of bytes.
|
||||
*/
|
||||
/** 1 megabyte = 1,000,000 bytes
|
||||
*
|
||||
* @param mb
|
||||
* The number of megabytes.
|
||||
* @return
|
||||
* The number of bytes.
|
||||
*/
|
||||
def fromMegabytes(mb: Long): ByteCount =
|
||||
Math.abs(mb) * 1000000L
|
||||
|
||||
/**
|
||||
* 1 gigabyte = 1,000,000,000 bytes
|
||||
*
|
||||
* @param mb The number of gigabytes.
|
||||
* @return The number of bytes.
|
||||
*/
|
||||
/** 1 gigabyte = 1,000,000,000 bytes
|
||||
*
|
||||
* @param mb
|
||||
* The number of gigabytes.
|
||||
* @return
|
||||
* The number of bytes.
|
||||
*/
|
||||
def fromGigabytes(gb: Long): ByteCount =
|
||||
Math.abs(gb) * 1000000000
|
||||
|
||||
given CanEqual[ByteCount, ByteCount] = CanEqual.derived
|
||||
|
||||
given Ordering[ByteCount] with
|
||||
/** @inheritDocs */
|
||||
def compare(x: ByteCount, y: ByteCount): Int =
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
def compare(
|
||||
x: ByteCount,
|
||||
y: ByteCount
|
||||
): Int =
|
||||
if x > y then 1 else if x == y then 0 else -1
|
||||
|
||||
extension (byteCount: ByteCount)
|
||||
/**
|
||||
* @return The underlying `Long`.
|
||||
*/
|
||||
/** @return
|
||||
* The underlying `Long`.
|
||||
*/
|
||||
def unwrap(): Long = byteCount
|
||||
|
||||
/**
|
||||
* @return The underlying `Long`.
|
||||
*/
|
||||
/** @return
|
||||
* The underlying `Long`.
|
||||
*/
|
||||
def toLong(): Long = byteCount
|
||||
|
||||
/**
|
||||
* 1 kilobyte = 1,000 bytes
|
||||
*
|
||||
* @return The number of kilobytes represented by this count.
|
||||
*/
|
||||
/** 1 kilobyte = 1,000 bytes
|
||||
*
|
||||
* @return
|
||||
* The number of kilobytes represented by this count.
|
||||
*/
|
||||
def toKilobytes(): Double = byteCount / 1000.0
|
||||
|
||||
/**
|
||||
* 1 megabyte = 1,000,000 bytes.
|
||||
*
|
||||
* @return The number of megabytes represented by this count.
|
||||
*/
|
||||
/** 1 megabyte = 1,000,000 bytes.
|
||||
*
|
||||
* @return
|
||||
* The number of megabytes represented by this count.
|
||||
*/
|
||||
def toMegabytes(): Double = byteCount / 1000000.0
|
||||
|
||||
/**
|
||||
* 1 gigabyte = 1,000,000,000 bytes.
|
||||
*
|
||||
* @return The number of gigabytes represented by this count.
|
||||
*/
|
||||
/** 1 gigabyte = 1,000,000,000 bytes.
|
||||
*
|
||||
* @return
|
||||
* The number of gigabytes represented by this count.
|
||||
*/
|
||||
def toGigabytes(): Double = byteCount / 1000000000.0
|
||||
|
||||
/**
|
||||
* Add some count to this one.
|
||||
*
|
||||
* @param that The number to add.
|
||||
* @return The sum of the numbers.
|
||||
*/
|
||||
/** Add some count to this one.
|
||||
*
|
||||
* @param that
|
||||
* The number to add.
|
||||
* @return
|
||||
* The sum of the numbers.
|
||||
*/
|
||||
def +(that: ByteCount): ByteCount = byteCount + that
|
||||
|
||||
/**
|
||||
* Multiply this count by some other count.
|
||||
*
|
||||
* @param that The number to multiply by.
|
||||
* @return The product of the numbers.
|
||||
*/
|
||||
/** Multiply this count by some other count.
|
||||
*
|
||||
* @param that
|
||||
* The number to multiply by.
|
||||
* @return
|
||||
* The product of the numbers.
|
||||
*/
|
||||
def *(that: ByteCount): ByteCount = byteCount * that
|
||||
|
||||
/**
|
||||
* Check if this value is the same as some number.
|
||||
*
|
||||
* @param value The number to compare against.
|
||||
* @return True if the values are equal, false otherwise.
|
||||
*/
|
||||
/** Check if this value is the same as some number.
|
||||
*
|
||||
* @param value
|
||||
* The number to compare against.
|
||||
* @return
|
||||
* True if the values are equal, false otherwise.
|
||||
*/
|
||||
def equal(value: Int): Boolean = byteCount == value.toLong
|
||||
|
||||
end ByteCount
|
||||
|
|
|
|||
|
|
@ -2,22 +2,21 @@ package gs.std.v0
|
|||
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Opaque type that represents the instant something was created.
|
||||
/** Opaque type that represents the instant something was created.
|
||||
*/
|
||||
opaque type CreatedAt = Instant
|
||||
|
||||
/**
|
||||
* Opaque type that represents the instant something was created.
|
||||
/** Opaque type that represents the instant something was created.
|
||||
*/
|
||||
object CreatedAt:
|
||||
|
||||
/**
|
||||
* Instantiate a new [[CreatedAt]] from the given `java.time.Instant`.
|
||||
*
|
||||
* @param value The value to semantically represent.
|
||||
* @return The new [[CreatedAt]].
|
||||
*/
|
||||
/** Instantiate a new [[CreatedAt]] from the given `java.time.Instant`.
|
||||
*
|
||||
* @param value
|
||||
* The value to semantically represent.
|
||||
* @return
|
||||
* The new [[CreatedAt]].
|
||||
*/
|
||||
def apply(value: Instant): CreatedAt = value
|
||||
|
||||
given CanEqual[CreatedAt, CreatedAt] = CanEqual.derived
|
||||
|
|
@ -25,14 +24,14 @@ object CreatedAt:
|
|||
given Ordering[CreatedAt] = Ordering[Instant]
|
||||
|
||||
extension (createdAt: Instant)
|
||||
/**
|
||||
* @return The underlying `java.time.Instant`.
|
||||
*/
|
||||
/** @return
|
||||
* The underlying `java.time.Instant`.
|
||||
*/
|
||||
def unwrap(): Instant = createdAt
|
||||
|
||||
/**
|
||||
* @return The underlying `java.time.Instant`.
|
||||
*/
|
||||
/** @return
|
||||
* The underlying `java.time.Instant`.
|
||||
*/
|
||||
def toInstant(): Instant = createdAt
|
||||
|
||||
end CreatedAt
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
package gs.std.v0
|
||||
|
||||
/**
|
||||
* Interface for byte decoding from encoded String formats.
|
||||
/** Interface for byte decoding from encoded String formats.
|
||||
*/
|
||||
trait Decoder[-A <: EncodedString]:
|
||||
/**
|
||||
* Decode an input string to an array of bytes.
|
||||
*
|
||||
* @param input The input to decode.
|
||||
* @return The decoded byte array.
|
||||
*/
|
||||
/** Decode an input string to an array of bytes.
|
||||
*
|
||||
* @param input
|
||||
* The input to decode.
|
||||
* @return
|
||||
* The decoded byte array.
|
||||
*/
|
||||
def decode(input: A): Array[Byte]
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ final class B64(
|
|||
|
||||
object B64:
|
||||
|
||||
/** Instantiate [[B64]] from the given string. Assumes that the input is
|
||||
* base64-encoded.
|
||||
/** Instantiate [[B64]] from the given string. Assumes that the input is
|
||||
* base64-encoded.
|
||||
*
|
||||
* This function does NOT validate the input.
|
||||
*
|
||||
|
|
@ -97,8 +97,8 @@ final class B64Url(
|
|||
|
||||
object B64Url:
|
||||
|
||||
/** Instantiate [[B64Url]] from the given string. Assumes that the input is
|
||||
* base64-encoded.
|
||||
/** Instantiate [[B64Url]] from the given string. Assumes that the input is
|
||||
* base64-encoded.
|
||||
*
|
||||
* This function does NOT validate the input.
|
||||
*
|
||||
|
|
@ -143,8 +143,8 @@ final class Hex(
|
|||
|
||||
object Hex:
|
||||
|
||||
/** Instantiate [[Hex]] from the given string. Assumes that the input is
|
||||
* hex-encoded.
|
||||
/** Instantiate [[Hex]] from the given string. Assumes that the input is
|
||||
* hex-encoded.
|
||||
*
|
||||
* This function does NOT validate the input.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,105 +1,108 @@
|
|||
package gs.std.v0
|
||||
|
||||
import java.security.MessageDigest
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
|
||||
/**
|
||||
* Opaque type representing a MD5 hash.
|
||||
/** Opaque type representing a MD5 hash.
|
||||
*/
|
||||
opaque type MD5 = Array[Byte]
|
||||
|
||||
/**
|
||||
* Opaque type representing a MD5 hash.
|
||||
/** Opaque type representing a MD5 hash.
|
||||
*/
|
||||
object MD5:
|
||||
|
||||
/**
|
||||
* MD5 hashes are exactly 16 bytes.
|
||||
*/
|
||||
/** MD5 hashes are exactly 16 bytes.
|
||||
*/
|
||||
final val NumberOfBytes: ByteCount = ByteCount(16)
|
||||
|
||||
/**
|
||||
* The algorithm name is "MD5".
|
||||
*/
|
||||
/** The algorithm name is "MD5".
|
||||
*/
|
||||
final val Algorithm: String = "MD5"
|
||||
|
||||
/**
|
||||
* Instantiate a [[MD5]] from the given byte array. This function does not
|
||||
* know whether the array is actually a calculated hash.
|
||||
*
|
||||
* Typically used for loading pre-validated hashes (e.g. from a database).
|
||||
*
|
||||
* @param bytes The bytes - must contain exactly 16 bytes.
|
||||
* @return The new [[MD5]] instance.
|
||||
*/
|
||||
def fromBytes(bytes: Array[Byte]): MD5 =
|
||||
if NumberOfBytes.equal(bytes.length) then
|
||||
bytes
|
||||
/** Instantiate a [[MD5]] from the given byte array. This function does not
|
||||
* know whether the array is actually a calculated hash.
|
||||
*
|
||||
* Typically used for loading pre-validated hashes (e.g. from a database).
|
||||
*
|
||||
* @param bytes
|
||||
* The bytes - must contain exactly 16 bytes.
|
||||
* @return
|
||||
* The new [[MD5]] instance.
|
||||
*/
|
||||
def fromBytes(bytes: Array[Byte]): MD5 =
|
||||
if NumberOfBytes.equal(bytes.length) then bytes
|
||||
else
|
||||
throw IllegalArgumentException(s"MD5 values must be exactly $NumberOfBytes bytes.")
|
||||
throw IllegalArgumentException(
|
||||
s"MD5 values must be exactly $NumberOfBytes bytes."
|
||||
)
|
||||
|
||||
/**
|
||||
* Calculate the MD5 hash for the given byte array.
|
||||
*
|
||||
* @param data The byte array.
|
||||
* @return The calculated [[MD5]].
|
||||
*/
|
||||
def calculate(data: Array[Byte]): MD5 =
|
||||
/** Calculate the MD5 hash for the given byte array.
|
||||
*
|
||||
* @param data
|
||||
* The byte array.
|
||||
* @return
|
||||
* The calculated [[MD5]].
|
||||
*/
|
||||
def calculate(data: Array[Byte]): MD5 =
|
||||
MessageDigest.getInstance(Algorithm).digest(data)
|
||||
|
||||
/*
|
||||
* Calculate the MD5 hash for the given string.
|
||||
/* Calculate the MD5 hash for the given string.
|
||||
*
|
||||
* @param data The string data.
|
||||
* @param charset The character set of the string. Defaults to UTF-8.
|
||||
* @return The calculated [[MD5]].
|
||||
*/
|
||||
def calculate(data: String, charset: Charset = StandardCharsets.UTF_8): MD5 =
|
||||
* @return The calculated [[MD5]]. */
|
||||
def calculate(
|
||||
data: String,
|
||||
charset: Charset = StandardCharsets.UTF_8
|
||||
): MD5 =
|
||||
calculate(data.getBytes(charset))
|
||||
|
||||
extension (md5: MD5)
|
||||
/**
|
||||
* @return The underlying byte array.
|
||||
*/
|
||||
/** @return
|
||||
* The underlying byte array.
|
||||
*/
|
||||
def unwrap(): Array[Byte] = md5
|
||||
|
||||
/**
|
||||
* @return The underlying byte array.
|
||||
*/
|
||||
/** @return
|
||||
* The underlying byte array.
|
||||
*/
|
||||
def toBytes(): Array[Byte] = md5
|
||||
|
||||
/**
|
||||
* Get the byte at the given index (0 to 15).
|
||||
*
|
||||
* Throws an exception if an out-of-bound index is given.
|
||||
*
|
||||
* @param index The byte index (0 to 15).
|
||||
* @return The byte at the specified index.
|
||||
*/
|
||||
/** Get the byte at the given index (0 to 15).
|
||||
*
|
||||
* Throws an exception if an out-of-bound index is given.
|
||||
*
|
||||
* @param index
|
||||
* The byte index (0 to 15).
|
||||
* @return
|
||||
* The byte at the specified index.
|
||||
*/
|
||||
def getByte(index: Int): Byte =
|
||||
if index < 0 || index >= NumberOfBytes.unwrap() then
|
||||
throw IndexOutOfBoundsException(s"Index $index out of MD5 bound of $NumberOfBytes bytes.")
|
||||
else
|
||||
md5.apply(index)
|
||||
throw IndexOutOfBoundsException(
|
||||
s"Index $index out of MD5 bound of $NumberOfBytes bytes."
|
||||
)
|
||||
else md5.apply(index)
|
||||
|
||||
/**
|
||||
* Determine if this hash is the same as some other hash. Compares each byte
|
||||
* in order.
|
||||
*
|
||||
* @param other The [[MD5]] to compare against.
|
||||
* @return True if the hashes are identical, false otherwise.
|
||||
*/
|
||||
/** Determine if this hash is the same as some other hash. Compares each
|
||||
* byte in order.
|
||||
*
|
||||
* @param other
|
||||
* The [[MD5]] to compare against.
|
||||
* @return
|
||||
* True if the hashes are identical, false otherwise.
|
||||
*/
|
||||
def isSame(other: MD5): Boolean = md5.sameElements(other)
|
||||
|
||||
/**
|
||||
* @return This hash encoded to a Base64 string.
|
||||
*/
|
||||
/** @return
|
||||
* This hash encoded to a Base64 string.
|
||||
*/
|
||||
def base64(): EncodedString = Base64Encoder.encode(md5)
|
||||
|
||||
/**
|
||||
* @return This hash encoded to a Hexadecimal string.
|
||||
*/
|
||||
/** @return
|
||||
* This hash encoded to a Hexadecimal string.
|
||||
*/
|
||||
def hex(): EncodedString = HexEncoder.encode(md5)
|
||||
|
||||
end MD5
|
||||
|
|
|
|||
|
|
@ -1,41 +1,37 @@
|
|||
package gs.std.v0
|
||||
|
||||
/**
|
||||
* Opaque type for the natural numbers (including 0).
|
||||
/** Opaque type for the natural numbers (including 0).
|
||||
*/
|
||||
opaque type Nat = Int
|
||||
|
||||
/**
|
||||
* Opaque type for the natural numbers (including 0).
|
||||
/** Opaque type for the natural numbers (including 0).
|
||||
*/
|
||||
object Nat:
|
||||
|
||||
sealed trait Invalid
|
||||
object Invalid extends Invalid
|
||||
|
||||
/**
|
||||
* The number 0.
|
||||
*/
|
||||
/** The number 0.
|
||||
*/
|
||||
final val Zero: Nat = 0
|
||||
|
||||
/**
|
||||
* The number 1.
|
||||
*/
|
||||
/** The number 1.
|
||||
*/
|
||||
final val One: Nat = 1
|
||||
|
||||
/**
|
||||
* Express the given integer as a natural number.
|
||||
*
|
||||
* Throws an `IllegalArgumentException` if a negative value is given as input.
|
||||
*
|
||||
* @param value The input integer.
|
||||
* @return The [[Nat]] instance.
|
||||
*/
|
||||
/** Express the given integer as a natural number.
|
||||
*
|
||||
* Throws an `IllegalArgumentException` if a negative value is given as
|
||||
* input.
|
||||
*
|
||||
* @param value
|
||||
* The input integer.
|
||||
* @return
|
||||
* The [[Nat]] instance.
|
||||
*/
|
||||
def apply(value: Int): Nat =
|
||||
if value >= 0 then
|
||||
value
|
||||
else
|
||||
throw new IllegalArgumentException("Nat values must be 0 or greater.")
|
||||
if value >= 0 then value
|
||||
else throw new IllegalArgumentException("Nat values must be 0 or greater.")
|
||||
|
||||
def validate(value: Int): Either[Invalid, Nat] =
|
||||
if value >= 0 then Right(value) else Left(Invalid)
|
||||
|
|
@ -43,39 +39,46 @@ object Nat:
|
|||
given CanEqual[Nat, Nat] = CanEqual.derived
|
||||
|
||||
given Ordering[Nat] with
|
||||
/** @inheritDocs */
|
||||
def compare(x: Nat, y: Nat): Int = x - y
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
def compare(
|
||||
x: Nat,
|
||||
y: Nat
|
||||
): Int = x - y
|
||||
|
||||
extension (nat: Nat)
|
||||
/**
|
||||
* @return The underlying integer.
|
||||
*/
|
||||
/** @return
|
||||
* The underlying integer.
|
||||
*/
|
||||
def unwrap(): Int = nat
|
||||
|
||||
/**
|
||||
* @return The next integer.
|
||||
*/
|
||||
/** @return
|
||||
* The next integer.
|
||||
*/
|
||||
def next(): Nat = nat + 1
|
||||
|
||||
/**
|
||||
* @return The next integer.
|
||||
*/
|
||||
/** @return
|
||||
* The next integer.
|
||||
*/
|
||||
def increment(): Nat = nat + 1
|
||||
|
||||
/**
|
||||
* Add some natural number to this one.
|
||||
*
|
||||
* @param that The number to add.
|
||||
* @return The sum of the numbers.
|
||||
*/
|
||||
/** Add some natural number to this one.
|
||||
*
|
||||
* @param that
|
||||
* The number to add.
|
||||
* @return
|
||||
* The sum of the numbers.
|
||||
*/
|
||||
def +(that: Nat): Nat = nat + that
|
||||
|
||||
/**
|
||||
* Multiply this natural number by some other natural number.
|
||||
*
|
||||
* @param that The number to multiply by.
|
||||
* @return The product of the numbers.
|
||||
*/
|
||||
/** Multiply this natural number by some other natural number.
|
||||
*
|
||||
* @param that
|
||||
* The number to multiply by.
|
||||
* @return
|
||||
* The product of the numbers.
|
||||
*/
|
||||
def *(that: Nat): Nat = nat * that
|
||||
|
||||
end Nat
|
||||
|
|
|
|||
|
|
@ -1,105 +1,112 @@
|
|||
package gs.std.v0
|
||||
|
||||
import java.security.MessageDigest
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
|
||||
/**
|
||||
* Opaque type representing a SHA-256 hash.
|
||||
/** Opaque type representing a SHA-256 hash.
|
||||
*/
|
||||
opaque type SHA256 = Array[Byte]
|
||||
|
||||
/**
|
||||
* Opaque type representing a SHA-256 hash.
|
||||
/** Opaque type representing a SHA-256 hash.
|
||||
*/
|
||||
object SHA256:
|
||||
|
||||
/**
|
||||
* SHA-256 hashes are exactly 32 bytes.
|
||||
*/
|
||||
/** SHA-256 hashes are exactly 32 bytes.
|
||||
*/
|
||||
final val NumberOfBytes: ByteCount = ByteCount(32)
|
||||
|
||||
/**
|
||||
* The algorithm name is "SHA-256".
|
||||
*/
|
||||
/** The algorithm name is "SHA-256".
|
||||
*/
|
||||
final val Algorithm: String = "SHA-256"
|
||||
|
||||
/**
|
||||
* Instantiate a [[SHA256]] from the given byte array. This function does not
|
||||
* know whether the array is actually a calculated hash.
|
||||
*
|
||||
* Typically used for loading pre-validated hashes (e.g. from a database).
|
||||
*
|
||||
* @param bytes The bytes - must contain exactly 32 bytes.
|
||||
* @return The new [[SHA256]] instance.
|
||||
*/
|
||||
def fromBytes(bytes: Array[Byte]): SHA256 =
|
||||
if NumberOfBytes.equal(bytes.length) then
|
||||
bytes
|
||||
/** Instantiate a [[SHA256]] from the given byte array. This function does not
|
||||
* know whether the array is actually a calculated hash.
|
||||
*
|
||||
* Typically used for loading pre-validated hashes (e.g. from a database).
|
||||
*
|
||||
* @param bytes
|
||||
* The bytes - must contain exactly 32 bytes.
|
||||
* @return
|
||||
* The new [[SHA256]] instance.
|
||||
*/
|
||||
def fromBytes(bytes: Array[Byte]): SHA256 =
|
||||
if NumberOfBytes.equal(bytes.length) then bytes
|
||||
else
|
||||
throw IllegalArgumentException(s"SHA-256 values must be exactly $NumberOfBytes bytes.")
|
||||
throw IllegalArgumentException(
|
||||
s"SHA-256 values must be exactly $NumberOfBytes bytes."
|
||||
)
|
||||
|
||||
/**
|
||||
* Calculate the SHA-256 hash for the given byte array.
|
||||
*
|
||||
* @param data The byte array.
|
||||
* @return The calculated [[SHA256]].
|
||||
*/
|
||||
def calculate(data: Array[Byte]): SHA256 =
|
||||
/** Calculate the SHA-256 hash for the given byte array.
|
||||
*
|
||||
* @param data
|
||||
* The byte array.
|
||||
* @return
|
||||
* The calculated [[SHA256]].
|
||||
*/
|
||||
def calculate(data: Array[Byte]): SHA256 =
|
||||
MessageDigest.getInstance(Algorithm).digest(data)
|
||||
|
||||
/**
|
||||
* Calculate the SHA-256 hash for the given string.
|
||||
*
|
||||
* @param data The string data.
|
||||
* @param charset The character set of the string. Defaults to UTF-8.
|
||||
* @return The calculated [[SHA256]].
|
||||
*/
|
||||
def calculate(data: String, charset: Charset = StandardCharsets.UTF_8): SHA256 =
|
||||
/** Calculate the SHA-256 hash for the given string.
|
||||
*
|
||||
* @param data
|
||||
* The string data.
|
||||
* @param charset
|
||||
* The character set of the string. Defaults to UTF-8.
|
||||
* @return
|
||||
* The calculated [[SHA256]].
|
||||
*/
|
||||
def calculate(
|
||||
data: String,
|
||||
charset: Charset = StandardCharsets.UTF_8
|
||||
): SHA256 =
|
||||
calculate(data.getBytes(charset))
|
||||
|
||||
extension (sha: SHA256)
|
||||
/**
|
||||
* @return The underlying byte array.
|
||||
*/
|
||||
/** @return
|
||||
* The underlying byte array.
|
||||
*/
|
||||
def unwrap(): Array[Byte] = sha
|
||||
|
||||
/**
|
||||
* @return The underlying byte array.
|
||||
*/
|
||||
/** @return
|
||||
* The underlying byte array.
|
||||
*/
|
||||
def toBytes(): Array[Byte] = sha
|
||||
|
||||
/**
|
||||
* Get the byte at the given index (0 to 31).
|
||||
*
|
||||
* Throws an exception if an out-of-bound index is given.
|
||||
*
|
||||
* @param index The byte index (0 to 31).
|
||||
* @return The byte at the specified index.
|
||||
*/
|
||||
/** Get the byte at the given index (0 to 31).
|
||||
*
|
||||
* Throws an exception if an out-of-bound index is given.
|
||||
*
|
||||
* @param index
|
||||
* The byte index (0 to 31).
|
||||
* @return
|
||||
* The byte at the specified index.
|
||||
*/
|
||||
def getByte(index: Int): Byte =
|
||||
if index < 0 || index >= NumberOfBytes.unwrap() then
|
||||
throw IndexOutOfBoundsException(s"Index $index out of SHA-256 bound of $NumberOfBytes bytes.")
|
||||
else
|
||||
sha.apply(index)
|
||||
throw IndexOutOfBoundsException(
|
||||
s"Index $index out of SHA-256 bound of $NumberOfBytes bytes."
|
||||
)
|
||||
else sha.apply(index)
|
||||
|
||||
/**
|
||||
* Determine if this hash is the same as some other hash. Compares each byte
|
||||
* in order.
|
||||
*
|
||||
* @param other The [[SHA256]] to compare against.
|
||||
* @return True if the hashes are identical, false otherwise.
|
||||
*/
|
||||
/** Determine if this hash is the same as some other hash. Compares each
|
||||
* byte in order.
|
||||
*
|
||||
* @param other
|
||||
* The [[SHA256]] to compare against.
|
||||
* @return
|
||||
* True if the hashes are identical, false otherwise.
|
||||
*/
|
||||
def isSame(other: SHA256): Boolean = sha.sameElements(other)
|
||||
|
||||
/**
|
||||
* @return This hash encoded to a Base64 string.
|
||||
*/
|
||||
/** @return
|
||||
* This hash encoded to a Base64 string.
|
||||
*/
|
||||
def base64(): EncodedString = Base64Encoder.encode(sha)
|
||||
|
||||
/**
|
||||
* @return This hash encoded to a Hexadecimal string.
|
||||
*/
|
||||
/** @return
|
||||
* This hash encoded to a Hexadecimal string.
|
||||
*/
|
||||
def hex(): EncodedString = HexEncoder.encode(sha)
|
||||
|
||||
end SHA256
|
||||
|
|
|
|||
|
|
@ -1,97 +1,102 @@
|
|||
package gs.std.v0
|
||||
|
||||
/**
|
||||
* Opaque type for collection sizes. Values are guaranteed to be 0 or greater.
|
||||
/** Opaque type for collection sizes. Values are guaranteed to be 0 or greater.
|
||||
*/
|
||||
opaque type Size = Int
|
||||
|
||||
/**
|
||||
* Opaque type for collection sizes. Values are guaranteed to be 0 or greater.
|
||||
/** Opaque type for collection sizes. Values are guaranteed to be 0 or greater.
|
||||
*/
|
||||
object Size:
|
||||
|
||||
sealed trait Invalid
|
||||
object Invalid extends Invalid
|
||||
|
||||
/**
|
||||
* The size 0.
|
||||
*/
|
||||
/** The size 0.
|
||||
*/
|
||||
final val Zero: Size = 0
|
||||
|
||||
/**
|
||||
* The size 1.
|
||||
*/
|
||||
/** The size 1.
|
||||
*/
|
||||
final val One: Size = 1
|
||||
|
||||
/**
|
||||
* Express the given integer as a size.
|
||||
*
|
||||
* Throws an `IllegalArgumentException` if a negative value is given as input.
|
||||
*
|
||||
* @param value The input integer.
|
||||
* @return The [[Size]] instance.
|
||||
*/
|
||||
/** Express the given integer as a size.
|
||||
*
|
||||
* Throws an `IllegalArgumentException` if a negative value is given as
|
||||
* input.
|
||||
*
|
||||
* @param value
|
||||
* The input integer.
|
||||
* @return
|
||||
* The [[Size]] instance.
|
||||
*/
|
||||
def apply(value: Int): Size =
|
||||
if value >= 0 then
|
||||
value
|
||||
else
|
||||
throw new IllegalArgumentException("Size values must be 0 or greater.")
|
||||
if value >= 0 then value
|
||||
else throw new IllegalArgumentException("Size values must be 0 or greater.")
|
||||
|
||||
def validate(value: Int): Either[Invalid, Size] =
|
||||
if value >= 0 then Right(value) else Left(Invalid)
|
||||
|
||||
/**
|
||||
* Express the size of any collection.
|
||||
*
|
||||
* @param iter The collection.
|
||||
* @return The size of the collection.
|
||||
*/
|
||||
/** Express the size of any collection.
|
||||
*
|
||||
* @param iter
|
||||
* The collection.
|
||||
* @return
|
||||
* The size of the collection.
|
||||
*/
|
||||
def of(iter: Iterable[?]): Size = iter.size
|
||||
|
||||
/**
|
||||
* Express the size of any array.
|
||||
*
|
||||
* @param arr The array.
|
||||
* @return The size of the array.
|
||||
*/
|
||||
/** Express the size of any array.
|
||||
*
|
||||
* @param arr
|
||||
* The array.
|
||||
* @return
|
||||
* The size of the array.
|
||||
*/
|
||||
def of(arr: Array[?]): Size = arr.length
|
||||
|
||||
given CanEqual[Size, Size] = CanEqual.derived
|
||||
|
||||
given Ordering[Size] with
|
||||
/** @inheritDocs */
|
||||
def compare(x: Size, y: Size): Int = x - y
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
def compare(
|
||||
x: Size,
|
||||
y: Size
|
||||
): Int = x - y
|
||||
|
||||
extension (size: Size)
|
||||
/**
|
||||
* @return The underlying integer.
|
||||
*/
|
||||
/** @return
|
||||
* The underlying integer.
|
||||
*/
|
||||
def unwrap(): Int = size
|
||||
|
||||
/**
|
||||
* @return The next value.
|
||||
*/
|
||||
/** @return
|
||||
* The next value.
|
||||
*/
|
||||
def next(): Size = size + 1
|
||||
|
||||
/**
|
||||
* @return The next value.
|
||||
*/
|
||||
/** @return
|
||||
* The next value.
|
||||
*/
|
||||
def increment(): Size = size + 1
|
||||
|
||||
/**
|
||||
* Add some size to this one.
|
||||
*
|
||||
* @param that The number to add.
|
||||
* @return The sum of the numbers.
|
||||
*/
|
||||
/** Add some size to this one.
|
||||
*
|
||||
* @param that
|
||||
* The number to add.
|
||||
* @return
|
||||
* The sum of the numbers.
|
||||
*/
|
||||
def +(that: Size): Size = size + that
|
||||
|
||||
/**
|
||||
* Multiply this size by some other size.
|
||||
*
|
||||
* @param that The number to multiply by.
|
||||
* @return The product of the numbers.
|
||||
*/
|
||||
/** Multiply this size by some other size.
|
||||
*
|
||||
* @param that
|
||||
* The number to multiply by.
|
||||
* @return
|
||||
* The product of the numbers.
|
||||
*/
|
||||
def *(that: Size): Size = size * that
|
||||
|
||||
end Size
|
||||
|
|
|
|||
|
|
@ -2,22 +2,21 @@ package gs.std.v0
|
|||
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Opaque type that represents the instant something was updated.
|
||||
/** Opaque type that represents the instant something was updated.
|
||||
*/
|
||||
opaque type UpdatedAt = Instant
|
||||
|
||||
/**
|
||||
* Opaque type that represents the instant something was updated.
|
||||
/** Opaque type that represents the instant something was updated.
|
||||
*/
|
||||
object UpdatedAt:
|
||||
|
||||
/**
|
||||
* Instantiate a new [[UpdatedAt]] from the given `java.time.Instant`.
|
||||
*
|
||||
* @param value The value to semantically represent.
|
||||
* @return The new [[UpdatedAt]].
|
||||
*/
|
||||
/** Instantiate a new [[UpdatedAt]] from the given `java.time.Instant`.
|
||||
*
|
||||
* @param value
|
||||
* The value to semantically represent.
|
||||
* @return
|
||||
* The new [[UpdatedAt]].
|
||||
*/
|
||||
def apply(value: Instant): UpdatedAt = value
|
||||
|
||||
given CanEqual[UpdatedAt, UpdatedAt] = CanEqual.derived
|
||||
|
|
@ -25,15 +24,14 @@ object UpdatedAt:
|
|||
given Ordering[UpdatedAt] = Ordering[Instant]
|
||||
|
||||
extension (updatedAt: Instant)
|
||||
/**
|
||||
* @return The underlying `java.time.Instant`.
|
||||
*/
|
||||
/** @return
|
||||
* The underlying `java.time.Instant`.
|
||||
*/
|
||||
def unwrap(): Instant = updatedAt
|
||||
|
||||
/**
|
||||
* @return The underlying `java.time.Instant`.
|
||||
*/
|
||||
/** @return
|
||||
* The underlying `java.time.Instant`.
|
||||
*/
|
||||
def toInstant(): Instant = updatedAt
|
||||
|
||||
end UpdatedAt
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue