-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#203: Wrap POST body as in wiro (closes #203) #212
Conversation
@@ -9,33 +9,42 @@ import cats.data.NonEmptyList | |||
object Meta { | |||
val codecsImplicits = (routes: List[TapiroRoute]) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is getting quite messy...
Part of the problem is that we're delaying the conversion to scalameta type for each type of codec because we want to call .distinct
while we still have a list of metarpheus types, because it wouldn't work on scalameta types because ==
is by reference
Maybe we could use structural equality from scala.meta.contrib (https://scalameta.org/docs/trees/guide.html#compare-trees-for-equality) to clean this up a bit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think using isEqual
is a good idea
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried to rewrite that function and I think it reads a bit better now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made a first pass: overall it looks like a good approach.
It would be nice to add unit tests before merging, once #211 is in
case 0 => q"_ => $controllersName()" | ||
case 1 => controllersName | ||
case _ => q"($controllersName _).tupled" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
} | ||
case RouteMethod.POST => | ||
val fields = route.route.params | ||
.filterNot(_.tpe == MetarpheusType.Name("AuthToken")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it correct that we hardcode AuthToken
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's sort-of-correct right now because we're hardcoding it everywhere anyway, so we expect the type parameter in the controller trait to be always named exactly AuthToken
. As I understand it, we should fix that in #204 when we change metarpheus to return the real name of the type parameter
q"x => $controllersName(..${fields.map(f => q"x.$f")})" | ||
} | ||
val toRoute = q"$endpointsName.toRoute($controllerContent)" | ||
q"val ${Pat.Var(name)} = $toRoute" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor, but I think this can be
q"val ${Pat.Var(name)} = $toRoute" | |
q"val $name = $toRoute" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't get this to work 🙁 it expects a Pat
there and $name
is a Term.Name
, which doesn't seem to work (using a String
instead compiles but generates a literal string pattern)
val endpoints = (routes: List[TapiroRoute]) => | ||
routes.map { route => | ||
val name = Term.Name(route.route.name.last) | ||
val endpointsName = q"endpoints.$name" | ||
val controllersName = q"controller.$name" | ||
val controllerContent = | ||
if (route.params.length <= 1) Some(controllersName) | ||
else Some(Term.Select(Term.Eta(controllersName), Term.Name("tupled"))) | ||
controllerContent.map { content => | ||
val toRoutes = Term.Apply(Term.Select(endpointsName, Term.Name("toRoutes")), List(content)) | ||
q"val ${Pat.Var(name)} = $toRoutes" | ||
} | ||
route.method match { | ||
case RouteMethod.GET => | ||
route.route.params.length match { | ||
case 0 => q"_ => $controllersName()" | ||
case 1 => controllersName | ||
case _ => q"($controllersName _).tupled" | ||
} | ||
case RouteMethod.POST => | ||
val fields = route.route.params | ||
.filterNot(_.tpe == MetarpheusType.Name("AuthToken")) | ||
.map(p => Term.Name(p.name.getOrElse(Meta.typeNameString(p.tpe)))) | ||
q"x => $controllersName(..${fields.map(f => q"x.$f")})" | ||
} | ||
val toRoutes = q"$endpointsName.toRoutes($controllerContent)" | ||
q"val ${Pat.Var(name)} = $toRoutes" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems identical to the same code in AkkaHttpMeta
. Can we deduplicate it? (Not necessarily in this PR)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've factored out most of the duplication while fixing bugs
There's still some duplicated code here and there that processes route parameters (filtering out the auth param, computing default param names from their types if missing, etc.) which I think we should factor out, but it's best done in another PR I think
In particular, I think we could include in TapiroRoute
a "cleaned" version of the route parameters, already processed so we don't have to do the same processing in different functions
@@ -9,33 +9,42 @@ import cats.data.NonEmptyList | |||
object Meta { | |||
val codecsImplicits = (routes: List[TapiroRoute]) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think using isEqual
is a good idea
This generates wrong I launched tapiro via
where Main is
then compiled the target service (called
and got the following
|
Many thanks for the test @giuscri! I had indeed messed things up with with auth tokens 🙈 The version I just pushed (a8ecd98) should fix that. I still get an error on your code, but it seems unrelated ( Adding some unit tests will surely be useful 😄 |
I removed myself as an assignee enough people is working on this :D |
(reverting 0a856a8)
a8ecd98
to
fad617e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two minor comments, but otherwise LGTM!
@@ -5,7 +5,7 @@ import java.nio.file.Files | |||
class TapiroSuite extends munit.FunSuite { | |||
|
|||
check( | |||
"http4s", | |||
"tapir and http4s endpoints", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: let's try to keep test names short and without spaces, so that they're easily selectable for testOnly
.
E.g.
tapiroCore/testOnly -- io.buildo.tapiro.TapiroSuite.tapir-http4s-endpoints
@@ -44,17 +45,10 @@ object Http4sMeta { | |||
q"Router($route -> NonEmptyList($first, List(..$rest)).reduceK)" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not related to this PR, but this can be:
q"Router($route -> NonEmptyList($first, List(..$rest)).reduceK)" | |
q"Router($route -> NonEmptyList.of(first, rest: _*).reduceK)" |
Closes #2520577
Closes #203
String
output (see tapiro generated code will need a JsonCodec for _primitive_ types (String, Int, ...) #180 (comment) and later comments)Notes:
JsonCodec
, I've added an import ofimport sttp.tapir.json.circe._
to that file (so it can generateJsonCodec
instances given CirceEncoder
andDecoder
) and added the Circe semiauto derivation of theEncoder
andDecoder
, adding implicit parameters ofEncoder
andDecoder
for field names.JsonCodec
/PlainCodec
with circeEncoder
/Decoder
, it would be maybe nicer to always requireJsonCodec
and be able to derive theJsonCodec
for the case class from those of the fields, but I couldn't find a way to do it.Encoder
, but tapir always expects both to be able to describe clients I think.For example, for the controller
we generate the tapir endpoints
and the http4s endpoints
Test Plan
Tested on a small example project