Tapir very naturally gives me endpoint vals that let me build clients and servers. I use these one-at-a-time with .serverLogic() , then aggregate them - literally a List - to build up a server. That’s working really well.
Is there a good standard practice to let me ensure that a server supports all the endpoints I meant to aggregate with appropriate .serverLogic() filling? Preferably with a a compile-time error if I leave one out?
The best idea I’ve thought of is a service-specific trait AggregatedServer that I add a new def to every time I add a new endpoint. I’d cut/paste the def ‘s parameter and return type to match each .serverLogic()'s spec. Is there a better way? There must be.
Hm I don’t think I fully understand the problem - can you share an example, of what incorrect usage you’d like to fail at compile-time? Is it ensuring that each endpoint has a server defintion?
I should be able to. The sbt warnings can get pretty precise. … But it’s not enough that the endpoints aren’t used; test code could use them for example. They need to be used to make a server.
(Plus I’ll be defining the endpoints in one project, using them in a server in another.)
I don’t think there’s any “best practice”, but I think what you’d want to achieve should be possible with a pretty straightforward macro. Of course, that also depends on the precise definition of supports all the endpoints I meant to aggregate - as the computer has to know what you mean
But a macro which inspects a class/object and checks if every field of type Endpoint is used in some other class/object, is something even an LLM should implement, I think.
Any way to do it short of a macro? I have to support Scala 2 and 3.
I’d rather have the compiler do the work, but maybe there’s a reusable test I could write using reflection. It’s really easy to put the endpoints in a list - maybe make that via reflection - or by a trait on the object that holds the spec. (And I have to do that to make OpenAPI anyway.)
I actually have a custom hlist-alike that I use for exactly this, but it always felt too gross to pr back into tapir. General idea is that you use a source code generator on the tapir plugin output to creat a a :+: b :+: c… then fold over the definitions with implementations. It’s pretty gnarly and I wouldn’t want to support it right now but I’ll look into pring that, hopefully this year, if there’s interest (as I use it, it also needs to be able to convert from v1 to V2 of an endpoint using chimney so that the same implementation bindings can be used on both API specs and honestly the whole this is super gross and specific so. Um. Yeah, gotta figure out what the reusable part really looks like there… but anyway an hlist subtype where each arg is an endpoint definition might get you where you want to go, did for me