Skip to content

Instantly share code, notes, and snippets.

@TimWSpence
Created March 26, 2019 11:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save TimWSpence/57f02cb06aa6a193e9485794aa06ee92 to your computer and use it in GitHub Desktop.
Save TimWSpence/57f02cb06aa6a193e9485794aa06ee92 to your computer and use it in GitHub Desktop.
import java.util.UUID
import io.estatico.newtype.Coercible
import sangria.marshalling.FromInput.{CoercedScalaResult, InputObjectResult}
import sangria.schema._
import sangria.util.tag.@@
import sangria.validation.ValueCoercionViolation
import shapeless.{LabelledGeneric, Lazy}
import shapeless._
import shapeless.labelled.FieldType
import scala.reflect.ClassTag
object derivation {
def deriveInputType[A](implicit toInput: ToInputType[A]): InputType[toInput.Repr] = toInput.to
}
case object IDViolation extends ValueCoercionViolation("Invalid ID")
sealed trait ToInputType[A] {
type Repr
def to: InputType[Repr]
}
object ToInputType extends LowPriorityInstances with LowestPriorityInstances {
type Aux[A, R] = ToInputType[A] {type Repr = R}
implicit val stringToInputType: ToInputType.Aux[String, String @@ CoercedScalaResult] = new ToInputType[String] {
override type Repr = String @@ CoercedScalaResult
override def to: InputType[Repr] = StringType
}
implicit val longToInputType: ToInputType.Aux[Long, Long @@ CoercedScalaResult] = new ToInputType[Long] {
override type Repr = Long @@ CoercedScalaResult
override def to: InputType[Repr] = LongType
}
implicit val intToInputType: ToInputType.Aux[Int, Int @@ CoercedScalaResult] = new ToInputType[Int] {
override type Repr = Int @@ CoercedScalaResult
override def to: InputType[Repr] = IntType
}
implicit val booleanToInputType: ToInputType.Aux[Boolean, Boolean @@ CoercedScalaResult] = new ToInputType[Boolean] {
override type Repr = Boolean @@ CoercedScalaResult
override def to: InputType[Repr] = BooleanType
}
implicit val doubleToInputType: ToInputType.Aux[Double, Double @@ CoercedScalaResult] = new ToInputType[Double] {
override type Repr = Double @@ CoercedScalaResult
//The Sangria FloatType is actually `InputType[Double]` ¯\_(ツ)_/¯
override def to: InputType[Repr] = FloatType
}
implicit val uuidToInputType: ToInputType.Aux[UUID, UUID @@ CoercedScalaResult] = new ToInputType[UUID] {
override type Repr = UUID @@ CoercedScalaResult
override def to: InputType[Repr] = ScalarAlias[UUID, String](StringType,
toScalar = _.toString,
fromScalar = idString ⇒ try Right(UUID.fromString(idString)) catch {
case _: IllegalArgumentException ⇒ Left(IDViolation)
})
}
implicit def optionToInputType[A, R](implicit toInput: ToInputType.Aux[A, R]): ToInputType.Aux[Option[A], Option[R]] = new ToInputType[Option[A]] {
override type Repr = Option[R]
override def to: InputType[Repr] = OptionInputType(toInput.to)
}
implicit def listToInputType[A, R](implicit toInput: ToInputType.Aux[A, R]): ToInputType.Aux[List[A], Seq[R]] = new ToInputType[List[A]] {
override type Repr = Seq[R]
override def to: InputType[Repr] = ListInputType(toInput.to)
}
}
trait LowPriorityInstances {
implicit def newtypeToInputType[A, R1, R2](implicit coerce: Coercible[A, R1], reprToInput: ToInputType.Aux[R1, R2]): ToInputType.Aux[A, R2] = new ToInputType[A] {
override type Repr = R2
override def to: InputType[Repr] = reprToInput.to
}
implicit def seqToInputType[A, R](implicit toInput: ToInputType.Aux[A, R]): ToInputType.Aux[Seq[A], Seq[R]] = new ToInputType[Seq[A]] {
override type Repr = Seq[toInput.Repr]
override def to: InputType[Repr] = ListInputType(toInput.to)
}
}
trait LowestPriorityInstances {
implicit def toInputTypeViaGeneric[A, Repr](implicit gen: LabelledGeneric.Aux[A, Repr], toFieldList: Lazy[ToFieldList[Repr]], tag: ClassTag[A]): ToInputType.Aux[A, A @@ InputObjectResult] = new ToInputType[A] {
override type Repr = A @@ InputObjectResult
override def to: InputType[Repr] =
InputObjectType[A](
name = tag.toString.split("\\.").last,
fields = toFieldList.value.to
)
}
}
trait ToFieldList[A] {
def to: List[InputField[_]]
}
object ToFieldList {
implicit val hnilToFieldList: ToFieldList[HNil] = new ToFieldList[HNil] {
override def to: List[InputField[_]] = Nil
}
implicit def hlistToFieldList[K <: Symbol, R, H, T <: HList](
implicit witness: Witness.Aux[K],
headToInput: Lazy[ToInputType.Aux[H, R]],
tailToFieldList: ToFieldList[T]
): ToFieldList[FieldType[K, H] :: T] = new ToFieldList[FieldType[K, H] :: T] {
override def to: List[InputField[_]] = InputField(witness.value.name, headToInput.value.to) :: tailToFieldList.to
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment