anuragsoni / routes

typed bidirectional router for OCaml/ReasonML web applications
https://anuragsoni.github.io/routes/
BSD 3-Clause "New" or "Revised" License
145 stars 11 forks source link

Compiler error when combining custom pattern with "variable pattern" (e.g. str) #144

Closed woeps closed 2 years ago

woeps commented 2 years ago

Hi, I tried to tinker a little bit more (since last year) with this great library and got into troubles when I was trying to use a custom pattern together with str in several paths.

For examle take your routing_test.ml:288 "test custom pattern" and modify it like this:

let%expect_test "test custom pattern reuse" =
  let open Routes in
  let shape = pattern shape_to_string shape_of_string ":shape" in
(*  error if shape function is used twice: let shape' = custom ~serialize:shape_to_string ~parse:shape_of_string ~label:":shape" in *)
  let r1 () =
    (s "foo" / int / s "shape" / shape /? nil) (* NOTE: this call is now using shape as well *)
    @--> fun c shape -> Printf.sprintf "%d - %s" c (shape_to_string shape)
  in
  let r2 () = s "shape" / shape / str /? nil in (* NOTE: str was s "create" previously *)
  let router = one_of [ r1 () ] in
  let results =
    [ ( "can match a custom pattern"
      , ensure_string_match' ~target:"/foo/12/shape/Circle" router )
    ; ( "Invalid shape does not match"
      , ensure_string_match' ~target:"/foo/12/shape/rectangle" router )
    ; "pretty print custom pattern", Some (string_of_route (r1 ()))
    ; "serialize route with custom pattern", Some (sprintf (r2 ()) Square "name")
    ]
  in
  printf !"%{sexp: (string * string option) list}\n" results;
  [%expect
    {|
    (("can match a custom pattern" ("Exact match with result = 12 - circle"))
     ("Invalid shape does not match" ())
     ("pretty print custom pattern" (/foo/:int/shape/:shape))
     ("serialize route with custom pattern" (/shape/square/name))) |}]
;;

This will yield a compiler error:

This has type:
    ('a, 'b) RoutingExample.Routes.path ->
    (string -> 'a, 'b) RoutingExample.Routes.path
  Somewhere wanted:
    ('a, 'b) RoutingExample.Routes.path ->
    (string, string) RoutingExample.Routes.path

regarding str in

 let r2 () = s "shape" / shape / str /?

Is it possible to reuse custom patterns?

anuragsoni commented 2 years ago

Hi @woeps

The error you see is related to value restriction. You can make shape reusable by changing the definition to let shape () = pattern shape_to_string shape_of_string ":shape", and the route definitions to:

  let r1 () =
    (s "foo" / int / s "shape" / shape () /? nil) (* NOTE: this call is now using shape as well *)
    @--> fun c shape -> Printf.sprintf "%d - %s" c (shape_to_string shape)
  in
  let r2 () = s "shape" / shape () / str /? nil in (* NOTE: str was s "create" previously *)

I mention value restriction in the docs https://github.com/anuragsoni/routes/blob/main/src/routes.mli#L8-L16 but there is room for improvement when it comes to documenting this better.

woeps commented 2 years ago

Oh, damn. I totally overlooked, that's what I already use for reusable paths. Somehow I didn't see, I could reuse pattern this way as well.

Thank you very much for taking your time to respond and explain it to me.