Last active
October 7, 2017 14:13
-
-
Save afiore/937da9c6ef0cf9da8bfdcd5948e9ada5 to your computer and use it in GitHub Desktop.
AWS IAM Conditions encoded using phantom types and type members
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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