on 85b4bf7 help sign in

master and 85b4bf7 have 2 merge bases. This is what 85b4bf7 changed since the first, 8b83d0e, as git diff master...85b4bf7 shows it.

17 files changed+450−84
since splitsnapshots
unifiedsplit
ChangeLog.md+2 default.nix+53−4 dep/reflex-platform/github.json+3−3 lib/backend/src/Obelisk/Backend.hs+7−12 lib/command/src/Obelisk/Command.hs+27−4 lib/executable-config/lookup/obelisk-executable-config-lookup.cabal+3 lib/executable-config/lookup/src-ghcjs/Obelisk/ExecutableConfig/Lookup.hs+1−1 lib/executable-config/lookup/src-other/Obelisk/Configs/Internal/Environment.hs+116 lib/executable-config/lookup/src-other/Obelisk/ExecutableConfig/Lookup.hs+10−2 lib/executable-config/lookup/src/Obelisk/Configs.hs+4 lib/frontend/src/Obelisk/Frontend.hs+6−5 lib/route/src/Obelisk/Route.hs+191−21 lib/run/src/Obelisk/Run.hs+11−12 skeleton/backend/src/Backend.hs+1−1 skeleton/common/src/Common/Route.hs+8−12 skeleton/default.nix+6−6 skeleton/frontend/src-bin/main.hs+1−1
ChangeLog.md+2
@@ -9,6 +9,8 @@ This project's release branch is `master`. This log is written from the perspect
99* The backend no longer runs in BackendConfigsT.
1010* Add tabulation package. See Data.Tabulation for details.
1111* Add encoders for `DMap`, `HasFields` (cf. Data.Tabulation), and JSON.
12+* Generalised pathSegmentEncoder, added pathFieldEncoder.
13+* Added some Prisms to the encoder library for manipulating DSums (perhaps they should get moved to dependent-sum before release?)
1214
1315## v0.1.1.0 - 2019-05-17
1416
default.nix+53−4
@@ -157,12 +157,16 @@ in rec {
157157 obelisk-asset-manifest-generate "$src" "$haskellManifest" ${packageName} ${moduleName} "$symlinked"
158158 '';
159159
160− compressedJs = frontend: optimizationLevel: pkgs.runCommand "compressedJs" { buildInputs = [ pkgs.closurecompiler ]; } ''
160+ compressedJs = frontend: optimizationLevel: pkgs.runCommand "compressedJs" {} ''
161161 mkdir $out
162162 cd $out
163163 ln -s "${haskellLib.justStaticExecutables frontend}/bin/frontend.jsexe/all.js" all.unminified.js
164− closure-compiler --externs "${reflex-platform.ghcjsExternsJs}" -O ${optimizationLevel} --jscomp_warning=checkVars --create_source_map="all.js.map" --source_map_format=V3 --js_output_file="all.js" all.unminified.js
165− echo "//# sourceMappingURL=all.js.map" >> all.js
164+ ${if optimizationLevel == null then ''
165+ ln -s all.unminified.js all.js
166+ '' else ''
167+ ${pkgs.closurecompiler}/bin/closure-compiler --externs "${reflex-platform.ghcjsExternsJs}" -O ${optimizationLevel} --jscomp_warning=checkVars --create_source_map="all.js.map" --source_map_format=V3 --js_output_file="all.js" all.unminified.js
168+ echo "//# sourceMappingURL=all.js.map" >> all.js
169+ ''}
166170 '';
167171
168172 serverModules = {
@@ -233,6 +237,49 @@ in rec {
233237 };
234238 };
235239
240+ dockerImage = args@{exe, name, version, extraContents ? [], extraPaths ? [], config ? {}}: let
241+ appDirSetupScript = nixpkgs.runCommand "appDirSetupScript.sh" {} ''
242+ mkdir -p $out/var/lib/backend
243+ ln -sft $out/var/lib/backend '${exe}'/*
244+ ${nixpkgs.findutils}/bin/find $out/var/lib/backend
245+
246+ # Needed for postgresql
247+ mkdir $out/bin
248+ ln -s ${nixpkgs.bash}/bin/sh $out/bin/sh
249+ mkdir $out/etc
250+ echo 'nobody:x:99:99:Nobody:/:/sbin/nologin' >> $out/etc/passwd
251+ echo 'nobody:*:17416:0:99999:7:::' >> $out/etc/shadow
252+ echo 'nobody:x:99:' >> $out/etc/group
253+ echo 'nobody:::' >> $out/etc/gshadow
254+
255+ # Allow us to use docker secrets as backend config files more easily.
256+ mkdir -p $out/var/lib/backend/config
257+ ln -s /run/secrets $out/var/lib/backend/config/backend
258+ '';
259+
260+ in nixpkgs.dockerTools.buildImage {
261+ name = name;
262+ tag = version;
263+ contents = [ nixpkgs.iana-etc nixpkgs.cacert appDirSetupScript ] ++ extraContents;
264+ keepContentsDirlinks = true;
265+ extraCommands = ''
266+ chmod 1777 var/lib/backend
267+ '';
268+ config = {
269+ Env = [
270+ ("PATH=" + builtins.concatStringsSep(":")(extraPaths ++ [
271+ "/var/lib/backend" # put the obelisk project on the path.
272+ "/bin" # put contents on path
273+ ] ++ map (pkg: "${pkg}/bin") pkgs.stdenv.initialPath # put common tools in path so docker exec is useful
274+ ))
275+ ];
276+ Expose = 8000;
277+ Entrypoint = ["/var/lib/backend/backend"];
278+ WorkingDir = "/var/lib/backend";
279+ User = "99:99";
280+ } // config;
281+ };
282+
236283 serverExe = backend: frontend: assets: optimizationLevel: version:
237284 pkgs.runCommand "serverExe" {} ''
238285 mkdir $out
@@ -268,7 +315,7 @@ in rec {
268315 , tools ? _: []
269316 , shellToolOverrides ? _: _: {}
270317 , withHoogle ? false # Setting this to `true` makes shell reloading far slower
271− , __closureCompilerOptimizationLevel ? "ADVANCED"
318+ , __closureCompilerOptimizationLevel ? "ADVANCED" # Set this to `null` to skip the closure-compiler step
272319 }:
273320 let frontendName = "frontend";
274321 backendName = "backend";
@@ -349,6 +396,8 @@ in rec {
349396 exe = serverOn system dummyVersion;
350397 server = args@{ hostName, adminEmail, routeHost, enableHttps, version }:
351398 server (args // { exe = linuxExe version; });
399+ dockerImage = args@{ name, version, ... }:
400+ dockerImage ({ exe = linuxExe version; } // args);
352401 obelisk = import (base + "/.obelisk/impl") {};
353402 };
354403 haskellPackageSets = {
dep/reflex-platform/github.json+3−3
@@ -1,7 +1,7 @@
11{
22 "owner": "reflex-frp",
33 "repo": "reflex-platform",
4− "branch": "cg-bump-reflex-dom",
5− "rev": "7ecb4c255bb053a7a4796ffd6331cdbe210de91c",
6− "sha256": "1mngxa24cfpvxxq4hgh77nw36vny4abl5wi2xmlwpkk25wzm0h0x"
4+ "branch": "mb-fix-jsaddle-with-ledgerjs",
5+ "rev": "1d8a445918d2dc04dc93f3aad91a32178d73ade6",
6+ "sha256": "0h19dkd4vxfjlfwpxvvy6kapgzd4vvwkwgv79bnqadq8k6f14ic7"
77}
lib/backend/src/Obelisk/Backend.hs+7−12
@@ -31,7 +31,6 @@ import Data.ByteString (ByteString)
3131import qualified Data.ByteString.Char8 as BSC8
3232import Data.Default (Default (..))
3333import Data.Dependent.Sum
34−import Data.Functor.Sum
3534import Data.Functor.Identity
3635import Data.Map (Map)
3736import qualified Data.Map as Map
@@ -52,7 +51,7 @@ import Snap.Internal.Http.Server.Config (Config (accessLog, errorLog), ConfigLog
5251import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
5352
5453data Backend backendRoute frontendRoute = Backend
55− { _backend_routeEncoder :: Encoder (Either Text) Identity (R (Sum backendRoute (ObeliskRoute frontendRoute))) PageName
54+ { _backend_routeEncoder :: Encoder (Either Text) Identity (R (FullRoute backendRoute frontendRoute)) PageName
5655 , _backend_run :: ((R backendRoute -> Snap ()) -> IO ()) -> IO ()
5756 }
5857
@@ -114,11 +113,7 @@ getPageName :: (MonadSnap m) => m PageName
114113getPageName = do
115114 p <- getsRequest rqPathInfo
116115 q <- getsRequest rqQueryString
117− let pageNameEncoder' :: Encoder Identity Identity PageName (String, String)
118− pageNameEncoder' = bimap
119− (unpackTextEncoder . pathSegmentsTextEncoder . listToNonEmptyEncoder)
120− (unpackTextEncoder . queryParametersTextEncoder . toListMapEncoder)
121− return $ decode pageNameEncoder' (T.unpack (decodeUtf8 p), T.unpack (decodeUtf8 q))
116+ return $ byteStringsToPageName p q
122117
123118getRouteWith :: (MonadSnap m) => Encoder Identity parse route PageName -> m (parse route)
124119getRouteWith e = do
@@ -174,7 +169,7 @@ serveGhcjsApp urlEnc app config = \case
174169 writeBS <=< renderGhcjsFrontend urlEnc (appRouteComponent :/ appRouteRest) config $ _ghcjsApp_value app
175170 GhcjsAppRoute_Resource :=> Identity pathSegments -> serveStaticAssets (_ghcjsApp_compiled app) pathSegments
176171
177−runBackend :: Backend fullRoute frontendRoute -> Frontend (R frontendRoute) -> IO ()
172+runBackend :: Backend backendRoute frontendRoute -> Frontend (R frontendRoute) -> IO ()
178173runBackend backend frontend = case checkEncoder $ _backend_routeEncoder backend of
179174 Left e -> fail $ "backend error:\n" <> T.unpack e
180175 Right validFullEncoder -> do
@@ -183,15 +178,15 @@ runBackend backend frontend = case checkEncoder $ _backend_routeEncoder backend
183178 runSnapWithCommandLineArgs $ do
184179 getRouteWith validFullEncoder >>= \case
185180 Identity r -> case r of
186− InL backendRoute :=> Identity a -> serveRoute $ backendRoute :/ a
187− InR obeliskRoute :=> Identity a ->
181+ FullRoute_Backend backendRoute :/ a -> serveRoute $ backendRoute :/ a
182+ FullRoute_Frontend obeliskRoute :/ a ->
188183 serveDefaultObeliskApp (mkRouteToUrl validFullEncoder) (serveStaticAssets defaultStaticAssets) frontend publicConfigs $
189184 obeliskRoute :/ a
190185
191−mkRouteToUrl :: Encoder Identity parse (R (Sum f (ObeliskRoute r))) PageName -> R r -> Text
186+mkRouteToUrl :: Encoder Identity parse (R (FullRoute br fr)) PageName -> R fr -> Text
192187mkRouteToUrl validFullEncoder =
193188 let pageNameEncoder' :: Encoder Identity (Either Text) PageName PathQuery = pageNameEncoder
194− in \(k :/ v) -> T.pack . uncurry (<>) . encode pageNameEncoder' . encode validFullEncoder $ (InR $ ObeliskRoute_App k) :/ v
189+ in \(k :/ v) -> T.pack . uncurry (<>) . encode pageNameEncoder' . encode validFullEncoder $ (FullRoute_Frontend $ ObeliskRoute_App k) :/ v
195190
196191renderGhcjsFrontend
197192 :: (MonadSnap m, HasCookies m)
lib/command/src/Obelisk/Command.hs+27−4
@@ -20,6 +20,7 @@ import Data.Text.Encoding
2020import Data.Text.Encoding.Error (lenientDecode)
2121import GHC.StaticPtr
2222import Options.Applicative
23+import Options.Applicative.Help.Pretty (text, (<$$>))
2324import System.Directory
2425import System.Environment
2526import System.FilePath
@@ -93,6 +94,7 @@ data ObCommand
9394 | ObCommand_Repl
9495 | ObCommand_Watch
9596 | ObCommand_Shell ShellOpts
97+ | ObCommand_Doc String [String] -- shell and list of packages
9698 | ObCommand_Internal ObInternal
9799 deriving Show
98100
@@ -129,6 +131,11 @@ obCommand cfg = hsubparser
129131 , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter"
130132 , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings"
131133 , command "shell" $ info (ObCommand_Shell <$> shellOpts) $ progDesc "Enter a shell with project dependencies"
134+ , command "doc" $ info (ObCommand_Doc <$> shellFlags <*> packageNames) $
135+ progDesc "List paths to haddock documentation for specified packages"
136+ <> footerDoc (Just $
137+ text "Hint: To open the documentation you can pipe the output of this command like"
138+ <$$> text "ob doc reflex reflex-dom-core | xargs -n1 xdg-open")
132139 ])
133140 <|> subparser
134141 (mconcat
@@ -136,6 +143,9 @@ obCommand cfg = hsubparser
136143 , command "internal" (info (ObCommand_Internal <$> internalCommand) mempty)
137144 ])
138145
146+packageNames :: Parser [String]
147+packageNames = some (strArgument (metavar "PACKAGE-NAME..."))
148+
139149deployCommand :: ArgsConfig -> Parser DeployCommand
140150deployCommand cfg = hsubparser $ mconcat
141151 [ command "init" $ info (DeployCommand_Init <$> deployInitOpts) $ progDesc "Initialize a deployment configuration directory"
@@ -233,12 +243,15 @@ data ShellOpts
233243 }
234244 deriving Show
235245
246+shellFlags :: Parser String
247+shellFlags =
248+ flag' "ghc" (long "ghc" <> help "Enter a shell environment having ghc (default)")
249+ <|> flag "ghc" "ghcjs" (long "ghcjs" <> help "Enter a shell having ghcjs rather than ghc")
250+ <|> strOption (short 'A' <> long "argument" <> metavar "NIXARG" <> help "Use the environment specified by the given nix argument of `shells'")
251+
236252shellOpts :: Parser ShellOpts
237253shellOpts = ShellOpts
238− <$> ( flag' "ghc" (long "ghc" <> help "Enter a shell environment having ghc (default)")
239− <|> flag "ghc" "ghcjs" (long "ghcjs" <> help "Enter a shell having ghcjs rather than ghc")
240− <|> strOption (short 'A' <> long "argument" <> metavar "NIXARG" <> help "Use the environment specified by the given nix argument of `shells'")
241− )
254+ <$> shellFlags
242255 <*> optional (strArgument (metavar "COMMAND"))
243256
244257parserPrefs :: ParserPrefs
@@ -369,6 +382,8 @@ ob = \case
369382 ObCommand_Watch -> inNixShell' $ static runWatch
370383 ObCommand_Shell so -> withProjectRoot "." $ \root ->
371384 projectShell root False (_shellOpts_shell so) (_shellOpts_command so)
385+ ObCommand_Doc shell pkgs -> withProjectRoot "." $ \root ->
386+ projectShell root False shell (Just $ haddockCommand pkgs)
372387 ObCommand_Internal icmd -> case icmd of
373388 ObInternal_RunStaticIO k -> liftIO (unsafeLookupStaticPtr @(ObeliskT IO ()) k) >>= \case
374389 Nothing -> failWith $ "ObInternal_RunStaticIO: no such StaticKey: " <> T.pack (show k)
@@ -377,6 +392,14 @@ ob = \case
377392 liftIO $ runObelisk c $ deRefStaticPtr p
378393 ObInternal_CLIDemo -> cliDemo
379394
395+haddockCommand :: [String] -> String
396+haddockCommand pkgs = unwords
397+ [ "for p in"
398+ , unwords [getHaddockPath p ++ "/index.html" | p <- pkgs]
399+ , "; do echo $p; done"
400+ ]
401+ where getHaddockPath p = "$(ghc-pkg field " ++ p ++ " haddock-html --simple-output)"
402+
380403--TODO: Clean up all the magic strings throughout this codebase
381404
382405getArgsConfig :: IO ArgsConfig
lib/executable-config/lookup/obelisk-executable-config-lookup.cabal+3
@@ -24,7 +24,10 @@ library
2424 else
2525 hs-source-dirs: src-other, src
2626 other-modules: Obelisk.Configs.Internal.Directory
27+ , Obelisk.Configs.Internal.Environment
2728 build-depends: directory
29+ , attoparsec
30+ , witherable
2831
2932 build-depends:
3033 base,
lib/executable-config/lookup/src-ghcjs/Obelisk/ExecutableConfig/Lookup.hs+1−1
@@ -35,7 +35,7 @@ getConfigs = do
3535 where
3636 collToList es = do
3737 len <- getLength es
38− list <- traverse (item es) [0..len-1]
38+ list <- traverse (item es) $ if len == 0 then [] else [0..len-1]
3939 pure $ catMaybes list
4040 decodeOrFail x = case B64.decode (T.encodeUtf8 x) of
4141 Left e -> error ("Obelisk.ExecutableConfig.Lookup.getConfigs: error when decoding base64: " ++ e)
lib/executable-config/lookup/src-other/Obelisk/Configs/Internal/Environment.hsnew file+116
@@ -0,0 +1,116 @@
1+{-# LANGUAGE TypeApplications #-}
2+{-# LANGUAGE PatternGuards #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+
5+module Obelisk.Configs.Internal.Environment where
6+
7+import Control.Applicative
8+import Control.Monad
9+import qualified Data.Attoparsec.ByteString as P
10+import qualified Data.Attoparsec.ByteString.Char8 as P (hexadecimal)
11+import qualified Data.Attoparsec.Combinator as P
12+import Data.Bifunctor (second)
13+import Data.ByteString (ByteString)
14+import qualified Data.ByteString as BS
15+import qualified Data.ByteString.Base64 as Base64
16+import qualified Data.ByteString.Builder as Builder
17+import qualified Data.ByteString.Char8 as Char8
18+import qualified Data.ByteString.Lazy as LBS
19+import qualified Data.Char as Char
20+import Data.Map (Map)
21+import qualified Data.Map as Map
22+import Data.Text (Text)
23+import qualified Data.Text as T
24+import qualified Data.Text.Lazy as LT
25+import Data.Tuple (swap)
26+import Data.Witherable
27+import Data.Word
28+import System.Environment
29+import qualified Text.Printf as Printf
30+
31+
32+data EnvCodec
33+ = EnvCodec_Char8
34+ | EnvCodec_Base64
35+ deriving (Eq, Ord, Show)
36+
37+envVarEscapes :: [(Char, Word8)]
38+envVarEscapes = fmap (second f)
39+ [ ('-', 'h')
40+ , ('_', 'u')
41+ , ('.', 'd')
42+ ]
43+ where
44+ f :: Char -> Word8
45+ f = fromIntegral . Char.ord
46+
47+escapeVarName :: EnvCodec -> Text -> ByteString
48+escapeVarName codec x = LBS.toStrict . Builder.toLazyByteString $ T.foldr ((<>) . f) mempty x <> codec'
49+ where
50+ escapeCode = Map.fromList envVarEscapes
51+
52+ codec' = case codec of
53+ EnvCodec_Base64 -> "__base64"
54+ EnvCodec_Char8 -> ""
55+
56+ f :: Char -> Builder.Builder
57+ f c
58+ | not (Char.isAscii c) = error "bad"
59+ | c == '/' = "_"
60+ | Char.isAlphaNum c = Builder.char7 c
61+ | Just c' <- Map.lookup c escapeCode = "__" <> Builder.word8 c'
62+ | otherwise = "__x" <> (Builder.string7 $ Printf.formatInt (Char.ord c) (Printf.FieldFormat (Just 2) Nothing (Just Printf.ZeroPad) Nothing False "" 'x') "")
63+
64+unescapeVarName :: ByteString -> Either String (Text, EnvCodec)
65+unescapeVarName x = P.parseOnly ((,) <$> fileName <*> codec <* P.endOfInput) x
66+ where
67+ escapeCode = Map.fromList $ fmap swap envVarEscapes
68+
69+ codec :: P.Parser EnvCodec
70+ codec = P.option EnvCodec_Char8 $ (EnvCodec_Base64 <$ "__base64")
71+
72+ -- | parse ps. succeed only if both ps and qs succeeds
73+ guardLookahead ps qs = do
74+ p <- ps
75+ _ <- P.lookAhead qs
76+ return p
77+
78+ parseEscapeCode =
79+ ("__" *> (do
80+ c <- P.anyWord8
81+ case Map.lookup c escapeCode of
82+ Nothing -> mzero
83+ Just c' -> return c')
84+ ) <|> ( "__x" *> (Char.chr . fromIntegral <$> P.hexadecimal @Word8))
85+
86+ parseEnvChar = Char.chr . fromIntegral <$> P.satisfy (P.inClass "a-zA-Z0-9")
87+
88+ fileName = LT.toStrict . LT.pack <$> P.many' (P.choice
89+ [ parseEnvChar
90+ , parseEscapeCode
91+ , guardLookahead ('/' <$ "_") (parseEnvChar <|> parseEscapeCode)
92+ ])
93+
94+
95+getConfigsFromEnvironment :: ByteString -> Map Text ByteString -> IO (Map Text ByteString)
96+getConfigsFromEnvironment varNamePrefix dirConfigs = do
97+ let
98+ prefix needle haystack
99+ | needle `BS.isPrefixOf` haystack = Just (BS.drop (BS.length needle) haystack)
100+ | otherwise = Nothing
101+
102+ makeEnv (k0, v0) = case prefix varNamePrefix (Char8.pack k0) of
103+ Nothing -> return Nothing
104+ Just k -> case unescapeVarName k of
105+ Left bad -> error bad
106+ Right (k', codec) ->
107+ let
108+ v = case codec of
109+ EnvCodec_Char8 -> Char8.pack v0
110+ EnvCodec_Base64 -> Base64.decodeLenient (Char8.pack v0)
111+ in case Map.lookup k' dirConfigs of
112+ Just v' | v /= v' -> error $ "conflicting obelisk config: env " <> k0 <> " /= ./config/" <> (T.unpack k')
113+ _ -> return (Just (k', v))
114+
115+ env <- wither makeEnv =<< getEnvironment
116+ return $ (Map.fromList env) <> dirConfigs
lib/executable-config/lookup/src-other/Obelisk/ExecutableConfig/Lookup.hs+10−2
@@ -1,6 +1,12 @@
11{-# LANGUAGE LambdaCase #-}
22{-# LANGUAGE OverloadedStrings #-}
3−module Obelisk.ExecutableConfig.Lookup where
3+module Obelisk.ExecutableConfig.Lookup
4+ ( getConfigs
5+ , escapeVarName
6+ , EnvCodec(..)
7+ , unescapeVarName
8+ )
9+where
410
511import Data.Map (Map)
612import qualified Data.Map as Map
@@ -14,5 +20,7 @@ import Control.Monad
1420
1521import Obelisk.Configs.Internal.Directory
1622
23+import Obelisk.Configs.Internal.Environment
24+
1725getConfigs :: IO (Map Text ByteString)
18−getConfigs = getConfigsFromDirectory "config"
26+getConfigs = getConfigsFromDirectory "config" >>= getConfigsFromEnvironment "OBELISK_CONFIG_"
lib/executable-config/lookup/src/Obelisk/Configs.hs+4
@@ -31,6 +31,7 @@ import Data.ByteString (ByteString)
3131import Data.Map (Map)
3232import qualified Data.Map as Map
3333import Data.Text (Text)
34+import qualified Data.Text.Encoding as T
3435import Reflex
3536import Reflex.Host.Class
3637import Reflex.Dom
@@ -50,6 +51,9 @@ class Monad m => HasConfigs m where
5051instance Monad m => HasConfigs (ConfigsT m) where
5152 getConfigs = ConfigsT ask
5253
54+getTextConfig :: HasConfigs m => Text -> m (Maybe Text)
55+getTextConfig k = fmap T.decodeUtf8 <$> getConfig k
56+
5357instance HasConfigs m => HasConfigs (BehaviorWriterT t w m)
5458instance HasConfigs m => HasConfigs (DynamicWriterT t w m)
5559instance HasConfigs m => HasConfigs (EventWriterT t w m)
lib/frontend/src/Obelisk/Frontend.hs+6−5
@@ -83,6 +83,7 @@ type ObeliskWidget js t route m =
8383 , PrebuildAgnostic t route (Client m)
8484 , HasConfigs m
8585 , HasCookies m
86+ , MonadIO (Performable m)
8687 )
8788
8889type PrebuildAgnostic t route m =
@@ -137,7 +138,7 @@ data FrontendMode = FrontendMode
137138-- Selects FrontendMode based on platform; this doesn't work for jsaddle-warp
138139runFrontend
139140 :: forall backendRoute route
140− . Encoder Identity Identity (R (Sum backendRoute (ObeliskRoute route))) PageName
141+ . Encoder Identity Identity (R (FullRoute backendRoute route)) PageName
141142 -> Frontend (R route)
142143 -> JSM ()
143144runFrontend validFullEncoder frontend = do
@@ -165,14 +166,14 @@ runFrontend validFullEncoder frontend = do
165166 runFrontendWithConfigsAndCurrentRoute mode configs validFullEncoder frontend
166167
167168runFrontendWithConfigsAndCurrentRoute
168− :: forall backendRoute route
169+ :: forall backendRoute frontendRoute
169170 . FrontendMode
170171 -> Map Text ByteString
171− -> Encoder Identity Identity (R (Sum backendRoute (ObeliskRoute route))) PageName
172− -> Frontend (R route)
172+ -> Encoder Identity Identity (R (FullRoute backendRoute frontendRoute)) PageName
173+ -> Frontend (R frontendRoute)
173174 -> JSM ()
174175runFrontendWithConfigsAndCurrentRoute mode configs validFullEncoder frontend = do
175− let ve = validFullEncoder . hoistParse errorLeft (prismEncoder (rPrism $ _InR . _ObeliskRoute_App))
176+ let ve = validFullEncoder . hoistParse errorLeft (prismEncoder (rPrism $ _FullRoute_Frontend . _ObeliskRoute_App))
176177 errorLeft = \case
177178 Left _ -> error "runFrontend: Unexpected non-app ObeliskRoute reached the frontend. This shouldn't happen."
178179 Right x -> Identity x
lib/route/src/Obelisk/Route.hs+191−21
@@ -12,6 +12,7 @@
1212{-# LANGUAGE PatternSynonyms #-}
1313{-# LANGUAGE RankNTypes #-}
1414{-# LANGUAGE ScopedTypeVariables #-}
15+{-# LANGUAGE StandaloneDeriving #-}
1516{-# LANGUAGE TemplateHaskell #-}
1617{-# LANGUAGE TypeApplications #-}
1718{-# LANGUAGE TypeFamilies #-}
@@ -62,10 +63,15 @@ module Obelisk.Route
6263 , shadowEncoder
6364 , prismEncoder
6465 , rPrism
66+ , _R
6567 , obeliskRouteEncoder
6668 , obeliskRouteSegment
6769 , pageNameEncoder
6870 , handleEncoder
71+ , FullRoute (..)
72+ , _FullRoute_Frontend
73+ , _FullRoute_Backend
74+ , mkFullRouteEncoder
6975 , ObeliskRoute (..)
7076 , _ObeliskRoute_App
7177 , _ObeliskRoute_Resource
@@ -90,7 +96,9 @@ module Obelisk.Route
9096 , Decoder(..)
9197 , dmapEncoder
9298 , fieldMapEncoder
99+ , pathFieldEncoder
93100 , jsonEncoder
101+ , byteStringsToPageName
94102 ) where
95103
96104import Prelude hiding ((.), id)
@@ -101,8 +109,31 @@ import qualified Control.Categorical.Functor as Cat
101109import Control.Categorical.Bifunctor
102110import Control.Category.Associative
103111import Control.Category.Monoidal
104−import Control.Lens (Identity (..), Prism', makePrisms, itraverse, imap, prism, (^.), re, matching, (^?), _Just, _Nothing, Iso', from, view, Wrapped (..))
112+import Control.Category.Braided
113+import Control.Lens
114+ ( Identity (..)
115+ , (^.)
116+ , (^?)
117+ , _Just
118+ , _Nothing
119+ , Cons(..)
120+ , from
121+ , imap
122+ , iso
123+ , Iso'
124+ , itraverse
125+ , makePrisms
126+ , Prism'
127+ , prism'
128+ , re
129+ , view
130+ , Wrapped (..)
131+ )
105132import Control.Monad.Except
133+import Control.Monad.Writer (execWriter, tell)
134+import qualified Control.Monad.State.Strict as State
135+import Control.Monad.Trans (lift)
136+import qualified Data.ByteString as BS
106137import qualified Data.ByteString.Lazy as BSL
107138import Data.Dependent.Sum (DSum (..))
108139import Data.Dependent.Map (DMap)
@@ -252,6 +283,10 @@ instance Monad parse => Bifunctor (,) (EncoderImpl parse) (EncoderImpl parse) (E
252283 , _encoderImpl_decode = \(a, b) -> liftA2 (,) (_encoderImpl_decode f a) (_encoderImpl_decode g b)
253284 }
254285
286+instance (Monad parse, Applicative check) => Braided (Encoder check parse) (,) where
287+ braid = isoEncoder (iso swap swap)
288+
289+
255290instance (Applicative check, Monad parse) => PFunctor (,) (Encoder check parse) (Encoder check parse) where
256291 first f = bimap f id
257292instance (Applicative check, Monad parse) => QFunctor (,) (Encoder check parse) (Encoder check parse) where
@@ -265,6 +300,34 @@ instance (Traversable f, Monad parse) => Cat.Functor f (EncoderImpl parse) (Enco
265300 , _encoderImpl_decode = traverse $ _encoderImpl_decode ve
266301 }
267302
303+instance Monad parse => PFunctor Either (EncoderImpl parse) (EncoderImpl parse) where
304+ first f = bimap f id
305+instance Monad parse => QFunctor Either (EncoderImpl parse) (EncoderImpl parse) where
306+ second g = bimap id g
307+instance Monad parse => Bifunctor Either (EncoderImpl parse) (EncoderImpl parse) (EncoderImpl parse) where
308+ bimap f g = EncoderImpl
309+ { _encoderImpl_encode = bimap (_encoderImpl_encode f) (_encoderImpl_encode g)
310+ , _encoderImpl_decode = \case
311+ Left a -> Left <$> _encoderImpl_decode f a
312+ Right b -> Right <$> _encoderImpl_decode g b
313+ }
314+
315+instance (Monad parse, Applicative check) => QFunctor Either (Encoder check parse) (Encoder check parse) where
316+ second g = bimap id g
317+instance (Monad parse, Applicative check) => PFunctor Either (Encoder check parse) (Encoder check parse) where
318+ first f = bimap f id
319+instance (Monad parse, Applicative check) => Bifunctor Either (Encoder check parse) (Encoder check parse) (Encoder check parse) where
320+ bimap f g = Encoder $ liftA2 bimap (unEncoder f) (unEncoder g)
321+
322+instance (Applicative check, Monad parse) => Associative (Encoder check parse) Either where
323+ associate = isoEncoder (iso (associate @(->) @Either) disassociate)
324+ disassociate = isoEncoder (iso disassociate associate)
325+
326+instance (Monad parse, Applicative check) => Braided (Encoder check parse) Either where
327+ braid = isoEncoder (iso swap swap)
328+
329+
330+
268331instance (Traversable f, Monad check, Monad parse) => Cat.Functor f (Encoder check parse) (Encoder check parse) where
269332 fmap e = Encoder $ do
270333 ve <- unEncoder e
@@ -663,10 +726,47 @@ joinPairTextEncoder = Encoder . \case
663726 _ -> return (kt, T.drop (T.length separator) vt)
664727 }
665728
666−--TODO: Rewrite this by composing the given prism with a lens on the first element of the DSum
667−-- Or, more likely, the user can compose it themselves
668−rPrism :: forall f g. (forall a. Prism' (f a) (g a)) -> Prism' (R f) (R g)
669−rPrism p = prism (\(g :/ x) -> g ^. re p :/ x) (\(f :/ x) -> bimap (:/ x) (:/ x) $ matching p f)
729+-- This slight generalization of 'rPrism' happens to be enough to write all our
730+-- prism combinators so far.
731+dSumPrism
732+ :: forall f f' g
733+ . (forall a. Prism' (f a) (f' a))
734+ -> Prism' (DSum f g) (DSum f' g)
735+dSumPrism p = prism'
736+ (\(f' :=> x) -> f' ^. re p :=> x)
737+ (\(f :=> x) -> (:=> x) <$> (f ^? p))
738+
739+-- already in obelisk
740+rPrism
741+ :: forall f f'
742+ . (forall a. Prism' (f a) (f' a))
743+ -> Prism' (R f) (R f')
744+rPrism p = dSumPrism p
745+
746+dSumPrism'
747+ :: forall f g a
748+ . (forall b. Prism' (f b) (a :~: b))
749+ -> Prism' (DSum f g) (g a)
750+dSumPrism' p = dSumPrism p . iso (\(Refl :=> b) -> b) (Refl :=>)
751+
752+dSumGEqPrism
753+ :: GEq f
754+ => f a
755+ -> Prism' (DSum f g) (g a)
756+dSumGEqPrism variant = dSumPrism' $ prism' (\Refl -> variant) (\x -> geq variant x)
757+
758+-- | Given a 'tag :: f a', make a prism for 'R f'. This generalizes the usual
759+-- prisms for a sum type (the ones that 'mkPrisms' would make), just as 'R'
760+-- generalized a usual sum type.
761+--
762+-- [This is given the '_R' name of the "cannonical" prism not because it is the
763+-- most general, but because it seems the most useful for routes, and 'R' itself
764+-- trades generality for route-specificity.]
765+_R
766+ :: GEq f
767+ => f a
768+ -> Prism' (R f) a
769+_R variant = dSumGEqPrism variant . iso runIdentity Identity
670770
671771-- | An encoder that only works on the items available via the prism. An error will be thrown in the parse monad
672772-- if the prism doesn't match.
@@ -710,6 +810,45 @@ handleEncoder recover e = Encoder $ do
710810-- Actual obelisk route info
711811--------------------------------------------------------------------------------
712812
813+-- | The typical full route type comprising all of an Obelisk application's routes.
814+-- Parameterised by the top level GADTs that define backend and frontend routes, respectively.
815+data FullRoute :: (* -> *) -> (* -> *) -> * -> * where
816+ FullRoute_Backend :: br a -> FullRoute br fr a
817+ FullRoute_Frontend :: ObeliskRoute fr a -> FullRoute br fr a
818+
819+instance (GShow br, GShow fr) => GShow (FullRoute br fr) where
820+ gshowsPrec p = \case
821+ FullRoute_Backend x -> showParen (p > 10) (showString "FullRoute_Backend " . gshowsPrec 11 x)
822+ FullRoute_Frontend x -> showParen (p > 10) (showString "FullRoute_Frontend " . gshowsPrec 11 x)
823+
824+instance (GEq br, GEq fr) => GEq (FullRoute br fr) where
825+ geq (FullRoute_Backend x) (FullRoute_Backend y) = geq x y
826+ geq (FullRoute_Frontend x) (FullRoute_Frontend y) = geq x y
827+ geq _ _ = Nothing
828+
829+instance (GCompare br, GCompare fr) => GCompare (FullRoute br fr) where
830+ gcompare (FullRoute_Backend _) (FullRoute_Frontend _) = GLT
831+ gcompare (FullRoute_Frontend _) (FullRoute_Backend _) = GGT
832+ gcompare (FullRoute_Backend x) (FullRoute_Backend y) = gcompare x y
833+ gcompare (FullRoute_Frontend x) (FullRoute_Frontend y) = gcompare x y
834+
835+instance (UniverseSome br, UniverseSome fr) => UniverseSome (FullRoute br fr) where
836+ universeSome = [Some (FullRoute_Backend x) | Some x <- universeSome]
837+ ++ [Some (FullRoute_Frontend x) | Some x <- universeSome]
838+
839+-- | Build the typical top level application route encoder from a route for handling 404's,
840+-- and segment encoders for backend and frontend routes.
841+mkFullRouteEncoder
842+ :: (GCompare br, GCompare fr, GShow br, GShow fr, UniverseSome br, UniverseSome fr)
843+ => R (FullRoute br fr) -- ^ 404 handler
844+ -> (forall a. br a -> SegmentResult (Either Text) (Either Text) a) -- ^ How to encode a single backend route segment
845+ -> (forall a. fr a -> SegmentResult (Either Text) (Either Text) a) -- ^ How to encode a single frontend route segment
846+ -> Encoder (Either Text) Identity (R (FullRoute br fr)) PageName
847+mkFullRouteEncoder missing backendSegment frontendSegment = handleEncoder (const missing) $
848+ pathComponentEncoder $ \case
849+ FullRoute_Backend backendRoute -> backendSegment backendRoute
850+ FullRoute_Frontend obeliskRoute -> obeliskRouteSegment obeliskRoute frontendSegment
851+
713852-- | A type which can represent Obelisk-specific resource routes, in addition to application specific routes which serve your
714853-- frontend.
715854data ObeliskRoute :: (* -> *) -> * -> * where
@@ -839,7 +978,7 @@ concat <$> mapM deriveRouteComponent
839978 ]
840979
841980makePrisms ''ObeliskRoute
842−
981+makePrisms ''FullRoute
843982deriveGEq ''Void1
844983deriveGCompare ''Void1
845984
@@ -847,27 +986,27 @@ deriveGCompare ''Void1
847986-- and query string). See 'checkEncoder' for how to produce a checked encoder.
848987renderBackendRoute
849988 :: forall br a.
850− Encoder Identity Identity (R (Sum br a)) PageName
989+ Encoder Identity Identity (R (FullRoute br a)) PageName
851990 -> R br
852991 -> Text
853−renderBackendRoute enc = renderObeliskRoute enc . hoistR InL
992+renderBackendRoute enc = renderObeliskRoute enc . hoistR FullRoute_Backend
854993
855994-- | Renders a frontend route with the supplied checked encoder
856995renderFrontendRoute
857996 :: forall a fr.
858− Encoder Identity Identity (R (Sum a (ObeliskRoute fr))) PageName
997+ Encoder Identity Identity (R (FullRoute a fr)) PageName
859998 -> R fr
860999 -> Text
861−renderFrontendRoute enc = renderObeliskRoute enc . hoistR (InR . ObeliskRoute_App)
1000+renderFrontendRoute enc = renderObeliskRoute enc . hoistR (FullRoute_Frontend . ObeliskRoute_App)
8621001
8631002-- | Renders a route of the form typically found in an Obelisk project
8641003renderObeliskRoute
8651004 :: forall a b.
866− Encoder Identity Identity (R (Sum a b)) PageName
867− -> R (Sum a b)
1005+ Encoder Identity Identity (R (FullRoute a b)) PageName
1006+ -> R (FullRoute a b)
8681007 -> Text
8691008renderObeliskRoute e r =
870− let enc :: Encoder Identity (Either Text) (R (Sum a b)) PathQuery
1009+ let enc :: Encoder Identity (Either Text) (R (FullRoute a b)) PathQuery
8711010 enc = (pageNameEncoder . hoistParse (pure . runIdentity) e)
8721011 in (T.pack . uncurry (<>)) $ encode enc r
8731012
@@ -877,14 +1016,9 @@ readShowEncoder = singlePathSegmentEncoder . unsafeTshowEncoder
8771016integralEncoder :: (MonadError Text parse, Applicative check, Integral a) => Encoder check parse a Integer
8781017integralEncoder = prismEncoder (Numeric.Lens.integral)
8791018
880−pathSegmentEncoder :: (MonadError Text parse, Applicative check) =>
881− Encoder check parse (Text, PageName) PageName
882−pathSegmentEncoder = unsafeMkEncoder EncoderImpl
883− { _encoderImpl_encode = \(x, (y, z)) -> (x:y, z)
884− , _encoderImpl_decode = \(xss, y) -> case xss of
885− [] -> throwError "not enough path segments"
886− (x:xs) -> pure (x, (xs, y))
887− }
1019+pathSegmentEncoder :: (MonadError Text parse, Applicative check, Cons as as a a) =>
1020+ Encoder check parse (a, (as, b)) (as, b)
1021+pathSegmentEncoder = first (prismEncoder _Cons) . disassociate
8881022
8891023newtype Decoder check parse b a = Decoder { toEncoder :: Encoder check parse a b }
8901024
@@ -938,6 +1072,32 @@ fieldMapEncoder = unsafeEncoder $ do
9381072 Just (Identity v) -> return v
9391073 }
9401074
1075+-- this is in base 4.12 (GHC 8.6);
1076+newtype Ap f a = Ap {getAp :: f a}
1077+
1078+instance (Applicative f, Semigroup a) => Semigroup (Ap f a) where
1079+ Ap x <> Ap y = Ap (liftA2 (<>) x y)
1080+
1081+instance (Applicative f, Monoid a) => Monoid (Ap f a) where
1082+ mappend = (<>)
1083+ mempty = Ap (pure mempty)
1084+
1085+pathFieldEncoder :: forall a p check parse . (HasFields a, Monad check, MonadError Text parse, GCompare (Field a)) => (forall x. Field a x -> Encoder check parse x p) -> Encoder check parse (a, [p]) [p]
1086+pathFieldEncoder fieldEncoder = unsafeEncoder $ do
1087+ fieldEncoderPureMap :: DMap.DMap (Field a) (Decoder Identity parse p) <- getAp $ getConst $ tabulateFieldsA @a $ \f -> Const (Ap $ fmap (DMap.singleton f . Decoder) $ (checkEncoder @Identity) $ fieldEncoder f)
1088+ let fieldEncoderPure :: forall x. Field a x -> Encoder Identity parse x p
1089+ fieldEncoderPure f = toEncoder (DMap.findWithDefault (error "bad") f fieldEncoderPureMap)
1090+ pure $ EncoderImpl
1091+ { _encoderImpl_encode = \(x, rest) -> execWriter $ do
1092+ _ <- traverseWithField (\f x_i -> tell (pure $ encode (fieldEncoderPure f) x_i) *> pure x_i) x
1093+ tell rest
1094+ , _encoderImpl_decode = State.runStateT $ tabulateFieldsA $ \f -> State.get >>= \case
1095+ [] -> throwError $ T.pack "not enough path components"
1096+ p:ps -> do
1097+ State.put ps
1098+ lift $ tryDecode (fieldEncoderPure f) p
1099+ }
1100+
9411101-- | Use ToJSON/FromJSON to encode to Text. The correctness of this encoder is dependent on the encoding being injective and round-tripping correctly.
9421102jsonEncoder :: forall check parse r.
9431103 ( ToJSON r
@@ -954,4 +1114,14 @@ jsonEncoder = unsafeEncoder $ do
9541114 Right x -> return x
9551115 }
9561116
1117+-- Useful for app server integration.
1118+-- p must not start with slashes
1119+byteStringsToPageName :: BS.ByteString -> BS.ByteString -> PageName
1120+byteStringsToPageName p q =
1121+ let pageNameEncoder' :: Encoder Identity Identity PageName (String, String)
1122+ pageNameEncoder' = bimap
1123+ (unpackTextEncoder . pathSegmentsTextEncoder . listToNonEmptyEncoder)
1124+ (unpackTextEncoder . queryParametersTextEncoder . toListMapEncoder)
1125+ in decode pageNameEncoder' (T.unpack (T.decodeUtf8 p), T.unpack (T.decodeUtf8 q))
1126+
9571127--TODO: decodeURIComponent as appropriate
lib/run/src/Obelisk/Run.hs+11−12
@@ -20,11 +20,10 @@ import Control.Exception
2020import Control.Lens ((%~), (^?), _Just, _Right)
2121import qualified Data.Attoparsec.ByteString.Char8 as A
2222import Data.ByteString (ByteString)
23+import qualified Data.ByteString as BS
2324import qualified Data.ByteString.Char8 as BSC
2425import qualified Data.ByteString.Lazy.Char8 as BSLC
25−import Data.Dependent.Sum (DSum (..))
2626import Data.Functor.Identity
27−import Data.Functor.Sum
2827import Data.List (uncons)
2928import Data.Map (Map)
3029import qualified Data.Map as Map
@@ -83,8 +82,8 @@ run port serveStaticAsset backend frontend = do
8382 runSnapWithCommandLineArgs $ do
8483 getRouteWith validFullEncoder >>= \case
8584 Identity r -> case r of
86− InL backendRoute :=> Identity a -> serveRoute $ backendRoute :/ a
87− InR obeliskRoute :=> Identity a ->
85+ FullRoute_Backend backendRoute :/ a -> serveRoute $ backendRoute :/ a
86+ FullRoute_Frontend obeliskRoute :/ a ->
8887 serveDefaultObeliskApp (mkRouteToUrl validFullEncoder) serveStaticAsset frontend publicConfigs $ obeliskRoute :/ a
8988 let conf = defRunConfig { _runConfig_redirectPort = port }
9089 runWidget conf publicConfigs frontend validFullEncoder `finally` killThread backendTid
@@ -105,8 +104,8 @@ getConfigRoute configs = case Map.lookup "common/route" configs of
105104runWidget
106105 :: RunConfig
107106 -> Map Text ByteString
108− -> Frontend (R route)
109− -> Encoder Identity Identity (R (Sum backendRoute (ObeliskRoute route))) PageName
107+ -> Frontend (R frontendRoute)
108+ -> Encoder Identity Identity (R (FullRoute backendRoute frontendRoute)) PageName
110109 -> IO ()
111110runWidget conf configs frontend validFullEncoder = do
112111 uri <- either (fail . T.unpack) pure $ getConfigRoute configs
@@ -130,11 +129,11 @@ runWidget conf configs frontend validFullEncoder = do
130129 runner settings skt app)
131130
132131obeliskApp
133− :: forall route backendRoute
132+ :: forall frontendRoute backendRoute
134133 . Map Text ByteString
135134 -> ConnectionOptions
136− -> Frontend (R route)
137− -> Encoder Identity Identity (R (Sum backendRoute (ObeliskRoute route))) PageName
135+ -> Frontend (R frontendRoute)
136+ -> Encoder Identity Identity (R (FullRoute backendRoute frontendRoute)) PageName
138137 -> URI
139138 -> Application
140139 -> IO Application
@@ -150,14 +149,14 @@ obeliskApp configs opts frontend validFullEncoder uri backend = do
150149 let jsaddleUri = BSLC.fromStrict $ URI.renderBs $ uri & uriPath %~ (<>[jsaddlePath])
151150 Right (jsaddleWarpRouteValidEncoder :: Encoder Identity (Either Text) (R JSaddleWarpRoute) PageName) <- return $ checkEncoder jsaddleWarpRouteEncoder
152151 jsaddle <- jsaddleWithAppOr opts entryPoint $ \_ sendResponse -> sendResponse $ W.responseLBS H.status500 [("Content-Type", "text/plain")] "obeliskApp: jsaddle got a bad URL"
153− return $ \req sendResponse -> case tryDecode validFullEncoder (W.pathInfo req, mempty) of --TODO: Query strings
152+ return $ \req sendResponse -> case tryDecode validFullEncoder $ byteStringsToPageName (BS.dropWhile (== (fromIntegral $ fromEnum '/')) $ W.rawPathInfo req) (BS.drop 1 $ W.rawQueryString req) of
154153 Identity r -> case r of
155− InR (ObeliskRoute_Resource ResourceRoute_JSaddleWarp) :=> Identity jsaddleRoute -> case jsaddleRoute of
154+ FullRoute_Frontend (ObeliskRoute_Resource ResourceRoute_JSaddleWarp) :/ jsaddleRoute -> case jsaddleRoute of
156155 JSaddleWarpRoute_JavaScript :/ () -> sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/javascript")] $ jsaddleJs' (Just jsaddleUri) False
157156 _ -> flip jsaddle sendResponse $ req
158157 { W.pathInfo = fst $ encode jsaddleWarpRouteValidEncoder jsaddleRoute
159158 }
160− InR (ObeliskRoute_App appRouteComponent) :=> Identity appRouteRest -> do
159+ FullRoute_Frontend (ObeliskRoute_App appRouteComponent) :/ appRouteRest -> do
161160 let cookies = maybe [] parseCookies $ lookup (fromString "Cookie") (W.requestHeaders req)
162161 html <- renderJsaddleFrontend configs cookies (mkRouteToUrl validFullEncoder) (appRouteComponent :/ appRouteRest) frontend
163162 sendResponse $ W.responseLBS H.status200 [("Content-Type", staticRenderContentType)] $ BSLC.fromStrict html
skeleton/backend/src/Backend.hs+1−1
@@ -11,5 +11,5 @@ import Obelisk.Backend
1111backend :: Backend BackendRoute FrontendRoute
1212backend = Backend
1313 { _backend_run = \serve -> serve $ const $ return ()
14− , _backend_routeEncoder = backendRouteEncoder
14+ , _backend_routeEncoder = fullRouteEncoder
1515 }
skeleton/common/src/Common/Route.hs+8−12
@@ -19,7 +19,6 @@ import Control.Category
1919
2020import Data.Text (Text)
2121import Data.Functor.Identity
22−import Data.Functor.Sum
2322
2423import Obelisk.Route
2524import Obelisk.Route.TH
@@ -34,17 +33,14 @@ data FrontendRoute :: * -> * where
3433 FrontendRoute_Main :: FrontendRoute ()
3534 -- This type is used to define frontend routes, i.e. ones for which the backend will serve the frontend.
3635
37−backendRouteEncoder
38− :: Encoder (Either Text) Identity (R (Sum BackendRoute (ObeliskRoute FrontendRoute))) PageName
39−backendRouteEncoder = handleEncoder (const (InL BackendRoute_Missing :/ ())) $
40− pathComponentEncoder $ \case
41− InL backendRoute -> case backendRoute of
42− BackendRoute_Missing -> PathSegment "missing" $ unitEncoder mempty
43− InR obeliskRoute -> obeliskRouteSegment obeliskRoute $ \case
44− -- The encoder given to PathEnd determines how to parse query parameters,
45− -- in this example, we have none, so we insist on it.
46− FrontendRoute_Main -> PathEnd $ unitEncoder mempty
47−
36+fullRouteEncoder
37+ :: Encoder (Either Text) Identity (R (FullRoute BackendRoute FrontendRoute)) PageName
38+fullRouteEncoder = mkFullRouteEncoder
39+ (FullRoute_Backend BackendRoute_Missing :/ ())
40+ (\case
41+ BackendRoute_Missing -> PathSegment "missing" $ unitEncoder mempty)
42+ (\case
43+ FrontendRoute_Main -> PathEnd $ unitEncoder mempty)
4844
4945concat <$> mapM deriveRouteComponent
5046 [ ''BackendRoute
skeleton/default.nix+6−6
@@ -1,10 +1,10 @@
11{ obelisk ? import ./.obelisk/impl {
2− system = builtins.currentSystem;
3− iosSdkVersion = "10.2";
4− # You must accept the Android Software Development Kit License Agreement at
5− # https://developer.android.com/studio/terms in order to build Android apps.
6− # Uncomment and set this to `true` to indicate your acceptance:
7− # config.android_sdk.accept_license = false;
2+ system = builtins.currentSystem;
3+ iosSdkVersion = "10.2";
4+ # You must accept the Android Software Development Kit License Agreement at
5+ # https://developer.android.com/studio/terms in order to build Android apps.
6+ # Uncomment and set this to `true` to indicate your acceptance:
7+ # config.android_sdk.accept_license = false;
88 }
99}:
1010with obelisk;
skeleton/frontend/src-bin/main.hs+1−1
@@ -6,5 +6,5 @@ import Reflex.Dom
66
77main :: IO ()
88main = do
9− let Right validFullEncoder = checkEncoder backendRouteEncoder
9+ let Right validFullEncoder = checkEncoder fullRouteEncoder
1010 run $ runFrontend validFullEncoder frontend