Skip to content

Commit

Permalink
Endpoint metadata ADT and specs
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeykolbasov committed Dec 10, 2017
1 parent 50dbc23 commit 1c157dc
Show file tree
Hide file tree
Showing 13 changed files with 186 additions and 1 deletion.
23 changes: 23 additions & 0 deletions core/src/main/scala/io/finch/Endpoint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ trait Endpoint[A] { self =>
*/
def apply(input: Input): Endpoint.Result[A]

def meta: Endpoint.Meta

/**
* Maps this endpoint to the given function `A => B`.
*/
Expand All @@ -91,6 +93,7 @@ trait Endpoint[A] { self =>

final override def item = self.item
final override def toString: String = self.toString
final override def meta: Endpoint.Meta = self.meta
}

/**
Expand All @@ -114,6 +117,7 @@ trait Endpoint[A] { self =>

override def item = self.item
final override def toString: String = self.toString
final override def meta: Endpoint.Meta = self.meta
}

/**
Expand Down Expand Up @@ -141,6 +145,7 @@ trait Endpoint[A] { self =>

override def item = self.item
final override def toString: String = self.toString
final override def meta: Endpoint.Meta = self.meta
}

/**
Expand Down Expand Up @@ -187,6 +192,7 @@ trait Endpoint[A] { self =>

override def item = self.item
final override def toString: String = self.toString
final override def meta: Endpoint.Meta = self.meta
}

/**
Expand All @@ -202,6 +208,7 @@ trait Endpoint[A] { self =>

override def item = items.MultipleItems
final override def toString: String = s"${other.toString} :: ${self.toString}"
final override def meta: Endpoint.Meta = EndpointMetadata.Product(other.meta, self.meta)
}

/**
Expand All @@ -220,6 +227,7 @@ trait Endpoint[A] { self =>

override def item = items.MultipleItems
final override def toString: String = s"(${self.toString} :+: ${other.toString})"
final override def meta: Endpoint.Meta = EndpointMetadata.Coproduct(other.meta, self.meta)
}

/**
Expand Down Expand Up @@ -349,6 +357,7 @@ trait Endpoint[A] { self =>

override def item = self.item
override final def toString: String = self.toString
override final def meta: Endpoint.Meta = self.meta
}

/**
Expand All @@ -357,6 +366,7 @@ trait Endpoint[A] { self =>
final def withToString(ts: => String): Endpoint[A] = new Endpoint[A] {
final def apply(input: Input): Endpoint.Result[A] = self(input)
final override def toString: String = ts
final override def meta: Endpoint.Meta = self.meta
}

private[this] def withOutput[B](fn: Output[A] => Output[B]): Endpoint[B] =
Expand All @@ -370,11 +380,14 @@ object Endpoint {

type Result[A] = EndpointResult[A]

type Meta = EndpointMetadata

/**
* Creates an empty [[Endpoint]] (an endpoint that never matches) for a given type.
*/
def empty[A]: Endpoint[A] = new Endpoint[A] {
final def apply(input: Input): Result[A] = EndpointResult.Skipped
final def meta: Endpoint.Meta = EndpointMetadata.Empty
}

/**
Expand All @@ -383,6 +396,8 @@ object Endpoint {
def const[A](a: A): Endpoint[A] = new Endpoint[A] {
final def apply(input: Input): Result[A] =
EndpointResult.Matched(input, Rerunnable.const(Output.payload(a)))

final def meta: Endpoint.Meta = EndpointMetadata.Const
}

/**
Expand All @@ -400,6 +415,8 @@ object Endpoint {
def lift[A](a: => A): Endpoint[A] = new Endpoint[A] {
final def apply(input: Input): Result[A] =
EndpointResult.Matched(input, Rerunnable(Output.payload(a)))

final def meta: Endpoint.Meta = EndpointMetadata.Const
}

/**
Expand All @@ -408,6 +425,8 @@ object Endpoint {
def liftAsync[A](fa: => Future[A]): Endpoint[A] = new Endpoint[A] {
final def apply(input: Input): Result[A] =
EndpointResult.Matched(input, Rerunnable.fromFuture(fa).map(a => Output.payload(a)))

final def meta: Endpoint.Meta = EndpointMetadata.Const
}

/**
Expand All @@ -422,6 +441,8 @@ object Endpoint {
def liftOutput[A](oa: => Output[A]): Endpoint[A] = new Endpoint[A] {
final def apply(input: Input): Result[A] =
EndpointResult.Matched(input, Rerunnable(oa))

final def meta: Endpoint.Meta = EndpointMetadata.Const
}

/**
Expand All @@ -431,6 +452,8 @@ object Endpoint {
def liftOutputAsync[A](foa: => Future[Output[A]]): Endpoint[A] = new Endpoint[A] {
final def apply(input: Input): Result[A] =
EndpointResult.Matched(input, Rerunnable.fromFuture(foa))

final def meta: Endpoint.Meta = EndpointMetadata.Const
}

/**
Expand Down
43 changes: 43 additions & 0 deletions core/src/main/scala/io/finch/EndpointMetadata.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.finch

sealed trait Segment

object Segment {

case class Part(name: String) extends Segment

case object Wildcard extends Segment

case object Empty extends Segment

}

sealed trait EndpointMetadata

object EndpointMetadata {

case class Method(method: com.twitter.finagle.http.Method, a: EndpointMetadata) extends EndpointMetadata

case class Path(segment: io.finch.Segment) extends EndpointMetadata

case class Parameter(name: String) extends EndpointMetadata

case class Parameters(name: String) extends EndpointMetadata

case class Header(name: String) extends EndpointMetadata

case class Cookie(name: String) extends EndpointMetadata

case object Body extends EndpointMetadata

case class Multipart(name: String) extends EndpointMetadata

case class Coproduct(a: EndpointMetadata, b: EndpointMetadata) extends EndpointMetadata

case class Product(a: EndpointMetadata, b: EndpointMetadata) extends EndpointMetadata

case object Const extends EndpointMetadata

case object Empty extends EndpointMetadata

}
6 changes: 6 additions & 0 deletions core/src/main/scala/io/finch/Endpoints.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ trait Endpoints extends Bodies
EndpointResult.Matched(input.copy(route = Nil), Rs.OutputHNil)

final override def toString: String = "*"

final def meta: Endpoint.Meta = EndpointMetadata.Path(Segment.Wildcard)
}

/**
Expand All @@ -33,6 +35,8 @@ trait Endpoints extends Bodies
EndpointResult.Matched(input, Rs.OutputHNil)

final override def toString: String = ""

final def meta: Endpoint.Meta = EndpointMetadata.Path(Segment.Empty)
}

/**
Expand All @@ -43,5 +47,7 @@ trait Endpoints extends Bodies
EndpointResult.Matched(input, Rs.payload(input.request))

final override def toString: String = "root"

final def meta: Endpoint.Meta = EndpointMetadata.Const
}
}
3 changes: 3 additions & 0 deletions core/src/main/scala/io/finch/endpoint/body.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ private abstract class FullBody[A] extends Endpoint[A] {
}

final override def item: RequestItem = items.BodyItem

final def meta: Endpoint.Meta = EndpointMetadata.Body
}

private object FullBody {
Expand Down Expand Up @@ -153,5 +155,6 @@ private[finch] trait Bodies {

final override def item: RequestItem = items.BodyItem
final override def toString: String = "asyncBody"
final def meta: Endpoint.Meta = EndpointMetadata.Body
}
}
1 change: 1 addition & 0 deletions core/src/main/scala/io/finch/endpoint/cookie.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ private abstract class Cookie[A](name: String) extends Endpoint[A] {

final override def item: items.RequestItem = items.CookieItem(name)
final override def toString: String = s"cookie($name)"
final def meta: Endpoint.Meta = EndpointMetadata.Cookie(name)
}

private object Cookie {
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/io/finch/endpoint/header.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ private abstract class Header[F[_], A](name: String, d: DecodeEntity[A], tag: Cl

final override def item: RequestItem = items.HeaderItem(name)
final override def toString: String = s"header($name)"
final def meta: Endpoint.Meta = EndpointMetadata.Header(name)
}

private object Header {
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/io/finch/endpoint/multipart.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ private abstract class Multipart[A, B](name: String) extends Endpoint[B] {

final override def item: RequestItem = ParamItem(name)
final override def toString: String = name
final def meta: Endpoint.Meta = EndpointMetadata.Multipart(name)
}

private object Multipart {
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/scala/io/finch/endpoint/param.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ private abstract class Param[F[_], A](name: String, d: DecodeEntity[A], tag: Cla

final override def item: items.RequestItem = items.ParamItem(name)
final override def toString: String = s"param($name)"

final def meta: Endpoint.Meta = EndpointMetadata.Parameter(name)

}

Expand Down Expand Up @@ -75,6 +75,7 @@ private abstract class Params[F[_], A](name: String, d: DecodeEntity[A], tag: Cl
}
final override def item: items.RequestItem = items.ParamItem(name)
final override def toString: String = s"params($name)"
final def meta: Endpoint.Meta = EndpointMetadata.Parameters(name)
}

private object Params {
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/scala/io/finch/endpoint/path.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ private class MatchPath(s: String) extends Endpoint[HNil] {
}

final override def toString: String = s
final def meta: Endpoint.Meta = EndpointMetadata.Path(Segment.Part(s))
}

private class ExtractPath[A](implicit d: DecodePath[A], ct: ClassTag[A]) extends Endpoint[A] {
Expand All @@ -28,6 +29,9 @@ private class ExtractPath[A](implicit d: DecodePath[A], ct: ClassTag[A]) extends
}

final override def toString: String = s":${ct.runtimeClass.getSimpleName.toLowerCase}"

final def meta: Endpoint.Meta =
EndpointMetadata.Path(Segment.Part(ct.runtimeClass.getSimpleName.toLowerCase))
}

private class ExtractPaths[A](implicit d: DecodePath[A], ct: ClassTag[A]) extends Endpoint[Seq[A]] {
Expand All @@ -38,6 +42,9 @@ private class ExtractPaths[A](implicit d: DecodePath[A], ct: ClassTag[A]) extend
)

final override def toString: String = s":${ct.runtimeClass.getSimpleName.toLowerCase}*"

final def meta: Endpoint.Meta =
EndpointMetadata.Path(Segment.Part(s"${ct.runtimeClass.getSimpleName.toLowerCase}*"))
}

private[finch] trait Paths {
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala/io/finch/syntax/EndpointMapper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ class EndpointMapper[A](m: Method, e: Endpoint[A]) extends Endpoint[A] { self =>
else EndpointResult.Skipped

final override def toString: String = s"${ m.toString.toUpperCase } /${ e.toString }"

final def meta: Endpoint.Meta = EndpointMetadata.Method(m, e.meta)
}
33 changes: 33 additions & 0 deletions core/src/test/scala/io/finch/EndpointMetadataSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.finch

import io.finch.syntax.EndpointMapper

class EndpointMetadataSpec extends FinchSpec {

behavior of "EndpointMetadata"

private def interpreter(ms: EndpointMetadata): Endpoint[_] = ms match {
case EndpointMetadata.Method(m, meta) => new EndpointMapper(m, interpreter(meta))
case EndpointMetadata.Path(s) => s match {
case Segment.Part(part) => path(part)
case Segment.Wildcard => *
case Segment.Empty => /
}
case EndpointMetadata.Multipart(name) => multipartAttribute(name)
case EndpointMetadata.Cookie(name) => cookie(name)
case EndpointMetadata.Parameter(name) => param(name)
case EndpointMetadata.Parameters(name) => params(name)
case EndpointMetadata.Header(name) => header(name)
case EndpointMetadata.Body => stringBody
case EndpointMetadata.Empty => Endpoint.empty[String]
case EndpointMetadata.Const => Endpoint.const("foo")
case EndpointMetadata.Coproduct(a, b) => interpreter(b) :+: interpreter(a)
case EndpointMetadata.Product(a, b) => interpreter(a) :: interpreter(b)
}

it should "do a round-trip" in {
check { l: EndpointMetadata =>
interpreter(l).meta === l
}
}
}
Loading

0 comments on commit 1c157dc

Please sign in to comment.