| Copyright | (C) 2016-2026 David M. Johnson |
|---|---|
| License | BSD3-style (see the file LICENSE) |
| Maintainer | David M. Johnson <code@dmj.io> |
| Stability | experimental |
| Portability | non-portable |
| Safe Haskell | None |
| Language | Haskell2010 |
Miso.Router
Description
Overview
Miso.Router provides a type-safe, bidirectional client-side router.
A Haskell sum type represents your application's routes; the Router
class encodes and decodes between that type and URL strings. The router
is used together with uriSub or
routerSub to react to browser navigation.
Approach 1 — Generic deriving (recommended)
{-# LANGUAGE DeriveGeneric, DeriveAnyClass, DerivingStrategies #-}
import GHC.Generics (Generic)
import Miso.Router
data Route
= Index -- "/"
| About -- "/about"
| User (Capture "id" Int) -- "/user/42"
| Search (QueryParam "q" MisoString) -- "/search?q=foo"
deriving stock (Show, Eq, Generic)
deriving anyclass Router
Decoding:
toRoute"/user/42" -- Right (User (Capture 42))toRoute"/search?q=hello" -- Right (Search (QueryParam (Just "hello")))
Encoding (type-safe links):
prettyRoute(User (Capture 42)) -- "/user/42" button_ [href_(User (Capture 42)) ] [ text "Profile" ]
Approach 2 — Manual instance
data Route = Widget Int deriving (Show, Eq) instanceRouterRoute where routeParser =routes[ Widget <$> (path"widget" *>capture) ] fromRoute (Widget n) = [toPath"widget",toCapturen ]
Generic naming rules
- Constructor name becomes the lowercase path segment:
About→/about,UserProfile→/user(first camel-case hump only). - A constructor whose first camel-case hump is
Index(Index,IndexPage) encodes the root path/. ItsQueryParamandQueryFlagfields are encoded into the query string and decoded back: with aQueryParam "page" Intfield,Index (QueryParam (Just 2))is/?page=2.Capture,Pathand nested route fields are not supported on an index route: their path segments are dropped when encoding, so the route can't be decoded from the URL it prints. AFragmentfield is encoded but can't be decoded yet. Don't give one type two index constructors (sayIndexandIndexPage): both match/, so decoding it is ambiguous. - The position of
CaptureandPathfields in the constructor determines their order in the URL path. The position ofQueryParamandQueryFlagdoes not matter.
URL types
Capturesym a- dynamic path segment —
Capture 42→/42 Pathsym- fixed path segment —
Path "foo"→/foo QueryParamsym a- optional query key —
QueryParam (Just 1)→?sym=1,QueryParam Nothing→ omitted QueryFlagsym- boolean query flag —
QueryFlag True→?sym,QueryFlag False→ omitted Fragmentsym- hash fragment —
Fragment→#sym
Integration with history subscription
import Miso.Subscription.History (routerSub) subs :: [SubAction] subs = [routerSubRouteChanged ]
See also
- Miso.Subscription.History —
uriSub,routerSub,pushURI - Miso.Html.Property —
href_(plain string version)
Synopsis
- class Router route where
- fromRoute :: route -> [Token]
- toURI :: route -> URI
- route :: URI -> Either RoutingError route
- href_ :: route -> Attribute model action
- prettyRoute :: route -> MisoString
- dumpURI :: route -> MisoString
- toRoute :: MisoString -> Either RoutingError route
- routeParser :: RouteParser route
- type RouteParser = ParserT URI [Token] []
- class GRouter (f :: k -> Type) where
- gFromRoute :: forall (route :: k). f route -> [Token]
- gRouteParser :: forall (route :: k). RouteParser (f route)
- newtype Capture (sym :: Symbol) a = Capture a
- newtype Path (path :: Symbol) = Path MisoString
- newtype QueryParam (path :: Symbol) a = QueryParam (Maybe a)
- newtype QueryFlag (path :: Symbol) = QueryFlag Bool
- data Fragment (path :: Symbol) = Fragment
- data Token
- data URI = URI {}
- data RoutingError
- parseURI :: MisoString -> Either MisoString URI
- prettyURI :: URI -> MisoString
- prettyQueryString :: URI -> MisoString
- runRouter :: MisoString -> RouteParser route -> Either RoutingError route
- routes :: [RouteParser route] -> RouteParser route
- toQueryParam :: ToMisoString s => MisoString -> s -> Token
- toCapture :: ToMisoString string => string -> Token
- toPath :: MisoString -> Token
- emptyURI :: URI
- queryFlag :: forall (flag :: Symbol). KnownSymbol flag => RouteParser (QueryFlag flag)
- queryParam :: forall (param :: Symbol) a. (FromMisoString a, KnownSymbol param) => RouteParser (QueryParam param a)
- capture :: FromMisoString value => RouteParser value
- path :: MisoString -> RouteParser MisoString
- fragment :: MisoString -> RouteParser MisoString
- lexTokens :: MisoString -> Either LexerError [Token]
- tokensToURI :: [Token] -> URI
Classes
class Router route where Source #
Class used to facilitate routing for miso applications
Minimal complete definition
Nothing
Methods
fromRoute :: route -> [Token] Source #
toURI :: route -> URI Source #
Convert a 'Router route => route' into a URI
route :: URI -> Either RoutingError route Source #
Map a URI back to a route
href_ :: route -> Attribute model action Source #
Convenience for specifying a URL as a hyperlink reference in View
prettyRoute :: route -> MisoString Source #
Route pretty printing
dumpURI :: route -> MisoString Source #
Route debugging
toRoute :: MisoString -> Either RoutingError route Source #
Route parsing from a MisoString
routeParser :: RouteParser route Source #
default routeParser :: (Generic route, GRouter (Rep route)) => RouteParser route Source #
Instances
| Router UUID Source # | |
Defined in Miso.UUID | |
class GRouter (f :: k -> Type) where Source #
Generic deriving for Router
Methods
gFromRoute :: forall (route :: k). f route -> [Token] Source #
gRouteParser :: forall (route :: k). RouteParser (f route) Source #
Instances
| GRouter (U1 :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). U1 route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser (U1 route) Source # | |
| (GRouter left, GRouter right) => GRouter (left :*: right :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). (left :*: right) route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser ((left :*: right) route) Source # | |
| (GRouter left, GRouter right) => GRouter (left :+: right :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). (left :+: right) route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser ((left :+: right) route) Source # | |
| (KnownSymbol name, GRouter next) => GRouter (C1 ('MetaCons name x y) next :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). C1 ('MetaCons name x y) next route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser (C1 ('MetaCons name x y) next route) Source # | |
| GRouter next => GRouter (D1 m next :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). D1 m next route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser (D1 m next route) Source # | |
| (FromMisoString a, ToMisoString a) => GRouter (K1 m (Capture sym a) :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). K1 m (Capture sym a) route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser (K1 m (Capture sym a) route) Source # | |
| KnownSymbol frag => GRouter (K1 m (Fragment frag) :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). K1 m (Fragment frag) route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser (K1 m (Fragment frag) route) Source # | |
| KnownSymbol path => GRouter (K1 m (Path path) :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). K1 m (Path path) route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser (K1 m (Path path) route) Source # | |
| KnownSymbol flag => GRouter (K1 m (QueryFlag flag) :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). K1 m (QueryFlag flag) route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser (K1 m (QueryFlag flag) route) Source # | |
| (ToMisoString a, FromMisoString a, KnownSymbol param) => GRouter (K1 m (QueryParam param a) :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). K1 m (QueryParam param a) route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser (K1 m (QueryParam param a) route) Source # | |
| Router a => GRouter (K1 m a :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). K1 m a route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser (K1 m a route) Source # | |
| GRouter next => GRouter (S1 m next :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). S1 m next route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser (S1 m next route) Source # | |
Types
newtype Capture (sym :: Symbol) a Source #
Type used for representing capture variables
Constructors
| Capture a |
Instances
| (FromMisoString a, ToMisoString a) => GRouter (K1 m (Capture sym a) :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). K1 m (Capture sym a) route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser (K1 m (Capture sym a) route) Source # | |
| Eq a => Eq (Capture sym a) Source # | |
| Show a => Show (Capture sym a) Source # | |
| FromMisoString a => FromMisoString (Capture sym a) Source # | |
Defined in Miso.Router Methods fromMisoStringEither :: MisoString -> Either String (Capture sym a) Source # | |
| ToMisoString a => ToMisoString (Capture sym a) Source # | |
Defined in Miso.Router Methods toMisoString :: Capture sym a -> MisoString Source # | |
newtype Path (path :: Symbol) Source #
Type used for representing URL paths
Constructors
| Path MisoString |
Instances
| KnownSymbol path => GRouter (K1 m (Path path) :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). K1 m (Path path) route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser (K1 m (Path path) route) Source # | |
| Eq (Path path) Source # | |
| IsString (Path path) Source # | |
Defined in Miso.Router Methods fromString :: String -> Path path # | |
| Show (Path path) Source # | |
| ToMisoString (Path path) Source # | |
Defined in Miso.Router Methods toMisoString :: Path path -> MisoString Source # | |
newtype QueryParam (path :: Symbol) a Source #
Type used for representing query parameters
Constructors
| QueryParam (Maybe a) |
Instances
newtype QueryFlag (path :: Symbol) Source #
Type used for representing query flags
Instances
| KnownSymbol flag => GRouter (K1 m (QueryFlag flag) :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). K1 m (QueryFlag flag) route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser (K1 m (QueryFlag flag) route) Source # | |
| Eq (QueryFlag path) Source # | |
| Show (QueryFlag path) Source # | |
| KnownSymbol name => ToMisoString (QueryFlag name) Source # | Renders this one flag as |
Defined in Miso.Router Methods toMisoString :: QueryFlag name -> MisoString Source # | |
data Fragment (path :: Symbol) Source #
Type used for representing fragments
Constructors
| Fragment |
Instances
| KnownSymbol frag => GRouter (K1 m (Fragment frag) :: k -> Type) Source # | |
Defined in Miso.Router Methods gFromRoute :: forall (route :: k). K1 m (Fragment frag) route -> [Token] Source # gRouteParser :: forall (route :: k). RouteParser (K1 m (Fragment frag) route) Source # | |
| Eq (Fragment path) Source # | |
| Show (Fragment path) Source # | |
| KnownSymbol frag => ToMisoString (Fragment frag) Source # | |
Defined in Miso.Router Methods toMisoString :: Fragment frag -> MisoString Source # | |
A list of tokens are returned from a successful lex of a URI
Constructors
| QueryParamTokens [(MisoString, Maybe MisoString)] | |
| QueryParamToken MisoString (Maybe MisoString) | |
| CaptureOrPathToken MisoString | |
| FragmentToken MisoString | |
| IndexToken |
Instances
| Eq Token Source # | |
| Show Token Source # | |
| ToMisoString Token Source # | Renders a single |
Defined in Miso.Router Methods toMisoString :: Token -> MisoString Source # | |
URI type. See the official specification
Constructors
| URI | |
Fields
| |
Instances
Errors
data RoutingError Source #
An error that can occur during lexing / parsing of a URI into a user-defined data type
Constructors
| ParseError MisoString [Token] | |
| AmbiguousParse MisoString [Token] | |
| LexError MisoString MisoString | |
| LexErrorEOF MisoString | |
| NoParses MisoString |
Instances
| Eq RoutingError Source # | |
Defined in Miso.Router | |
| Show RoutingError Source # | |
Defined in Miso.Router Methods showsPrec :: Int -> RoutingError -> ShowS # show :: RoutingError -> String # showList :: [RoutingError] -> ShowS # | |
Functions
parseURI :: MisoString -> Either MisoString URI Source #
URI parsing
prettyURI :: URI -> MisoString Source #
Pretty-prints a URI.
A # is added before a non-empty uriFragment. A leading # already
present in uriFragment is not repeated, so URI "foo" "#top" mempty
and URI "foo" "top" mempty both print as /foo#top.
prettyQueryString :: URI -> MisoString Source #
Pretty-prints a URI query string.
Parameters and flags share a single ? and are separated by &, in key
order: ?a=1&flag. Entries with an empty key are skipped, since they
can't be parsed back, and an empty query string prints as nothing.
Keys and values are printed exactly as stored; they are not
percent-encoded.
Manual Routing
Arguments
| :: MisoString | The raw URL string to parse |
| -> RouteParser route | Parser to apply against the tokenised URL |
| -> Either RoutingError route |
Smart constructor for building a RouteParser
data Route = Widget MisoString Int instance Router Route where routeParser = routes [ Widget <$> path "widget" <*> capture ] fromRoute (Widget path value) = [ toPath path, toCapture value ] router :: Router router => RouteParser router router = routes [ Widget <$> path "widget" <*> capture ] > Right (Widget "widget" 10)
routes :: [RouteParser route] -> RouteParser route Source #
Convenience for specifying multiple routes
Construction
Arguments
| :: ToMisoString s | |
| => MisoString | Query parameter key |
| -> s | Query parameter value |
| -> Token |
Smart constructor for building a QueryParamToken
toCapture :: ToMisoString string => string -> Token Source #
Smart constructor for building a capture variable
toPath :: MisoString -> Token Source #
Smart constructor for building a path fragment
Parser combinators
queryFlag :: forall (flag :: Symbol). KnownSymbol flag => RouteParser (QueryFlag flag) Source #
Query flag parser from a route
queryParam :: forall (param :: Symbol) a. (FromMisoString a, KnownSymbol param) => RouteParser (QueryParam param a) Source #
Query parameter parser from a route.
A missing key, a bare key (?page), or a value that doesn't parse as a
gives Nothing. An empty value (?page=) is passed to
fromMisoStringEither, so it gives for a Just ""MisoString
param and Nothing for an Int.
capture :: FromMisoString value => RouteParser value Source #
Combinator for parsing a capture variable out of a URI
path :: MisoString -> RouteParser MisoString Source #
Combinator for parsing a path out of a URI
fragment :: MisoString -> RouteParser MisoString Source #
Matches a literal URI fragment (the part after #), failing the parse if
it differs. Returns the matched fragment.
Lexing
lexTokens :: MisoString -> Either LexerError [Token] Source #
Lexes a URI into route Tokens, or reports where lexing failed.
tokensToURI :: [Token] -> URI Source #
Converts a list of [Token] into an actual URI.
uriFragment is stored without the leading #. The lexer produces at
most one FragmentToken, but fromRoute can produce several; those are
joined with #, so they print back as /foo#a#b.