Closed tg44 closed 3 years ago
I'll have to think about this a bit more, but a first reflex and quickly writing down a possible solution (maybe it doesn't work ;) ) would be to use Codec
s:
import scala.collection.immutable.ListMap
import sttp.tapir._
object TestMulti extends App {
def codecEither[L, A, B, CF <: CodecFormat](c1: Codec[L, A, CF], c2: Codec[L, B, CF]): Codec[L, Either[A, B], CF] = {
Codec
.id[L, CF](
c1.format,
Schema[L](
SchemaType.SCoproduct(
SchemaType.SObjectInfo(s"Either[${c1.schema.schemaType.show},${c2.schema.schemaType.show}]"),
ListMap(), // TODO
None
)(_ => None) // TODO
)
)
.mapDecode[Either[A, B]] { (l: L) =>
c1.decode(l) match {
case _: DecodeResult.Failure => c2.decode(l).map(Right(_))
case DecodeResult.Value(v) => DecodeResult.Value(Left(v))
}
} {
case Left(a) => c1.encode(a)
case Right(b) => c2.encode(b)
}
}
implicit val longOrActualCodec: Codec[String, Either[Long, Unit], CodecFormat.TextPlain] =
codecEither(Codec.long, Codec.string.validate(Validator.`enum`(List("actual"))).map(_ => ())(_ => "actual"))
path[Either[Long, Unit]]
}
At least currently, you can only combine two inputs/outputs using AND, not with an OR. The composition is limited, but so far we've managed without adding this rather complex scenario :)
After some thinkering and experimenting I think for these situations one of the best method is;
val p1: Seq[EndpointInput.Basic[Option[Long]]] = Seq(
path[Long].map(i => Option(i))(_.get),
stringToPath("actual").map(_ => Option.empty[Long])(_ => {}),
)
val p2: Seq[EndpointInput[_ <: ReportScope]] = Seq(
stringToPath("plant").map(_ => Plant())(_ => {}),
stringToPath("department").and(path[Long].map(i => Department(i))(i => i.id)),
stringToPath("line").and(path[Long].map(i => Line(i))(i => i.id)),
)
val endpoints = for {
lOrA <- p1
scope <- p2
} yield {
endpoint.get
.in(lOrA)
.in(scope)
}
This will unwrap the endpoints and will generate them one-by-one. The other side can choose if they want to model the routing with knowing that the param is either a Long
or the string "actual"
, or not. Much easier than handle all the Codecs
and Schemas
, and probably the next programmer who will read the code will have a better time too :D
Haha true :) Though, maybe adding a codecEither
to Codec
won't be a bad idea. But I agree that having multiple endpoints is easiest, and in the spirit of the library, where Endpoint
s are the central primitive.
I think we can close this, for-comp for the win :)
My first use-case is simple; I want a path matcher which matches either to a
Long
or the exact string"actual"
.In akkaHttp I could write a matcher like;
So both
foo/5/bar
andfoo/actual/bar
could be matched with it. OpenApi can handle this too as a(probably the enum can be changed to an exact match, but I'm not pro at the specification).
What I've tried is;
This matches to
Long
but not matching the string, and generates a rather wrong API description.My second use-case would be a bit bigger, bcs I also want to create a matcher which can replicate the same functionality as;
And thirdly, it would be super interesting to add an option to "unpack" these matches in the docs. (So in the generated docs the above scope matcher would appear as 3 "different" routes). This unpack would be sometimes handy for;
enums too, which could have been part of the original codebase or the enumerator integration page.