softwaremill / tapir

Rapid development of self-documenting APIs
https://tapir.softwaremill.com
Apache License 2.0
1.34k stars 405 forks source link

Method for extracting path from endpoint #112

Closed PawelJ-PL closed 5 years ago

PawelJ-PL commented 5 years ago

According to discussion on Gitter, it would be nice to have methods for extracting path from endpoint. My proposal:

For endpoint:

val myEndpoint: Endpoint[(String, Long), Unit, Unit, Nothing] =
    endpoint.in("aaa" / "bbb" / path[String]("s1") /"ccc" / path[Long]("s2"))

1) def renderPath(): String = ??? example call: myEndpoint.renderPath() should return: aaa/bbb/:s1:/ccc/:s2:

2) I'm not sure how easy it will be to achieve because number and type of segments won't be fixed: def renderPath(segments: (String, Int)): String = ??? example call: myEndpoint.renderPath(("XXX", 123)) should return: aaa/bbb/XXX/ccc/123

3) With:

sealed trait PathFragment extends Product with Serializable
object PathFragment {
    case class Fixed(name: String) extends PathFragment
    case class Captured(name: String) extends PathFragment
}

def pathFragments(): Seq[PathFragment] = ??? example call: myEndpoint.pathFragments(): should return: Seq(PathFragment.Fixed("aaa"), PathFragment.Fixed("bbb"), PathFragment.Captured("s1"), PathFragment.Fixed("ccc"), PathFragment.Captured("s2"))

I'm not sure about third method. Maybe it could be improved.

PawelJ-PL commented 5 years ago

There is an example of similar solution from Haskell Servant:

module Http.TestLink where

import Servant

import Universum

import Servant.Swagger.UI (SwaggerSchemaUI)

type NamesApi

   = "v1" :> "test" :> Capture "id" Text :> "names" :> QueryFlag "testFlag" :> QueryParam "authors" Text :> ReqBody '[ JSON] Text :> Post '[ JSON] Text

type TestPut = "put" :> Put '[ PlainText] Text

type ReleaseInt = "release" :> Capture "myId" Int :> Post '[ PlainText] Text

type TestApi = TestPut :<|> ReleaseInt

type MyApi = NamesApi :<|> TestApi

type DocumentedApis = MyApi

type ApplicationApi = SwaggerSchemaUI "swagger" "swagger.json" :<|> DocumentedApis

linkTo id flag autor = linkURI $ safeLink (Proxy :: Proxy ApplicationApi) (Proxy :: Proxy NamesApi) id flag autor

linkTo "ttt" True Nothing

-- result ->  v1/test/ttt/names?testFlag

linkTo "ttt" True (Just "testUser")

-- result ->  v1/test/ttt/names?testFlag&authors=testUser
adamw commented 5 years ago

There's one problem here; the servant types capture more information than tapir does - that is, it's encoded in the type which parameter comes from the query, which from the path and which from the body (at least as far as I understand it). So they have the possibility of extracting the path-type only and generating a link.

With tapir, that's not possible - looking at the type of an endpoint's input, we don't know which values belong to the path, which to the query etc.

What would be possible, though, is defining a method which, given the input values, would "render the request" - that is return the path, query, headers and body.

This, in fact, would be quite close to interpreting an endpoint as a sttp request. Maybe that would work for you?

PawelJ-PL commented 5 years ago

I think it should be ok. In my case I'm constructing sttp Uri instance from extracted path