Gastronomy

About

Gastronomy provides a convenient, typesafe and immutable API for calculating hash digests (currently, MD5, SHA1 and SHA256) for a variety of primitive, collection, product and coproduct types.

Usage

Calculating Digests

Digests may be calculated for values by calling the .digest extension method, and specifying a hash function, for example,

import gastronomy._
val intDigest: Digest = 42.digest[Md5]
val stringDigest: Digest = "Hello world!".digest[Sha1]

The digest method requires two typeclass instances to be in scope: a HashFunction[A] for the hashing algorithm to be used, A; and a Hashable[T] for hashing a value of type T.

The types corresponding to hashing algorithms are, Md5, Sha1 and Sha256.

Product and Coproduct types

Gastronomy constructs Hashable typeclass instances for values of Scala’s standard library collection types, and can automatically derive Hashable[T] typeclass instances for product and coproduct types, using Magnolia. Any algebraic data types whose parameters are all types for which Hashable instances exist can themselves be hashed. For example,

import gastronomy._
case class Person(name: String, age: Int)
case class Group(people: List[Person])

val group = Group(List(Person("Bill", 18), Person("Jane", 22)))
val groupDigest: Digest = group.digest[Sha256]

Encoding digests

Calculated Digest values are represented as raw bytes, and may be presented in a variety of different encodings. The Digest type provides the encoded method for specifying this encoding, for example,

import gastronomy._
42.digest[Md5].encoded[Hex]

will return the string A515855799DDBDA08BC99FC2CE87FA79, whereas,

import gastronomy._
"Hello world!".digest[Sha256].encoded[Base64]

will return the string, wFNeS+K3n/2TKRMFQ2v4iTFOSj+uwF7P/Lt98xrZ5Ro=.

Types corresponding to byte encoders include Base64 and Hex.

Global hash algorithms and byte encodings

If, in some scope, digests are always calculated with the same hashing function, and/or are always encoded with the same byte encoder, these can be set globally by defining an implicit in or importing one into that scope. This makes it unnecessary to explicitly specify the hashing scheme at every call site.

For example,

import gastronomy._, HashFunction.md5
val intDigest: Digest = 42.digest

or

import gastronomy._, ByteEncoder.base64, HashFunction.sha256
val digested: String = "Hello world!".digest.encoded

Extending Gastronomy

It is possible to define instances of HashFunction for using alternative hashing algorithms, and alternative byte encodings with instances of ByteEncoder. In each case, these require both a typeclass instance to be defined, and a corresponding phantom type by which to resolve it.

Bytes

Gastronomy’s API defines a Bytes type, which is a typesafe wrapper around a byte array, and while preventing accidental (or deliberate!) mutation of the byte array, also has methods for combining and comparing Bytes instances.

Availability

Source code for Gastronomy is available on GitHub. There is currently no binary release available.

License

Gastronomy is made available under the Apache 2.0 license.