Skip to content

Commit edf2d49

Browse files
committed
wip: Refactor Configuration
1 parent 69b6cf5 commit edf2d49

File tree

7 files changed

+163
-112
lines changed

7 files changed

+163
-112
lines changed

.hlint.yaml

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,31 @@
88

99
# Specify additional command line arguments
1010
#
11-
# - arguments: [--color, --cpp-simple, -XQuasiQuotes]
11+
- arguments:
12+
- TemplateHaskell
13+
- OverloadedStrings
14+
- RecordWildCards
15+
- ScopedTypeVariables
16+
- DeriveGeneric
17+
- TypeApplications
18+
- FlexibleContexts
19+
- DeriveAnyClass
20+
- QuasiQuotes
1221

1322

1423
# Control which extensions/flags/modules/functions can be used
1524
#
16-
- extensions:
17-
- name:
18-
- TemplateHaskell
19-
- OverloadedStrings
20-
- RecordWildCards
21-
- ScopedTypeVariables
22-
- DeriveGeneric
23-
- TypeApplications
24-
- FlexibleContexts
25-
- DeriveAnyClass
26-
- QuasiQuotes
25+
# - extensions:
26+
# - name:
27+
# - TemplateHaskell
28+
# - OverloadedStrings
29+
# - RecordWildCards
30+
# - ScopedTypeVariables
31+
# - DeriveGeneric
32+
# - TypeApplications
33+
# - FlexibleContexts
34+
# - DeriveAnyClass
35+
# - QuasiQuotes
2736
#
2837
# - flags:
2938
# - {name: -w, within: []} # -w is allowed nowhere

src/Aws/Lambda/Configuration.hs

Lines changed: 8 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,37 @@
11
{-# OPTIONS_GHC -fno-warn-unused-pattern-binds #-}
22
module Aws.Lambda.Configuration
3-
( LambdaOptions (..)
3+
( Main.LambdaOptions(..)
4+
, Main.generate
5+
, Main.getRecord
46
, configureLambda
57
, returnAndFail
68
, returnAndSucceed
79
, decodeObj
8-
, Options.getRecord
910
)
1011
where
1112

1213
import Data.Aeson
1314

14-
import Control.Monad
1515
import qualified Data.ByteString.Lazy as LazyByteString
16-
import Data.Function ((&))
1716
import Data.Text (Text)
1817
import qualified Data.Text as Text
1918
import qualified Data.Text.Encoding as Encoding
20-
import GHC.Generics
2119
import Language.Haskell.TH
22-
import qualified Options.Generic as Options
2320
import System.Exit (exitFailure, exitSuccess)
2421
import System.IO (hFlush, stderr, stdout)
2522

26-
import Path
27-
import qualified Path.IO as PathIO
28-
29-
import Aws.Lambda.ThHelpers
23+
import qualified Aws.Lambda.Meta.Main as Main
24+
import qualified Aws.Lambda.Meta.Run as Run
3025

3126
putTextLn :: Text -> IO ()
3227
putTextLn = putStrLn . Text.unpack
3328

34-
data LambdaOptions = LambdaOptions
35-
{ eventObject :: Text
36-
, contextObject :: Text
37-
, functionHandler :: Text
38-
, executionUuid :: Text
39-
} deriving (Generic)
40-
instance Options.ParseRecord LambdaOptions
41-
42-
43-
-- This function is the reason why we disable the warning on top of the module
44-
mkMain :: Q [Dec]
45-
mkMain = [d|
46-
$(pName "main") = getRecord "" >>= run
47-
|]
48-
49-
mkRun :: Q Dec
50-
mkRun = do
51-
handlers <- runIO getHandlers
52-
clause' <- recordQ "LambdaOptions" ["functionHandler", "contextObject", "eventObject", "executionUuid"]
53-
body <- dispatcherCaseQ handlers
54-
pure $ FunD (mkName "run") [Clause [clause'] (NormalB body) []]
55-
56-
57-
dispatcherCaseQ :: [Text] -> Q Exp
58-
dispatcherCaseQ fileNames = do
59-
caseExp <- eName "functionHandler"
60-
matches <- traverse handlerCaseQ fileNames
61-
unmatched <- unmatchedCaseQ
62-
pure $ CaseE caseExp (matches <> [unmatched])
63-
64-
65-
handlerCaseQ :: Text -> Q Match
66-
handlerCaseQ lambdaHandler = do
67-
let pat = LitP (StringL $ Text.unpack lambdaHandler)
68-
body <- [e|do
69-
result <- $(eName qualifiedName) (decodeObj $(eName "eventObject")) (decodeObj $(eName "contextObject"))
70-
either (returnAndFail $(eName "executionUuid")) (returnAndSucceed $(eName "executionUuid")) result |]
71-
pure $ Match pat (NormalB body) []
72-
where
73-
qualifiedName =
74-
lambdaHandler
75-
& Text.dropWhile (/= '/')
76-
& Text.drop 1
77-
& Text.replace "/" "."
78-
79-
80-
unmatchedCaseQ :: Q Match
81-
unmatchedCaseQ = do
82-
let pattern = WildP
83-
body <- [e|
84-
returnAndFail $(eName "executionUuid") ("Handler " <> $(eName "functionHandler") <> " does not exist on project")
85-
|]
86-
pure $ Match pattern (NormalB body) []
87-
8829
configureLambda :: Q [Dec]
8930
configureLambda = do
90-
main <- mkMain
91-
run <- mkRun
31+
main <- Main.generate
32+
run <- Run.generate
9233
return $ main <> [run]
9334

94-
9535
returnAndFail :: ToJSON a => Text -> a -> IO ()
9636
returnAndFail uuid v = do
9737
hFlush stdout
@@ -115,23 +55,4 @@ decodeObj :: FromJSON a => Text -> a
11555
decodeObj x =
11656
case (eitherDecode $ LazyByteString.fromStrict $ Encoding.encodeUtf8 x) of
11757
Left e -> error e
118-
Right v -> v
119-
120-
getHandlers :: IO [Text]
121-
getHandlers = do
122-
(_, files) <- PathIO.listDirRecurRel [reldir|.|]
123-
handlerFiles <- files
124-
& fmap toFilePath
125-
& fmap Text.pack
126-
& filter (Text.isSuffixOf ".hs")
127-
& filterM containsHandler
128-
& fmap (fmap $ Text.dropEnd 3)
129-
& fmap (fmap $ Text.drop 2)
130-
& fmap (fmap (<> ".handler"))
131-
return handlerFiles
132-
133-
134-
containsHandler :: Text -> IO Bool
135-
containsHandler file = do
136-
fileContents <- readFile $ Text.unpack file
137-
return $ "handler :: " `Text.isInfixOf` Text.pack fileContents
58+
Right v -> v
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
module Aws.Lambda.ThHelpers
2-
( pName
3-
, eName
4-
, recordQ
1+
module Aws.Lambda.Meta.Common
2+
( declarationName
3+
, expressionName
4+
, getFieldsFrom
55
) where
66

77
import Data.Text (Text)
@@ -10,26 +10,26 @@ import Language.Haskell.TH
1010

1111
-- | Helper for defining names in declarations
1212
-- think of @myValue@ in @myValue = 2@
13-
pName :: Text -> Q Pat
14-
pName = pure . VarP . mkName . Text.unpack
13+
declarationName :: Text -> Q Pat
14+
declarationName = pure . VarP . mkName . Text.unpack
1515

1616
-- | Helper for defining names in expressions
1717
-- think of @myFunction@ in @quux = myFunction 3@
18-
eName :: Text -> Q Exp
19-
eName = pure . VarE . mkName . Text.unpack
18+
expressionName :: Text -> Q Exp
19+
expressionName = pure . VarE . mkName . Text.unpack
2020

2121

2222
-- | Helper for extracting fields of a specified record
2323
-- it expects the constructor name as the first parameter,
2424
-- and the list of fields to bring into scope as second
2525
-- think of @Person@, and @personAge@, @personName@ in
2626
-- @myFunction Person { personAge, personName } = ...@
27-
recordQ :: Text -> [Text] -> Q Pat
28-
recordQ name fields = do
29-
extractedFields <- traverse fName fields
27+
getFieldsFrom :: Text -> [Text] -> Q Pat
28+
getFieldsFrom name fields = do
29+
extractedFields <- traverse extractField fields
3030
pure $ RecP (mkName $ Text.unpack name) extractedFields
3131
where
3232
-- | Helper for extracting fields of records
3333
-- think of @personAge@ in @myFunction Person { personAge = personAge } = ...@
34-
fName :: Text -> Q FieldPat
35-
fName n = pure (mkName $ Text.unpack n, VarP $ mkName $ Text.unpack n)
34+
extractField :: Text -> Q FieldPat
35+
extractField n = pure (mkName $ Text.unpack n, VarP $ mkName $ Text.unpack n)

src/Aws/Lambda/Meta/Discover.hs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module Aws.Lambda.Meta.Discover
2+
(handlers) where
3+
4+
import Data.Text (Text)
5+
import Data.Function ((&))
6+
import qualified Data.Text as Text
7+
import qualified Control.Monad as Monad
8+
import qualified Data.Maybe as Maybe
9+
10+
import Path
11+
import qualified Path.IO as PathIO
12+
13+
handlers :: IO [Text]
14+
handlers = do
15+
(_, files) <- PathIO.listDirRecurRel [reldir|.|]
16+
handlerFiles <- modulesWithHandler files
17+
pure (handlerNames handlerFiles)
18+
19+
modulesWithHandler :: [Path Rel File] -> IO [Path Rel File]
20+
modulesWithHandler files =
21+
filter isHaskellModule files
22+
& Monad.filterM containsHandler
23+
where
24+
isHaskellModule file =
25+
fileExtension file == ".hs"
26+
27+
handlerNames :: [Path Rel File] -> [Text]
28+
handlerNames modules =
29+
fmap changeExtensionToHandler modules
30+
& fmap (Text.pack . toFilePath)
31+
where
32+
changeExtensionToHandler file =
33+
setFileExtension ".handler" file
34+
& Maybe.fromJust
35+
36+
containsHandler :: Path Rel File -> IO Bool
37+
containsHandler file = do
38+
fileContents <- readFile $ toFilePath file
39+
pure $ "handler :: " `Text.isInfixOf` Text.pack fileContents

src/Aws/Lambda/Meta/Dispatch.hs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
module Aws.Lambda.Meta.Dispatch
2+
(generate) where
3+
4+
import Data.Function ((&))
5+
import Data.Text (Text)
6+
import qualified Data.Text as Text
7+
8+
import qualified Language.Haskell.TH as Meta
9+
10+
import Aws.Lambda.Meta.Common
11+
12+
generate :: [Text] -> Meta.ExpQ
13+
generate fileNames = do
14+
caseExp <- expressionName "functionHandler"
15+
matches <- traverse handlerCase fileNames
16+
unmatched <- unmatchedCase
17+
pure $ Meta.CaseE caseExp (matches <> [unmatched])
18+
19+
20+
handlerCase :: Text -> Meta.MatchQ
21+
handlerCase lambdaHandler = do
22+
let pat = Meta.LitP (Meta.StringL $ Text.unpack lambdaHandler)
23+
body <- [e|do
24+
result <- $(expressionName qualifiedName) (decodeObj $(expressionName "eventObject")) (decodeObj $(expressionName "contextObject"))
25+
either (returnAndFail $(expressionName "executionUuid")) (returnAndSucceed $(expressionName "executionUuid")) result |]
26+
pure $ Meta.Match pat (Meta.NormalB body) []
27+
where
28+
qualifiedName =
29+
lambdaHandler
30+
& Text.dropWhile (/= '/')
31+
& Text.drop 1
32+
& Text.replace "/" "."
33+
34+
35+
unmatchedCase :: Meta.MatchQ
36+
unmatchedCase = do
37+
let pattern = Meta.WildP
38+
body <- [e|
39+
returnAndFail $(expressionName "executionUuid") ("Handler " <> $(expressionName "functionHandler") <> " does not exist on project")
40+
|]
41+
pure $ Meta.Match pattern (Meta.NormalB body) []

src/Aws/Lambda/Meta/Main.hs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module Aws.Lambda.Meta.Main
2+
( LambdaOptions(..)
3+
, generate
4+
, Options.getRecord
5+
) where
6+
7+
import Data.Text (Text)
8+
import GHC.Generics (Generic)
9+
10+
import qualified Options.Generic as Options
11+
import qualified Language.Haskell.TH as Meta
12+
13+
import Aws.Lambda.Meta.Common
14+
15+
data LambdaOptions = LambdaOptions
16+
{ eventObject :: !Text
17+
, contextObject :: !Text
18+
, functionHandler :: !Text
19+
, executionUuid :: !Text
20+
} deriving (Generic, Options.ParseRecord)
21+
22+
generate :: Meta.DecsQ
23+
generate = [d|
24+
$(declarationName "main") = getRecord "" >>= run
25+
|]

src/Aws/Lambda/Meta/Run.hs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module Aws.Lambda.Meta.Run
2+
( generate
3+
) where
4+
5+
import qualified Language.Haskell.TH as Meta
6+
7+
import Aws.Lambda.Meta.Common
8+
import qualified Aws.Lambda.Meta.Dispatch as Dispatch
9+
import qualified Aws.Lambda.Meta.Discover as Discover
10+
11+
generate :: Meta.DecQ
12+
generate = do
13+
handlers <- Meta.runIO Discover.handlers
14+
clause' <- getFieldsFrom "LambdaOptions" ["functionHandler", "contextObject", "eventObject", "executionUuid"]
15+
body <- Dispatch.generate handlers
16+
pure $ Meta.FunD (Meta.mkName "run") [Meta.Clause [clause'] (Meta.NormalB body) []]

0 commit comments

Comments
 (0)