Route Prism

Deriving IsRoute for our route types gives us a routePrism function that returns (effectively1) a Prism' FilePath r (from optics-core Prism') that in turn can be used to encode and decode route values to and from URLs or filepaths.

Let’s consider the example route,

Example route
-- An example of nested routes
-- Route's expected encoding is given as a comment.

data Route
  = Route_Index          -- index.html
  | Route_About          -- about.html
  | Route_Contact        -- contact.html
  | Route_Blog BlogRoute -- blog/<BlogRoute>

data BlogRoute
  = BlogRoute_Index      -- index.html
  | BlogRoute_Post Slug  -- post/<Slug>

newtype Slug = Slug { unSlug :: String }

Here is a naive implementation of IsRoute for the BlogRoute above:

instance IsRoute BlogRoute where
  type RouteModel BlogRoute = ()
  routePrism () = toPrism_ $
    prism'
      ( \case 
          BlogRoute_Index -> "index.html"
          BlogRoute_Post slug -> "posts" </> slug <> ".html"
      )
      ( \case
          "index.html" -> Just BlogRoute_Index
          (parsePostSlug -> Just slug) -> Just $ BlogRoute_Post slug
          _ -> Nothing
      )
  routeUniverse () = []

In GHCi you can play with this prism as,

ghci> let rp = routePrism @BlogRoute ()
ghci> preview rp "posts/foo.html"
Just (BlogRoute_Post (Slug "foo"))
ghci> review rp $ BlogRoute_Post (Slug "foo")
"posts/foo.html"

Ema provides a routeUrl function that converts this filepath to an URL.

Generic prism

routePrism can also be generically determined for routes with “standard shapes” (both Route and BlogRoute above); see Generic deriving.