Skip to content

Instantly share code, notes, and snippets.

@afiore
Last active October 7, 2017 14:13
Show Gist options
  • Save afiore/937da9c6ef0cf9da8bfdcd5948e9ada5 to your computer and use it in GitHub Desktop.
Save afiore/937da9c6ef0cf9da8bfdcd5948e9ada5 to your computer and use it in GitHub Desktop.
AWS IAM Conditions encoded using phantom types and type members
package typeformation.cf.iam
import org.scalactic.TypeCheckedTripleEquals
import org.scalatest.{FreeSpec, Matchers}
/**
* Encoding of AWS IAM conditions using a combination of phantom types and type members
*
* A `Condition` is a type representing a key, and an expected parametric value.
* Conditions might be universally or existentially quantified, and can present an additional
* `IfExists` operator, which prevents the check from failing in case the a condition key
* is not present (see http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition).
*
* This encoding avoids materialising these two pieces of information as runtime values,
* and uses instead phantom types to drive the relevant variations in the serialisation logic.
*
*/
class PhantomMembersTest
extends FreeSpec
with Matchers
with TypeCheckedTripleEquals {
trait Quantifier
object ForAll extends Quantifier
object ForAny extends Quantifier
trait IfExists
object Condition {
type Aux[A, Q0, Ifx0] = Condition {
type Exp = A
type Q = Q0
type Ifx = Ifx0
}
def apply[T](k: String, e: T): Condition.Aux[T, Nothing, Nothing] =
new Condition {
override type Exp = T
override type Q = Nothing
override type Ifx = Nothing
override def key = k
override def exp = e
}
}
trait Condition {
type Exp
type Q
type Ifx
def key: String
def exp: Exp
def forAll: Condition.Aux[Exp, ForAll.type, Ifx] =
this.asInstanceOf[Condition.Aux[Exp, ForAll.type, Ifx]]
def forAny: Condition.Aux[Exp, ForAny.type, Ifx] =
this.asInstanceOf[Condition.Aux[Exp, ForAny.type, Ifx]]
def ifExists: Condition.Aux[Exp, Q, IfExists] =
this.asInstanceOf[Condition.Aux[Exp, Q, IfExists]]
}
trait ShowType[T] {
def show: String
}
object ShowType {
implicit val forAllShow = new ShowType[ForAll.type] {
override def show = "ForAllValue"
}
implicit val forAnyShow = new ShowType[ForAny.type] {
override def show = "ForAnyValue"
}
}
trait Show[A] {
def show(a: A): String
}
object Show {
def instance[A](f: A => String): Show[A] = (a: A) => f(a)
implicit val boolShow = instance[Boolean](_.toString)
implicit def showWithQ[E, Q <: Quantifier](implicit showE: Show[E],
showQ: ShowType[Q]) =
instance[Condition.Aux[E, Q, Nothing]] { c =>
s"${showQ.show}:${c.key}=${c.exp}"
}
implicit def showCondWithQAndIfx[E, Q <: Quantifier](
implicit showE: Show[E],
showQ: ShowType[Q]) =
instance[Condition.Aux[E, Q, IfExists]] { c =>
s"${showQ.show}:${c.key}=${c.exp}(ifExists)"
}
implicit def showCondWithoutQAndIfx[E: Show] =
instance[Condition.Aux[E, Nothing, IfExists]] { c =>
s"${c.key}=${c.exp}(ifExists)"
}
implicit def showCondWithouthQorIfx[E, Nothing] =
instance[Condition.Aux[E, Nothing, Nothing]] { c =>
s"${c.key}=${c.exp}"
}
}
"Cond carries its phantom type members" in {
implicit class ShowSyntax[A: Show](a: A) {
def show = implicitly[Show[A]].show(a)
}
val c = Condition("bar", false)
c.show should ===("bar=false")
c.forAll.show === ("ForAllValues:bar=false")
c.ifExists.show === ("bar=false(IfExists)")
c.forAny.ifExists.show === ("ForAnyValue:bar=false(IfExists)")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment