First run of pre-commit.
All checks were successful
/ Build and Release Library (push) Successful in 1m38s

This commit is contained in:
Pat Garrity 2026-04-25 22:46:40 -05:00
parent ea82a5ff44
commit df41b9bf01
Signed by: pfm
GPG key ID: 5CA5D21BAB7F3A76
10 changed files with 414 additions and 388 deletions

View file

@ -2,43 +2,48 @@ package gs.std.v0
import java.util.Base64 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]: final class Blob(private val data: Array[Byte]) extends IndexedSeq[Byte]:
/** @inheritDocs */ /** @inheritDocs
*/
override def apply(i: Int): Byte = byteAt(i) override def apply(i: Int): Byte = byteAt(i)
/** @inheritDocs */ /** @inheritDocs
*/
override def length: Int = data.length override def length: Int = data.length
/** /** @return
* @return The number of bytes in this blob, expressed as a count. * The number of bytes in this blob, expressed as a count.
*/ */
def numberOfBytes: ByteCount = ByteCount(data.length) def numberOfBytes: ByteCount = ByteCount(data.length)
/** @inheritDocs */ /** @inheritDocs
override def equals(obj: Any): Boolean = */
override def equals(obj: Any): Boolean =
obj match obj match
case other: Blob => data.sameElements(other.data) case other: Blob => data.sameElements(other.data)
case other: Array[Byte] => data.sameElements(other) case other: Array[Byte] => data.sameElements(other)
case _ => false case _ => false
/** @inheritDocs */ /** @inheritDocs
*/
override def hashCode(): Int = data.hashCode() override def hashCode(): Int = data.hashCode()
/** /** Retrieve the byte at the given index.
* Retrieve the byte at the given index. *
* * @param index
* @param index The index. * The index.
* @return The byte stored at the given index. * @return
*/ * The byte stored at the given index.
*/
def byteAt(index: Int): Byte = data.apply(index) def byteAt(index: Int): Byte = data.apply(index)
/** /** @return
* @return This byte array, encoded as a base64 string. * This byte array, encoded as a base64 string.
*/ */
def base64(): String = Base64.getEncoder().encodeToString(data) def base64(): String = Base64.getEncoder().encodeToString(data)

View file

@ -1,132 +1,138 @@
package gs.std.v0 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 ByteCount = Long
/** /** Opaque type for some number of bytes (>= 0).
* Opaque type for some number of bytes (>= 0).
*/ */
object ByteCount: object ByteCount:
/** /** 0 bytes.
* 0 bytes. */
*/
final val Zero: ByteCount = 0 final val Zero: ByteCount = 0
/** /** 1,000 bytes.
* 1,000 bytes. */
*/
final val OneKilobyte: ByteCount = 1000 final val OneKilobyte: ByteCount = 1000
/** /** 1,000,000 bytes.
* 1,000,000 bytes. */
*/
final val OneMegabyte: ByteCount = 1000000 final val OneMegabyte: ByteCount = 1000000
/** /** 1,000,000,000 bytes.
* 1,000,000,000 bytes. */
*/
final val OneGigabyte: ByteCount = 1000000000 final val OneGigabyte: ByteCount = 1000000000
/** /** Express the given number as a byte count. All values are normalized to the
* Express the given number as a byte count. All values are normalized to the * absolute value -- negative values are coerced to positive.
* absolute value -- negative values are coerced to positive. *
* * @param value
* @param value The input integer. * The input integer.
* @return The [[ByteCount]] instance. * @return
*/ * The [[ByteCount]] instance.
*/
def apply(value: Long): ByteCount = Math.abs(value) def apply(value: Long): ByteCount = Math.abs(value)
/** /** 1 kilobyte = 1,000 bytes
* 1 kilobyte = 1,000 bytes *
* * @param kb
* @param kb The number of kilobytes. * The number of kilobytes.
* @return The number of bytes. * @return
*/ * The number of bytes.
*/
def fromKilobytes(kb: Long): ByteCount = def fromKilobytes(kb: Long): ByteCount =
Math.abs(kb) * 1000L Math.abs(kb) * 1000L
/** /** 1 megabyte = 1,000,000 bytes
* 1 megabyte = 1,000,000 bytes *
* * @param mb
* @param mb The number of megabytes. * The number of megabytes.
* @return The number of bytes. * @return
*/ * The number of bytes.
*/
def fromMegabytes(mb: Long): ByteCount = def fromMegabytes(mb: Long): ByteCount =
Math.abs(mb) * 1000000L Math.abs(mb) * 1000000L
/** /** 1 gigabyte = 1,000,000,000 bytes
* 1 gigabyte = 1,000,000,000 bytes *
* * @param mb
* @param mb The number of gigabytes. * The number of gigabytes.
* @return The number of bytes. * @return
*/ * The number of bytes.
*/
def fromGigabytes(gb: Long): ByteCount = def fromGigabytes(gb: Long): ByteCount =
Math.abs(gb) * 1000000000 Math.abs(gb) * 1000000000
given CanEqual[ByteCount, ByteCount] = CanEqual.derived given CanEqual[ByteCount, ByteCount] = CanEqual.derived
given Ordering[ByteCount] with 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 if x > y then 1 else if x == y then 0 else -1
extension (byteCount: ByteCount) extension (byteCount: ByteCount)
/** /** @return
* @return The underlying `Long`. * The underlying `Long`.
*/ */
def unwrap(): Long = byteCount def unwrap(): Long = byteCount
/** /** @return
* @return The underlying `Long`. * The underlying `Long`.
*/ */
def toLong(): Long = byteCount def toLong(): Long = byteCount
/** /** 1 kilobyte = 1,000 bytes
* 1 kilobyte = 1,000 bytes *
* * @return
* @return The number of kilobytes represented by this count. * The number of kilobytes represented by this count.
*/ */
def toKilobytes(): Double = byteCount / 1000.0 def toKilobytes(): Double = byteCount / 1000.0
/** /** 1 megabyte = 1,000,000 bytes.
* 1 megabyte = 1,000,000 bytes. *
* * @return
* @return The number of megabytes represented by this count. * The number of megabytes represented by this count.
*/ */
def toMegabytes(): Double = byteCount / 1000000.0 def toMegabytes(): Double = byteCount / 1000000.0
/** /** 1 gigabyte = 1,000,000,000 bytes.
* 1 gigabyte = 1,000,000,000 bytes. *
* * @return
* @return The number of gigabytes represented by this count. * The number of gigabytes represented by this count.
*/ */
def toGigabytes(): Double = byteCount / 1000000000.0 def toGigabytes(): Double = byteCount / 1000000000.0
/** /** Add some count to this one.
* Add some count to this one. *
* * @param that
* @param that The number to add. * The number to add.
* @return The sum of the numbers. * @return
*/ * The sum of the numbers.
*/
def +(that: ByteCount): ByteCount = byteCount + that def +(that: ByteCount): ByteCount = byteCount + that
/** /** Multiply this count by some other count.
* Multiply this count by some other count. *
* * @param that
* @param that The number to multiply by. * The number to multiply by.
* @return The product of the numbers. * @return
*/ * The product of the numbers.
*/
def *(that: ByteCount): ByteCount = byteCount * that def *(that: ByteCount): ByteCount = byteCount * that
/** /** Check if this value is the same as some number.
* Check if this value is the same as some number. *
* * @param value
* @param value The number to compare against. * The number to compare against.
* @return True if the values are equal, false otherwise. * @return
*/ * True if the values are equal, false otherwise.
*/
def equal(value: Int): Boolean = byteCount == value.toLong def equal(value: Int): Boolean = byteCount == value.toLong
end ByteCount end ByteCount

View file

@ -2,22 +2,21 @@ package gs.std.v0
import java.time.Instant 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 CreatedAt = Instant
/** /** Opaque type that represents the instant something was created.
* Opaque type that represents the instant something was created.
*/ */
object CreatedAt: object CreatedAt:
/** /** Instantiate a new [[CreatedAt]] from the given `java.time.Instant`.
* Instantiate a new [[CreatedAt]] from the given `java.time.Instant`. *
* * @param value
* @param value The value to semantically represent. * The value to semantically represent.
* @return The new [[CreatedAt]]. * @return
*/ * The new [[CreatedAt]].
*/
def apply(value: Instant): CreatedAt = value def apply(value: Instant): CreatedAt = value
given CanEqual[CreatedAt, CreatedAt] = CanEqual.derived given CanEqual[CreatedAt, CreatedAt] = CanEqual.derived
@ -25,14 +24,14 @@ object CreatedAt:
given Ordering[CreatedAt] = Ordering[Instant] given Ordering[CreatedAt] = Ordering[Instant]
extension (createdAt: Instant) extension (createdAt: Instant)
/** /** @return
* @return The underlying `java.time.Instant`. * The underlying `java.time.Instant`.
*/ */
def unwrap(): Instant = createdAt def unwrap(): Instant = createdAt
/** /** @return
* @return The underlying `java.time.Instant`. * The underlying `java.time.Instant`.
*/ */
def toInstant(): Instant = createdAt def toInstant(): Instant = createdAt
end CreatedAt end CreatedAt

View file

@ -1,13 +1,13 @@
package gs.std.v0 package gs.std.v0
/** /** Interface for byte decoding from encoded String formats.
* Interface for byte decoding from encoded String formats.
*/ */
trait Decoder[-A <: EncodedString]: trait Decoder[-A <: EncodedString]:
/** /** Decode an input string to an array of bytes.
* Decode an input string to an array of bytes. *
* * @param input
* @param input The input to decode. * The input to decode.
* @return The decoded byte array. * @return
*/ * The decoded byte array.
*/
def decode(input: A): Array[Byte] def decode(input: A): Array[Byte]

View file

@ -51,8 +51,8 @@ final class B64(
object B64: object B64:
/** Instantiate [[B64]] from the given string. Assumes that the input is /** Instantiate [[B64]] from the given string. Assumes that the input is
* base64-encoded. * base64-encoded.
* *
* This function does NOT validate the input. * This function does NOT validate the input.
* *
@ -97,8 +97,8 @@ final class B64Url(
object B64Url: object B64Url:
/** Instantiate [[B64Url]] from the given string. Assumes that the input is /** Instantiate [[B64Url]] from the given string. Assumes that the input is
* base64-encoded. * base64-encoded.
* *
* This function does NOT validate the input. * This function does NOT validate the input.
* *
@ -143,8 +143,8 @@ final class Hex(
object Hex: object Hex:
/** Instantiate [[Hex]] from the given string. Assumes that the input is /** Instantiate [[Hex]] from the given string. Assumes that the input is
* hex-encoded. * hex-encoded.
* *
* This function does NOT validate the input. * This function does NOT validate the input.
* *

View file

@ -1,105 +1,108 @@
package gs.std.v0 package gs.std.v0
import java.security.MessageDigest
import java.nio.charset.Charset import java.nio.charset.Charset
import java.nio.charset.StandardCharsets 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 MD5 = Array[Byte]
/** /** Opaque type representing a MD5 hash.
* Opaque type representing a MD5 hash.
*/ */
object MD5: object MD5:
/** /** MD5 hashes are exactly 16 bytes.
* MD5 hashes are exactly 16 bytes. */
*/
final val NumberOfBytes: ByteCount = ByteCount(16) final val NumberOfBytes: ByteCount = ByteCount(16)
/** /** The algorithm name is "MD5".
* The algorithm name is "MD5". */
*/
final val Algorithm: String = "MD5" final val Algorithm: String = "MD5"
/** /** Instantiate a [[MD5]] from the given byte array. This function does not
* Instantiate a [[MD5]] from the given byte array. This function does not * know whether the array is actually a calculated hash.
* know whether the array is actually a calculated hash. *
* * Typically used for loading pre-validated hashes (e.g. from a database).
* Typically used for loading pre-validated hashes (e.g. from a database). *
* * @param bytes
* @param bytes The bytes - must contain exactly 16 bytes. * The bytes - must contain exactly 16 bytes.
* @return The new [[MD5]] instance. * @return
*/ * The new [[MD5]] instance.
def fromBytes(bytes: Array[Byte]): MD5 = */
if NumberOfBytes.equal(bytes.length) then def fromBytes(bytes: Array[Byte]): MD5 =
bytes if NumberOfBytes.equal(bytes.length) then bytes
else 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.
* Calculate the MD5 hash for the given byte array. *
* * @param data
* @param data The byte array. * The byte array.
* @return The calculated [[MD5]]. * @return
*/ * The calculated [[MD5]].
def calculate(data: Array[Byte]): MD5 = */
def calculate(data: Array[Byte]): MD5 =
MessageDigest.getInstance(Algorithm).digest(data) 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 data The string data.
* @param charset The character set of the string. Defaults to UTF-8. * @param charset The character set of the string. Defaults to UTF-8.
* @return The calculated [[MD5]]. * @return The calculated [[MD5]]. */
*/ def calculate(
def calculate(data: String, charset: Charset = StandardCharsets.UTF_8): MD5 = data: String,
charset: Charset = StandardCharsets.UTF_8
): MD5 =
calculate(data.getBytes(charset)) calculate(data.getBytes(charset))
extension (md5: MD5) extension (md5: MD5)
/** /** @return
* @return The underlying byte array. * The underlying byte array.
*/ */
def unwrap(): Array[Byte] = md5 def unwrap(): Array[Byte] = md5
/** /** @return
* @return The underlying byte array. * The underlying byte array.
*/ */
def toBytes(): Array[Byte] = md5 def toBytes(): Array[Byte] = md5
/** /** Get the byte at the given index (0 to 15).
* Get the byte at the given index (0 to 15). *
* * Throws an exception if an out-of-bound index is given.
* Throws an exception if an out-of-bound index is given. *
* * @param index
* @param index The byte index (0 to 15). * The byte index (0 to 15).
* @return The byte at the specified index. * @return
*/ * The byte at the specified index.
*/
def getByte(index: Int): Byte = def getByte(index: Int): Byte =
if index < 0 || index >= NumberOfBytes.unwrap() then if index < 0 || index >= NumberOfBytes.unwrap() then
throw IndexOutOfBoundsException(s"Index $index out of MD5 bound of $NumberOfBytes bytes.") throw IndexOutOfBoundsException(
else s"Index $index out of MD5 bound of $NumberOfBytes bytes."
md5.apply(index) )
else md5.apply(index)
/** /** Determine if this hash is the same as some other hash. Compares each
* Determine if this hash is the same as some other hash. Compares each byte * byte in order.
* in order. *
* * @param other
* @param other The [[MD5]] to compare against. * The [[MD5]] to compare against.
* @return True if the hashes are identical, false otherwise. * @return
*/ * True if the hashes are identical, false otherwise.
*/
def isSame(other: MD5): Boolean = md5.sameElements(other) def isSame(other: MD5): Boolean = md5.sameElements(other)
/** /** @return
* @return This hash encoded to a Base64 string. * This hash encoded to a Base64 string.
*/ */
def base64(): EncodedString = Base64Encoder.encode(md5) def base64(): EncodedString = Base64Encoder.encode(md5)
/** /** @return
* @return This hash encoded to a Hexadecimal string. * This hash encoded to a Hexadecimal string.
*/ */
def hex(): EncodedString = HexEncoder.encode(md5) def hex(): EncodedString = HexEncoder.encode(md5)
end MD5 end MD5

View file

@ -1,41 +1,37 @@
package gs.std.v0 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 Nat = Int
/** /** Opaque type for the natural numbers (including 0).
* Opaque type for the natural numbers (including 0).
*/ */
object Nat: object Nat:
sealed trait Invalid sealed trait Invalid
object Invalid extends Invalid object Invalid extends Invalid
/** /** The number 0.
* The number 0. */
*/
final val Zero: Nat = 0 final val Zero: Nat = 0
/** /** The number 1.
* The number 1. */
*/
final val One: Nat = 1 final val One: Nat = 1
/** /** Express the given integer as a natural number.
* Express the given integer as a natural number. *
* * Throws an `IllegalArgumentException` if a negative value is given as
* Throws an `IllegalArgumentException` if a negative value is given as input. * input.
* *
* @param value The input integer. * @param value
* @return The [[Nat]] instance. * The input integer.
*/ * @return
* The [[Nat]] instance.
*/
def apply(value: Int): Nat = def apply(value: Int): Nat =
if value >= 0 then if value >= 0 then value
value else throw new IllegalArgumentException("Nat values must be 0 or greater.")
else
throw new IllegalArgumentException("Nat values must be 0 or greater.")
def validate(value: Int): Either[Invalid, Nat] = def validate(value: Int): Either[Invalid, Nat] =
if value >= 0 then Right(value) else Left(Invalid) if value >= 0 then Right(value) else Left(Invalid)
@ -43,39 +39,46 @@ object Nat:
given CanEqual[Nat, Nat] = CanEqual.derived given CanEqual[Nat, Nat] = CanEqual.derived
given Ordering[Nat] with 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) extension (nat: Nat)
/** /** @return
* @return The underlying integer. * The underlying integer.
*/ */
def unwrap(): Int = nat def unwrap(): Int = nat
/** /** @return
* @return The next integer. * The next integer.
*/ */
def next(): Nat = nat + 1 def next(): Nat = nat + 1
/** /** @return
* @return The next integer. * The next integer.
*/ */
def increment(): Nat = nat + 1 def increment(): Nat = nat + 1
/** /** Add some natural number to this one.
* Add some natural number to this one. *
* * @param that
* @param that The number to add. * The number to add.
* @return The sum of the numbers. * @return
*/ * The sum of the numbers.
*/
def +(that: Nat): Nat = nat + that def +(that: Nat): Nat = nat + that
/** /** Multiply this natural number by some other natural number.
* Multiply this natural number by some other natural number. *
* * @param that
* @param that The number to multiply by. * The number to multiply by.
* @return The product of the numbers. * @return
*/ * The product of the numbers.
*/
def *(that: Nat): Nat = nat * that def *(that: Nat): Nat = nat * that
end Nat end Nat

View file

@ -1,105 +1,112 @@
package gs.std.v0 package gs.std.v0
import java.security.MessageDigest
import java.nio.charset.Charset import java.nio.charset.Charset
import java.nio.charset.StandardCharsets 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 SHA256 = Array[Byte]
/** /** Opaque type representing a SHA-256 hash.
* Opaque type representing a SHA-256 hash.
*/ */
object SHA256: object SHA256:
/** /** SHA-256 hashes are exactly 32 bytes.
* SHA-256 hashes are exactly 32 bytes. */
*/
final val NumberOfBytes: ByteCount = ByteCount(32) final val NumberOfBytes: ByteCount = ByteCount(32)
/** /** The algorithm name is "SHA-256".
* The algorithm name is "SHA-256". */
*/
final val Algorithm: String = "SHA-256" final val Algorithm: String = "SHA-256"
/** /** Instantiate a [[SHA256]] from the given byte array. This function does not
* Instantiate a [[SHA256]] from the given byte array. This function does not * know whether the array is actually a calculated hash.
* know whether the array is actually a calculated hash. *
* * Typically used for loading pre-validated hashes (e.g. from a database).
* Typically used for loading pre-validated hashes (e.g. from a database). *
* * @param bytes
* @param bytes The bytes - must contain exactly 32 bytes. * The bytes - must contain exactly 32 bytes.
* @return The new [[SHA256]] instance. * @return
*/ * The new [[SHA256]] instance.
def fromBytes(bytes: Array[Byte]): SHA256 = */
if NumberOfBytes.equal(bytes.length) then def fromBytes(bytes: Array[Byte]): SHA256 =
bytes if NumberOfBytes.equal(bytes.length) then bytes
else 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.
* Calculate the SHA-256 hash for the given byte array. *
* * @param data
* @param data The byte array. * The byte array.
* @return The calculated [[SHA256]]. * @return
*/ * The calculated [[SHA256]].
def calculate(data: Array[Byte]): SHA256 = */
def calculate(data: Array[Byte]): SHA256 =
MessageDigest.getInstance(Algorithm).digest(data) MessageDigest.getInstance(Algorithm).digest(data)
/** /** Calculate the SHA-256 hash for the given string.
* Calculate the SHA-256 hash for the given string. *
* * @param data
* @param data The string data. * The string data.
* @param charset The character set of the string. Defaults to UTF-8. * @param charset
* @return The calculated [[SHA256]]. * The character set of the string. Defaults to UTF-8.
*/ * @return
def calculate(data: String, charset: Charset = StandardCharsets.UTF_8): SHA256 = * The calculated [[SHA256]].
*/
def calculate(
data: String,
charset: Charset = StandardCharsets.UTF_8
): SHA256 =
calculate(data.getBytes(charset)) calculate(data.getBytes(charset))
extension (sha: SHA256) extension (sha: SHA256)
/** /** @return
* @return The underlying byte array. * The underlying byte array.
*/ */
def unwrap(): Array[Byte] = sha def unwrap(): Array[Byte] = sha
/** /** @return
* @return The underlying byte array. * The underlying byte array.
*/ */
def toBytes(): Array[Byte] = sha def toBytes(): Array[Byte] = sha
/** /** Get the byte at the given index (0 to 31).
* Get the byte at the given index (0 to 31). *
* * Throws an exception if an out-of-bound index is given.
* Throws an exception if an out-of-bound index is given. *
* * @param index
* @param index The byte index (0 to 31). * The byte index (0 to 31).
* @return The byte at the specified index. * @return
*/ * The byte at the specified index.
*/
def getByte(index: Int): Byte = def getByte(index: Int): Byte =
if index < 0 || index >= NumberOfBytes.unwrap() then if index < 0 || index >= NumberOfBytes.unwrap() then
throw IndexOutOfBoundsException(s"Index $index out of SHA-256 bound of $NumberOfBytes bytes.") throw IndexOutOfBoundsException(
else s"Index $index out of SHA-256 bound of $NumberOfBytes bytes."
sha.apply(index) )
else sha.apply(index)
/** /** Determine if this hash is the same as some other hash. Compares each
* Determine if this hash is the same as some other hash. Compares each byte * byte in order.
* in order. *
* * @param other
* @param other The [[SHA256]] to compare against. * The [[SHA256]] to compare against.
* @return True if the hashes are identical, false otherwise. * @return
*/ * True if the hashes are identical, false otherwise.
*/
def isSame(other: SHA256): Boolean = sha.sameElements(other) def isSame(other: SHA256): Boolean = sha.sameElements(other)
/** /** @return
* @return This hash encoded to a Base64 string. * This hash encoded to a Base64 string.
*/ */
def base64(): EncodedString = Base64Encoder.encode(sha) def base64(): EncodedString = Base64Encoder.encode(sha)
/** /** @return
* @return This hash encoded to a Hexadecimal string. * This hash encoded to a Hexadecimal string.
*/ */
def hex(): EncodedString = HexEncoder.encode(sha) def hex(): EncodedString = HexEncoder.encode(sha)
end SHA256 end SHA256

View file

@ -1,97 +1,102 @@
package gs.std.v0 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 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: object Size:
sealed trait Invalid sealed trait Invalid
object Invalid extends Invalid object Invalid extends Invalid
/** /** The size 0.
* The size 0. */
*/
final val Zero: Size = 0 final val Zero: Size = 0
/** /** The size 1.
* The size 1. */
*/
final val One: Size = 1 final val One: Size = 1
/** /** Express the given integer as a size.
* Express the given integer as a size. *
* * Throws an `IllegalArgumentException` if a negative value is given as
* Throws an `IllegalArgumentException` if a negative value is given as input. * input.
* *
* @param value The input integer. * @param value
* @return The [[Size]] instance. * The input integer.
*/ * @return
* The [[Size]] instance.
*/
def apply(value: Int): Size = def apply(value: Int): Size =
if value >= 0 then if value >= 0 then value
value else throw new IllegalArgumentException("Size values must be 0 or greater.")
else
throw new IllegalArgumentException("Size values must be 0 or greater.")
def validate(value: Int): Either[Invalid, Size] = def validate(value: Int): Either[Invalid, Size] =
if value >= 0 then Right(value) else Left(Invalid) if value >= 0 then Right(value) else Left(Invalid)
/** /** Express the size of any collection.
* Express the size of any collection. *
* * @param iter
* @param iter The collection. * The collection.
* @return The size of the collection. * @return
*/ * The size of the collection.
*/
def of(iter: Iterable[?]): Size = iter.size def of(iter: Iterable[?]): Size = iter.size
/** /** Express the size of any array.
* Express the size of any array. *
* * @param arr
* @param arr The array. * The array.
* @return The size of the array. * @return
*/ * The size of the array.
*/
def of(arr: Array[?]): Size = arr.length def of(arr: Array[?]): Size = arr.length
given CanEqual[Size, Size] = CanEqual.derived given CanEqual[Size, Size] = CanEqual.derived
given Ordering[Size] with 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) extension (size: Size)
/** /** @return
* @return The underlying integer. * The underlying integer.
*/ */
def unwrap(): Int = size def unwrap(): Int = size
/** /** @return
* @return The next value. * The next value.
*/ */
def next(): Size = size + 1 def next(): Size = size + 1
/** /** @return
* @return The next value. * The next value.
*/ */
def increment(): Size = size + 1 def increment(): Size = size + 1
/** /** Add some size to this one.
* Add some size to this one. *
* * @param that
* @param that The number to add. * The number to add.
* @return The sum of the numbers. * @return
*/ * The sum of the numbers.
*/
def +(that: Size): Size = size + that def +(that: Size): Size = size + that
/** /** Multiply this size by some other size.
* Multiply this size by some other size. *
* * @param that
* @param that The number to multiply by. * The number to multiply by.
* @return The product of the numbers. * @return
*/ * The product of the numbers.
*/
def *(that: Size): Size = size * that def *(that: Size): Size = size * that
end Size end Size

View file

@ -2,22 +2,21 @@ package gs.std.v0
import java.time.Instant 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 UpdatedAt = Instant
/** /** Opaque type that represents the instant something was updated.
* Opaque type that represents the instant something was updated.
*/ */
object UpdatedAt: object UpdatedAt:
/** /** Instantiate a new [[UpdatedAt]] from the given `java.time.Instant`.
* Instantiate a new [[UpdatedAt]] from the given `java.time.Instant`. *
* * @param value
* @param value The value to semantically represent. * The value to semantically represent.
* @return The new [[UpdatedAt]]. * @return
*/ * The new [[UpdatedAt]].
*/
def apply(value: Instant): UpdatedAt = value def apply(value: Instant): UpdatedAt = value
given CanEqual[UpdatedAt, UpdatedAt] = CanEqual.derived given CanEqual[UpdatedAt, UpdatedAt] = CanEqual.derived
@ -25,15 +24,14 @@ object UpdatedAt:
given Ordering[UpdatedAt] = Ordering[Instant] given Ordering[UpdatedAt] = Ordering[Instant]
extension (updatedAt: Instant) extension (updatedAt: Instant)
/** /** @return
* @return The underlying `java.time.Instant`. * The underlying `java.time.Instant`.
*/ */
def unwrap(): Instant = updatedAt def unwrap(): Instant = updatedAt
/** /** @return
* @return The underlying `java.time.Instant`. * The underlying `java.time.Instant`.
*/ */
def toInstant(): Instant = updatedAt def toInstant(): Instant = updatedAt
end UpdatedAt end UpdatedAt