elm-router icon indicating copy to clipboard operation
elm-router copied to clipboard

parameters in routes / alternate API design

Open vilterp opened this issue 10 years ago • 7 comments

Maybe using different types like this:

type alias Router a =
  List (String, (List String -> Maybe a))

type Page
  = IndexPage
  | BlogPost String
  | MonthIndex Int Int

myRouter : Router Page
myRouter =
  [ "/" :-> (\_ -> Just IndexPage)
  , "/post/:slug" :-> (\[slug] -> Just (BlogPost slug))
  -- would need to use String.toInt and handle Results here,
  -- returning Nothing if they fail
  , "/posts/:year/:month" :-> (\[year, month] -> Just (MonthIndex year month))
  ]

type alias URL = String

match : Router a -> URL -> Maybe a
match = ...

I've seen the /:param_name syntax in Rails, react-router, Ember, etc; seems to work well. It could also allow you to do something like this:

urlFor : Router a -> a -> URL
urlFor = ...

-- urlFor (BlogPost "elm-is-cool") => "/post/elm-is-cool"

If you put this in as a link's href, when the browser goes to it it'll trigger that route.

vilterp avatar Aug 13 '15 17:08 vilterp

Similar to the design of routing and handlers in Yesod?

http://www.yesodweb.com/book/routing-and-handlers

jvoigtlaender avatar Aug 13 '15 18:08 jvoigtlaender

It is interesting and I have been looking into this sort of functionality, but my concern with your example is this line

"/post/:slug" :-> (\[slug] -> Just (BlogPost slug))

This sort of pattern matching is dangerous as it could cause runtime crashes. I was wondering if there were ways to offer similar functionality that is not so runtime-crash prone.

TheSeamau5 avatar Aug 13 '15 18:08 TheSeamau5

Maybe an approach like Json.Decode would be good:

type Segment = ...
type Route = ...

type alias Router a =
  List (Route a)

constant : String -> Segment ()
int : Segment Int
string : Segment String

root : a -> Route a -- only matches if URL is '/'
route1 : (a -> value) -> Segment a -> Route value
route2 : (a -> b -> value) -> Segment a -> Segment b -> Route value
...

match : Router a -> URL -> Maybe a

myRouter =
  [ root Index
  , route2 (\_ slug -> BlogPost slug) (constant "post") string
  ]

It's kind of like we're reinventing parser combinators but more constrained. Kind of hard to read but seems nice.

vilterp avatar Aug 13 '15 19:08 vilterp

Sounds interesting. you're welcome to fork the repo and try your hand at an example

TheSeamau5 avatar Aug 13 '15 19:08 TheSeamau5

+1 for making an API like Json.Decode - seems like you could similarly compose together "route decoders" to end up with an analogous decodeString which would be decodeString : Route.Decoder a -> String -> Result () a, and the Result error case would just be a 404 Not Found.

rtfeldman avatar Aug 13 '15 20:08 rtfeldman

Yeah, that's what I was thinking. I'll try to take a shot at it in the next few days!

vilterp avatar Aug 13 '15 21:08 vilterp

I ended up putting this together based on the parser combinator package elm-combine. Putting it here in case it helps anyone:

module Router where

import Combine exposing (..)
import Combine.Char exposing (..)
import Combine.Num exposing (..)
import Combine.Infix exposing (..)

{-| Parses one or more slashes -}
slash : Parser ()
slash = many1 (char '/') *> succeed ()

{-| Parses zero or more slashes -}
optionalSlash : Parser ()
optionalSlash = optional () slash

{-| Parses an optional slash followed by the end of the string -}
slashEnd : Parser ()
slashEnd = optionalSlash *> end

{-| Parses one or more slashes followed by the end of the string -}
root a = slash *> end *> succeed a

match : List (Parser a) -> a -> String -> a
match routers defaultRoute path = 
  case parse (choice routers) path of
    (Done a, _)  -> a
    _ -> defaultRoute


-- Example usage:

type AppRoutes = Index | Experiments | ExperimentDetail Int

router = 
  [ root Index
  , ExperimentDetail `map` (slash *> string "experiments" *> slash *> int <* slashEnd)
  , slash *> string "experiments" *> slashEnd *> succeed Experiments
  ]

{-
> import Router exposing (..)
> match router Index "/"
Index : Router.AppRoutes
> match router Index "/experiments"
Experiments : Router.AppRoutes
> match router Index "/experiments/34"
ExperimentDetail 34 : Router AppRoutes
> match router Index "dfsdfkdjf"
Index : Router.AppRoutes
-}

jhickner avatar Nov 30 '15 18:11 jhickner