Skip to content

Commit

Permalink
Moves name mangling into the Generator monad
Browse files Browse the repository at this point in the history
  • Loading branch information
infomiho committed Mar 25, 2024
1 parent cc71b8a commit c24bbf1
Show file tree
Hide file tree
Showing 22 changed files with 351 additions and 266 deletions.
36 changes: 21 additions & 15 deletions waspc/src/Wasp/Generator/JsImport.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import qualified StrongPath as SP
import qualified Wasp.AppSpec.ExtImport as EI
import Wasp.Generator.Common (GeneratedSrcDir)
import Wasp.Generator.ExternalCodeGenerator.Common (GeneratedExternalCodeDir)
import Wasp.Generator.Monad (Generator, mangleName)
import Wasp.JsImport
( JsImport,
JsImportName (JsImportField, JsImportModule),
Expand All @@ -38,23 +39,28 @@ extImportNameToJsImportName :: EI.ExtImportName -> JsImportName
extImportNameToJsImportName (EI.ExtImportModule name) = JsImportModule name
extImportNameToJsImportName (EI.ExtImportField name) = JsImportField name

jsImportToImportJson :: Maybe JsImport -> Aeson.Value
jsImportToImportJson :: Maybe JsImport -> Generator Aeson.Value
jsImportToImportJson maybeJsImport = maybe notDefinedValue mkTmplData maybeJsImport
where
notDefinedValue = object ["isDefined" .= False]
notDefinedValue = return $ object ["isDefined" .= False]

mkTmplData :: JsImport -> Aeson.Value
mkTmplData jsImport =
let (jsImportStmt, jsImportIdentifier) = getJsImportStmtAndIdentifier $ mangleImportIdentifier jsImport
in object
[ "isDefined" .= True,
"importStatement" .= jsImportStmt,
"importIdentifier" .= jsImportIdentifier
]
mkTmplData :: JsImport -> Generator Aeson.Value
mkTmplData jsImport = do
mangledJsImport <- mangleImportIdentifier jsImport
let (jsImportStmt, jsImportIdentifier) = getJsImportStmtAndIdentifier mangledJsImport
return $
object
[ "isDefined" .= True,
"importStatement" .= jsImportStmt,
"importIdentifier" .= jsImportIdentifier
]
where
mangleImportIdentifier :: JsImport -> JsImport
mangleImportIdentifier JI.JsImport {JI._name = JsImportModule name} = mangleName name jsImport
mangleImportIdentifier JI.JsImport {JI._name = JsImportField name} = mangleName name jsImport
mangleImportIdentifier :: JsImport -> Generator JsImport
mangleImportIdentifier JI.JsImport {JI._name = JsImportModule name} = mangleJsImportName name jsImport
mangleImportIdentifier JI.JsImport {JI._name = JsImportField name} = mangleJsImportName name jsImport

mangleName :: String -> JsImport -> JsImport
mangleName originalName = applyJsImportAlias (Just (originalName ++ "__userDefined"))
mangleJsImportName :: String -> JsImport -> Generator JsImport
mangleJsImportName originalName originalJsImport = do
mangledName <- mangleName originalName

return $ applyJsImportAlias (Just mangledName) originalJsImport
37 changes: 30 additions & 7 deletions waspc/src/Wasp/Generator/Monad.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ module Wasp.Generator.Monad
logAndThrowGeneratorError,
logGeneratorWarning,
runGenerator,
mangleName,
)
where

import Control.Monad.Except (ExceptT, MonadError (throwError), runExceptT)
import qualified Control.Monad.Except as MonadExcept
import Control.Monad.Identity (Identity (runIdentity))
import Control.Monad.State (MonadState, StateT (runStateT), modify)
import Control.Monad.State (MonadState, StateT (runStateT), gets, modify)
import Data.List.NonEmpty (NonEmpty, fromList)
import qualified Data.Map

-- | Generator is a monad transformer stack where we abstract away the underlying
-- concrete monad transformers with the helper functions below. This will allow us
Expand All @@ -37,7 +39,8 @@ newtype Generator a = Generator

data GeneratorState = GeneratorState
{ warnings :: [GeneratorWarning],
errors :: [GeneratorError]
errors :: [GeneratorError],
mangledNames :: Data.Map.Map String Int
}

data GeneratorError = GenericGeneratorError String
Expand All @@ -61,24 +64,44 @@ runGenerator generator =
let (errorOrResult, finalState) = runIdentity $ runStateT (runExceptT (_runGenerator generator)) initialState
in (warnings finalState, loggedErrorsOrResult (errorOrResult, errors finalState))
where
initialState = GeneratorState {warnings = [], errors = []}
initialState = GeneratorState {warnings = [], errors = [], mangledNames = mempty}

loggedErrorsOrResult (Right result, []) = Right result
loggedErrorsOrResult (Left _, []) = error "Generator produced error, but had empty log - this should never happen!"
loggedErrorsOrResult (_, loggedErrors) = Left $ fromList loggedErrors

-- This logs a warning but does not short circuit the computation.
logGeneratorWarning :: GeneratorWarning -> Generator ()
logGeneratorWarning w = modify $ \GeneratorState {errors = errors', warnings = warnings'} ->
GeneratorState {errors = errors', warnings = w : warnings'}
logGeneratorWarning w = modify $ \GeneratorState {errors = errors', warnings = warnings', mangledNames = mangledNames'} ->
GeneratorState {errors = errors', warnings = w : warnings', mangledNames = mangledNames'}

-- Mangles a name to avoid name clashes. This is useful when generating code that may have user-defined names.
-- It keeps the mangled names unique by appending a counter to the end of the name.
mangleName :: String -> Generator String
mangleName originalName = do
mangledNamesMap <- gets mangledNames

let (mangledName, newMangledNamesMap) = mangleName' originalName mangledNamesMap

modify $ \s -> s {mangledNames = newMangledNamesMap}

return mangledName
where
mangleName' :: String -> Data.Map.Map String Int -> (String, Data.Map.Map String Int)
mangleName' originalName' mangledNamesMap =
case Data.Map.lookup mangledName mangledNamesMap of
Nothing -> (mangledName, Data.Map.insert mangledName 0 mangledNamesMap)
Just i -> (mangledName ++ "$" ++ show (i + 1), Data.Map.insert mangledName (i + 1) mangledNamesMap)
where
mangledName = originalName' ++ "__userDefined"

-- This logs an error and does throw, thus short-circuiting the computation until caught.
logAndThrowGeneratorError :: GeneratorError -> Generator a
logAndThrowGeneratorError e = logGeneratorError >> throwError e
where
logGeneratorError :: Generator ()
logGeneratorError = modify $ \GeneratorState {errors = errors', warnings = warnings'} ->
GeneratorState {errors = e : errors', warnings = warnings'}
logGeneratorError = modify $ \GeneratorState {errors = errors', warnings = warnings', mangledNames = mangledNames'} ->
GeneratorState {errors = e : errors', warnings = warnings', mangledNames = mangledNames'}

-- This stops the short-circuiting from above, if ever desired, but cannot be used for full recovery.
-- Once one error is logged and thrown the result will be error. This function exists to log
Expand Down
3 changes: 2 additions & 1 deletion waspc/src/Wasp/Generator/SdkGenerator/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Wasp.Generator.Common (ProjectRootDir)
import Wasp.Generator.ExternalCodeGenerator.Common (GeneratedExternalCodeDir)
import Wasp.Generator.FileDraft (FileDraft, createTemplateFileDraft)
import qualified Wasp.Generator.JsImport as GJI
import Wasp.Generator.Monad (Generator)
import Wasp.Generator.Templates (TemplatesDir)
import qualified Wasp.JsImport as JI
import Wasp.Project.Common (generatedCodeDirInDotWaspDir)
Expand Down Expand Up @@ -83,7 +84,7 @@ clientTemplatesDirInSdkTemplatesDir = [reldir|client|]
serverTemplatesDirInSdkTemplatesDir :: Path' (Rel SdkTemplatesDir) (Dir ServerTemplatesDir)
serverTemplatesDirInSdkTemplatesDir = [reldir|server|]

extImportToSdkImportJson :: Maybe EI.ExtImport -> Aeson.Value
extImportToSdkImportJson :: Maybe EI.ExtImport -> Generator Aeson.Value
extImportToSdkImportJson =
GJI.jsImportToImportJson
. extImportToSdkJsImport
Expand Down
39 changes: 20 additions & 19 deletions waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,26 @@ genCrud spec =
areThereAnyCruds = not $ null cruds

genCrudServerOperations :: AppSpec -> [(String, AS.Crud.Crud)] -> Generator [FileDraft]
genCrudServerOperations spec cruds = return $ map genCrudOperation cruds
genCrudServerOperations spec cruds = mapM genCrudOperation cruds
where
genCrudOperation :: (String, AS.Crud.Crud) -> FileDraft
genCrudOperation (name, crud) = C.mkTmplFdWithDstAndData tmplPath destPath (Just tmplData)
genCrudOperation :: (String, AS.Crud.Crud) -> Generator FileDraft
genCrudOperation (name, crud) = do
overrides <- mapM operationToOverrideImport crudOperations

let tmplData =
object
[ "crud" .= getCrudOperationJson name crud idField,
"isAuthEnabled" .= isAuthEnabled spec,
"userEntityUpper" .= maybeUserEntity,
"overrides" .= object overrides,
"queryType" .= queryTsType,
"actionType" .= actionTsType
]

return $ C.mkTmplFdWithDstAndData tmplPath destPath (Just tmplData)
where
tmplPath = [relfile|server/crud/_operationTypes.ts|]
destPath = [reldir|server/crud|] </> getCrudFilePath name "ts"
tmplData =
object
[ "crud" .= getCrudOperationJson name crud idField,
"isAuthEnabled" .= isAuthEnabled spec,
"userEntityUpper" .= maybeUserEntity,
"overrides" .= object overrides,
"queryType" .= queryTsType,
"actionType" .= actionTsType
]
idField = getIdFieldFromCrudEntity spec crud
maybeUserEntity = AS.refName . AS.Auth.userEntity <$> maybeAuth
maybeAuth = AS.App.auth $ snd $ getApp spec
Expand All @@ -58,12 +62,9 @@ genCrudServerOperations spec cruds = return $ map genCrudOperation cruds
actionTsType :: String
actionTsType = if isAuthEnabled spec then "AuthenticatedAction" else "Action"

overrides :: [Aeson.Types.Pair]
overrides = map operationToOverrideImport crudOperations

crudOperations = crudDeclarationToOperationsList crud

operationToOverrideImport :: (AS.Crud.CrudOperation, AS.Crud.CrudOperationOptions) -> Aeson.Types.Pair
operationToOverrideImport (operation, options) = makeCrudOperationKeyAndJsonPair operation importJson
where
importJson = extImportToSdkImportJson $ AS.Crud.overrideFn options
operationToOverrideImport :: (AS.Crud.CrudOperation, AS.Crud.CrudOperationOptions) -> Generator Aeson.Types.Pair
operationToOverrideImport (operation, options) = do
importJson <- extImportToSdkImportJson $ AS.Crud.overrideFn options
return $ makeCrudOperationKeyAndJsonPair operation importJson
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ genJob (jobName, job) =
getJobExecutorImportPath :: JobExecutor -> Path Posix (Rel r) File'
getJobExecutorImportPath PgBoss = makeSdkImportPath [relfileP|server/jobs/core/pgBoss|]

getImportJsonForJobDefinition :: String -> Aeson.Value
getImportJsonForJobDefinition :: String -> Generator Aeson.Value
getImportJsonForJobDefinition jobName =
GJI.jsImportToImportJson $
Just $
Expand Down
62 changes: 35 additions & 27 deletions waspc/src/Wasp/Generator/SdkGenerator/Server/OperationsGenerator.hs
Original file line number Diff line number Diff line change
Expand Up @@ -41,32 +41,38 @@ genOperations spec =
]

genServerTs :: AppSpec -> Generator FileDraft
genServerTs spec = return $ mkTmplFdWithData relPath tmplData
genServerTs spec = do
actions <- mapM getActionData (AS.getActions spec)
queries <- mapM getQueryData (AS.getQueries spec)
let tmplData =
object
[ "actions" .= actions,
"queries" .= queries
]

return $ mkTmplFdWithData relPath tmplData
where
relPath = serverOpsDirInSdkTemplatesDir </> [relfile|index.ts|]
tmplData =
object
[ "actions" .= map getActionData (AS.getActions spec),
"queries" .= map getQueryData (AS.getQueries spec)
]

genQueriesIndex :: AppSpec -> Generator FileDraft
genQueriesIndex spec = return $ mkTmplFdWithData relPath tmplData
genQueriesIndex spec = do
operations <- mapM getQueryData (AS.getQueries spec)

let tmplData = object ["operations" .= operations]

return $ mkTmplFdWithData relPath tmplData
where
relPath = serverOpsDirInSdkTemplatesDir </> [relfile|queries/index.ts|]
tmplData =
object
[ "operations" .= map getQueryData (AS.getQueries spec)
]

genActionsIndex :: AppSpec -> Generator FileDraft
genActionsIndex spec = return $ mkTmplFdWithData relPath tmplData
genActionsIndex spec = do
operations <- mapM getActionData (AS.getActions spec)

let tmplData = object ["operations" .= operations]

return $ mkTmplFdWithData relPath tmplData
where
relPath = serverOpsDirInSdkTemplatesDir </> [relfile|actions/index.ts|]
tmplData =
object
[ "operations" .= map getActionData (AS.getActions spec)
]

genQueryTypesFile :: AppSpec -> Generator FileDraft
genQueryTypesFile spec = genOperationTypesFile relPath operations isAuthEnabledGlobally
Expand All @@ -85,12 +91,12 @@ genActionTypesFile spec = genOperationTypesFile relPath operations isAuthEnabled
-- | Here we generate JS file that basically imports JS query function provided by user,
-- decorates it (mostly injects stuff into it) and exports. Idea is that the rest of the server,
-- and user also, should use this new JS function, and not the old one directly.
getQueryData :: (String, AS.Query.Query) -> Aeson.Value
getQueryData :: (String, AS.Query.Query) -> Generator Aeson.Value
getQueryData (queryName, query) = getOperationTmplData operation
where
operation = AS.Operation.QueryOp queryName query

getActionData :: (String, AS.Action.Action) -> Aeson.Value
getActionData :: (String, AS.Action.Action) -> Generator Aeson.Value
getActionData (actionName, action) = getOperationTmplData operation
where
operation = AS.Operation.ActionOp actionName action
Expand Down Expand Up @@ -125,12 +131,14 @@ serverOperationsDirInSdkRootDir =
(AS.Operation.QueryOp _ _) -> [reldir|queries|]
(AS.Operation.ActionOp _ _) -> [reldir|actions|]

getOperationTmplData :: AS.Operation.Operation -> Aeson.Value
getOperationTmplData operation =
object
[ "jsFn" .= extImportToSdkImportJson (Just $ AS.Operation.getFn operation),
"operationName" .= getName operation,
"operationTypeName" .= toUpperFirst (getName operation),
"entities"
.= maybe [] (map (makeJsonWithEntityData . AS.refName)) (AS.Operation.getEntities operation)
]
getOperationTmplData :: AS.Operation.Operation -> Generator Aeson.Value
getOperationTmplData operation = do
jsFn <- extImportToSdkImportJson (Just $ AS.Operation.getFn operation)
return $
object
[ "jsFn" .= jsFn,
"operationName" .= getName operation,
"operationTypeName" .= toUpperFirst (getName operation),
"entities"
.= maybe [] (map (makeJsonWithEntityData . AS.refName)) (AS.Operation.getEntities operation)
]
16 changes: 9 additions & 7 deletions waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,16 @@ genWebSockets spec
genFileCopy = return . C.mkTmplFd

genWebSocketServerIndex :: AppSpec -> Generator FileDraft
genWebSocketServerIndex spec = return $ C.mkTmplFdWithData [relfile|server/webSocket/index.ts|] tmplData
genWebSocketServerIndex spec = do
userWebSocketFn <- extImportToSdkImportJson maybeWebSocketFn
let tmplData =
object
[ "isAuthEnabled" .= isAuthEnabled spec,
"userWebSocketFn" .= userWebSocketFn,
"allEntities" .= map (makeJsonWithEntityData . fst) (AS.getEntities spec)
]
return $ C.mkTmplFdWithData [relfile|server/webSocket/index.ts|] tmplData
where
tmplData =
object
[ "isAuthEnabled" .= isAuthEnabled spec,
"userWebSocketFn" .= extImportToSdkImportJson maybeWebSocketFn,
"allEntities" .= map (makeJsonWithEntityData . fst) (AS.getEntities spec)
]
maybeWebSocket = AS.App.webSocket $ snd $ getApp spec
maybeWebSocketFn = AS.App.WS.fn <$> maybeWebSocket

Expand Down
18 changes: 11 additions & 7 deletions waspc/src/Wasp/Generator/ServerGenerator.hs
Original file line number Diff line number Diff line change
Expand Up @@ -205,16 +205,18 @@ genSrcDir spec =
genFileCopy = return . C.mkSrcTmplFd

genServerJs :: AppSpec -> Generator FileDraft
genServerJs spec =
genServerJs spec = do
setupFn <- extImportToImportJson relPathToServerSrcDir maybeSetupJsFunction
userWebSocketFn <- mkWebSocketFnImport maybeWebSocket [reldirP|./|]
return $
C.mkTmplFdWithDstAndData
(C.asTmplFile [relfile|src/server.ts|])
(C.asServerFile [relfile|src/server.ts|])
( Just $
object
[ "setupFn" .= extImportToImportJson relPathToServerSrcDir maybeSetupJsFunction,
[ "setupFn" .= setupFn,
"isPgBossJobExecutorUsed" .= isPgBossJobExecutorUsed spec,
"userWebSocketFn" .= mkWebSocketFnImport maybeWebSocket [reldirP|./|]
"userWebSocketFn" .= userWebSocketFn
]
)
where
Expand Down Expand Up @@ -256,15 +258,17 @@ genEnvValidationScript =
]

genMiddleware :: AppSpec -> Generator [FileDraft]
genMiddleware spec =
genMiddleware spec = do
globalMiddlewareConfigFn <- extImportToImportJson [reldirP|../|] maybeGlobalMiddlewareConfigFn

let globalMiddlewareTmplData = object ["globalMiddlewareConfigFn" .= globalMiddlewareConfigFn]

sequence
[ return $ C.mkTmplFd [relfile|src/middleware/index.ts|],
return $ C.mkTmplFdWithData [relfile|src/middleware/globalMiddleware.ts|] (Just tmplData),
return $ C.mkTmplFdWithData [relfile|src/middleware/globalMiddleware.ts|] (Just globalMiddlewareTmplData),
genOperationsMiddleware spec
]
where
tmplData = object ["globalMiddlewareConfigFn" .= globalMiddlewareConfigFn]
globalMiddlewareConfigFn = extImportToImportJson [reldirP|../|] maybeGlobalMiddlewareConfigFn
maybeGlobalMiddlewareConfigFn = AS.App.server (snd $ getApp spec) >>= AS.App.Server.middlewareConfigFn

genOperationsMiddleware :: AppSpec -> Generator FileDraft
Expand Down
Loading

0 comments on commit c24bbf1

Please sign in to comment.