elm-router
elm-router copied to clipboard
parameters in routes / alternate API design
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.
Similar to the design of routing and handlers in Yesod?
http://www.yesodweb.com/book/routing-and-handlers
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.
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.
Sounds interesting. you're welcome to fork the repo and try your hand at an example
+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.
Yeah, that's what I was thinking. I'll try to take a shot at it in the next few days!
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
-}