Closed jchavarri closed 3 years ago
This problem is easily solved if you make it more data-driven instead. Example:
let t = fun () -> [
s "foo" / str / s "bar" /? nil @-> fun s -> Foo s;
s "baz" /? nil @-> fun () -> Baz
]
@anmonteiro Maybe I'm not understanding well, but the suggested approach would make impossible to have the composability and extensibility that target type provides:
Is there a way to do this currently without having to include the handlers in the list?
The reason why I am looking for a solution that doesn't involve handlers is that using the generic type ('a, 'b) Routes.target list;
allows to define targets in multiple places without coupling them together. But still each list of targets can be matched against a handler that fits the target output type.
While having a closed type t list
like suggested above does not allow for any kind of composition / extensibility.
it sounded to me like you were sharing routes in the frontend / backend, and you'd want different functions to be called according to the environment. My proposal solves that by having a common data type of routes, instead of running the actions directly in the route handlers.
To give a more specific example, I have different "controllers" (each one for a different part of an app), like:
(* projects_controller.ml *)
let config =
{
...;
targets = (fun () -> [
s "project" / int64 / s "bar" /? nil;
s "add" /? nil]);
]
}
(* users_controller.ml *)
let config =
{
...;
targets = (fun () -> [
s "profile" / int64 /? nil;
s "list" /? nil]);
]
}
the nice thing about this design is that each controller can be added or removed without touching anything else. While having a common data type of routes means I have to change this type every time a new controller is added (or an existing one removed).
it sounded to me like you were sharing routes in the frontend / backend, and you'd want different functions to be called according to the environment.
Ideally, in the future i'd like to use these same targets to pretty print urls on the frontend, yes.
@jchavarri Is there a way to do this currently without having to include the handlers in the list? (Handlers are generally platform specific so I'd like to keep them away from this list of targets).
I don't think it'd be possible to achive this with the current api, and I can't think of how to do this with a nice interface in general. That's the con of type level lists i think, each time we have a new parameter we pluck, its reflected in the type. One way of hiding this type knowledge would be to wrap things into an existential
type my_route = MyRoute : ('a, 'c) target -> my_route;;
utop # let r () = s "foo" / str / s "bar" /? nil;;
val r : unit -> (string -> 'a, 'a) target = <fun>
utop # let r' = MyRoute (r());;
val r' : my_route = MyRoute <abstr>
But this doesn't help as there is no way to "unpack" this wrapped value and make use of the content.
@anmonteiro Maybe I'm not understanding well, but the suggested approach would make impossible to have the composability and extensibility that target type provides:
Is there a way to do this currently without having to include the handlers in the list?
The reason why I am looking for a solution that doesn't involve handlers is that using the generic type
('a, 'b) Routes.target list;
allows to define targets in multiple places without coupling them together. But still each list of targets can be matched against a handler that fits the target output type.While having a closed type
t list
like suggested above does not allow for any kind of composition / extensibility.
One possible approach could be to not group things as a list, but define the routes that need to be shared as regular targets and expose them in the mli, and also create a router in the specific module that's also exposed in the mli file. The routers can then be composed elsewhere via https://github.com/anuragsoni/routes/blob/ff9a4e154d88d1342ebb46a145e6bedee9ae0341/src/routes.mli#L219 and the individual targets can be used in places where you need sprintf/pretty printing etc. It'll be a little verbose than being able to pack things in a list, but i'm not sure if there is a better way to achieve this without making the library even more confusing than the current state 😬
@anuragsoni I think using a router is conceptually similar to what @anmonteiro was suggesting: in his proposal, one would stop at the point where a list of routes is created, while in yours (afaiu) one would apply the one_of
function to that list to get a router.
I will explore these paths and report back any updates. Thanks a lot to both of you for the suggestions!
I have some targets like:
that I would like to group under the same list. The above fails to compile as first target will pass the
string
over, while second one does not.I have been looking at
pattern
so that I could wrap the payload in some variant like:but it does not seem possible as
pattern
is limited to one path segment.Is there a way to do this currently without having to include the handlers in the list? (Handlers are generally platform specific so I'd like to keep them away from this list of targets).
Thanks!