3
votes

In this question, I noticed the need to separate logic into two steps with two class constructors and yet to fully understand the rationale behind it. But here I am, having a more complex problem.

In this case I need to achieve the following

    case class RawReq(ip: String, header: Map[String, String] = Map())
    case class Result(status: String, body: Option[String] = None)

    type Directive[T] = T ⇒ Result

    class ActionConstructor[T] {
       def apply[ExtractedRepr <: HList, ExtraInputRepr <: HList]
      (extractor: RawReq ⇒ ExtractedRepr, dir: Directive[T])
      (implicit supplement: Supplement.Aux[T, ExtractedRepr, ExtraInputRepr])
     : ExtraInputRepr => RawReq => Result 
      = ???
    }
    // The Supplement is something to be implemented or replaced ( it should
    // generate a ExtraInputRepr type that basically provide the missing 
    // fields that ExtractedRepr doesn't provide for creating a T,  the
    // example below explains it better.

   //  Example usage below
    case class RequestMessage(name: String, ipAllowed: Boolean, userId: String)

    val dir : Directive[RequestMessage] = (rm: RequestMessage) ⇒ 
       if(rm.ipAllowed) 
         Result("200", Some("hello! " + rm.name )) 
       else Result("401")

    // the extractor can get one piece of information from the RawReq   
    val extractor = (r: RawReq) => ('ipAllowed ->> (r.ip.startWith("10.4")> 3)) :: HNil

    val ac = new ActionConstructor[RequestMessage]

    val action = ac(extractor, dir)

    // The other two fields of RequestMessage are provided throw method call 
    val result = action(('name ->> "Mike") :: ('userId ->> "aId") :: HNil)(RawReq(ip = "10.4.2.5"))  

    result == Result("200", Some("hello! Mike"))

My several attempts to implement this all failed. Here is the last one

  class PartialHandlerConstructor[T, Repr <: HList, ExtractedRepr <: HList]
    (extractor: RawReq ⇒ ExtractedRepr)
    (implicit lgen: LabelledGeneric.Aux[T, Repr]) {
    def apply[TempFull <: HList, InputRepr <: HList]
      (dir: Directive[T])
      (implicit removeAll: RemoveAll.Aux[Repr, ExtractedRepr, InputRepr],
      prepend: Prepend.Aux[InputRepr, ExtractedRepr, TempFull],
      align: Align[TempFull, Repr]): InputRepr ⇒ RawReq ⇒ Result =
        (inputRepr: InputRepr) ⇒ (raw: RawReq) ⇒ {
          dir(lgen.from(align(inputRepr ++ extractor(raw))))
        }
  }

  class HandlerConstructor[T]() {
    def apply[ExtractedRepr <: HList, Repr <: HList]
      (extractor: RawReq ⇒ ExtractedRepr)
      (implicit lgen: LabelledGeneric.Aux[T, Repr]) = {
        new PartialHandlerConstructor[T, Repr, ExtractedRepr](extractor)
    }
  }


  val hc = new HandlerConstructor[RequestMessage]
  val handler1 = hc((r: RawReq) ⇒ ('ipAllowed ->> (r.ip.length > 3)) :: HNil)

  val dir: Directive[RequestMessage] = (rm: RequestMessage) ⇒ Result(rm.ipAllowed.toString, rm.name)

  val action = handler1(dir) // <=== compiler fail

  val result = action(('name ->> "big") :: ('userId ->> "aId") :: HNil)(RawReq("anewiP"))

The compiler error message I am getting is at the line p(dir)

Error:(85, 26) could not find implicit value for parameter removeAll: shapeless.ops.hlist.RemoveAll.Aux[this.Out,shapeless.::[Boolean with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("ipAllowed")],Boolean],shapeless.HNil],InputRepr] val action = handler1(dir) ^

I think the fundamental reason that I failed is that I don't understand why and how to organize this type construction logic into different classes. And I don't quite understand why the compiler can find the implicit in some cases but not in others.

1

1 Answers

2
votes

Figured out a solution. I have to create my own TypeClass RestOf

package com.iheart.playSwagger

import org.specs2.mutable.Specification
import shapeless._
import ops.hlist._
import syntax.singleton._

object Test {
  case class RawReq(ip: String, header: Map[String, String] = Map())

  case class Result(status: String, body: String)

  type Directive[T] = T ⇒ Result

  case class RequestMessage(name: String, ipAllowed: Boolean, userId: String)

  trait RestOf[L <: HList, SL <: HList] {
    type Out <: HList
  }

  object RestOf {

    type Aux[L <: HList, SL <: HList, Out0 <: HList] = RestOf[L, SL] { 
      type Out = Out0 
    }

    implicit def hlistRestOfNil[L <: HList]: Aux[L, HNil, L] = new RestOf[L, HNil] { type Out = L }

    implicit def hlistRestOf[L <: HList, E, RemE <: HList, Rem <: HList, SLT <: HList]
      (implicit rt: Remove.Aux[L, E, (E, RemE)], st: Aux[RemE, SLT, Rem]): Aux[L, E :: SLT, Rem] =
      new RestOf[L, E :: SLT] { type Out = Rem }
  }

  class PartialHandlerConstructor[T, Repr <: HList, ExtractedRepr <: HList, InputRepr <: HList]
    (extractor: RawReq ⇒ ExtractedRepr)
    (implicit lgen: LabelledGeneric.Aux[T, Repr]) {
    def apply[TempFull <: HList](dir: Directive[T])
      (implicit prepend: Prepend.Aux[InputRepr, ExtractedRepr, TempFull],
      align: Align[TempFull, Repr]): InputRepr ⇒ RawReq ⇒ Result =
        (inputRepr: InputRepr) ⇒ (raw: RawReq) ⇒ {
          dir(lgen.from(align(inputRepr ++ extractor(raw))))
        }
  }

  class HandlerConstructor[T]() {
    def apply[Repr <: HList,ExtractedRepr <: HList, InputRepr <: HList]
    (extractor: RawReq ⇒ ExtractedRepr)
    (implicit lgen: LabelledGeneric.Aux[T, Repr],
     restOf: RestOf.Aux[Repr, ExtractedRepr, InputRepr]) = {
      new PartialHandlerConstructor[T, Repr, ExtractedRepr, InputRepr](extractor)
    }
  }

}

class HandlerSpec extends Specification {
  import Test._


  "handler" should {
    "generate action functions" in {
      val hc = new HandlerConstructor[RequestMessage]().apply
      val handler1 = hc((r: RawReq) ⇒ ('ipAllowed ->> (r.ip.length > 3)) :: HNil)

      val dir: Directive[RequestMessage] = 
        (rm: RequestMessage) ⇒ Result(rm.ipAllowed.toString, rm.name)

      val action = handler1(dir)

      val result = action(('name ->> "big") :: ('userId ->> "aId") :: HNil)(RawReq("anewiP"))

      result === Result("true", "big")
    }
  }
}