Make sure entity declaration is not used in the Wasp file (#2152)

This commit is contained in:
Mihovil Ilakovac 2024-07-09 23:44:56 +02:00 committed by GitHub
parent 0eef00d5d9
commit 8f136fb5cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 136 additions and 8 deletions

View File

@ -127,6 +127,7 @@ import Wasp.Analyzer.AnalyzeError
) )
import Wasp.Analyzer.Evaluator (Decl, evaluate, takeDecls) import Wasp.Analyzer.Evaluator (Decl, evaluate, takeDecls)
import Wasp.Analyzer.Parser (parseStatements) import Wasp.Analyzer.Parser (parseStatements)
import Wasp.Analyzer.Parser.Valid (validateAst)
import Wasp.Analyzer.Prisma (injectEntitiesFromPrismaSchema) import Wasp.Analyzer.Prisma (injectEntitiesFromPrismaSchema)
import Wasp.Analyzer.StdTypeDefinitions (stdTypes) import Wasp.Analyzer.StdTypeDefinitions (stdTypes)
import Wasp.Analyzer.TypeChecker (typeCheck) import Wasp.Analyzer.TypeChecker (typeCheck)
@ -138,6 +139,25 @@ import qualified Wasp.Psl.Ast.Schema as Psl.Schema
analyze :: Psl.Schema.Schema -> String -> Either [AnalyzeError] [Decl] analyze :: Psl.Schema.Schema -> String -> Either [AnalyzeError] [Decl]
analyze prismaSchemaAst = analyze prismaSchemaAst =
(left (map ParseError) . parseStatements) (left (map ParseError) . parseStatements)
{--
Why introduce AST validation and not just throw a ParseError from the parser?
We want to support the `entity` declaration in the AST but not in the Wasp source
file.
This was the fastest and cleanest (e.g. not having to hack the type checker) way
to allow users to define entities in the Prisma schema file. We are parsing
the `schema.prisma` file and injecting the models into the Wasp AST as entity
statements.
We validate the AST to prevent users from defining entities in the Wasp source
file since we don't want to allow defining entities in two places.
Wasp file -(parse)-> AST -(validate)-> AST -(injectEntities)-> AST (...)
^ disallow entities here
^ inject entities here
--}
>=> (left ((: []) . ValidationError) . validateAst)
>=> injectEntitiesFromPrismaSchema prismaSchemaAst >=> injectEntitiesFromPrismaSchema prismaSchemaAst
>=> (left ((: []) . TypeError) . typeCheck stdTypes) >=> (left ((: []) . TypeError) . typeCheck stdTypes)
>=> (left ((: []) . EvaluationError) . evaluate stdTypes) >=> (left ((: []) . EvaluationError) . evaluate stdTypes)

View File

@ -16,10 +16,12 @@ data AnalyzeError
= ParseError PE.ParseError = ParseError PE.ParseError
| TypeError TE.TypeError | TypeError TE.TypeError
| EvaluationError EE.EvaluationError | EvaluationError EE.EvaluationError
| ValidationError (String, Ctx)
deriving (Show, Eq) deriving (Show, Eq)
getErrorMessageAndCtx :: AnalyzeError -> (String, Ctx) getErrorMessageAndCtx :: AnalyzeError -> (String, Ctx)
getErrorMessageAndCtx = \case getErrorMessageAndCtx = \case
ParseError e -> first (("Parse error:\n" ++) . indent 2) $ PE.getErrorMessageAndCtx e ParseError e -> first (("Parse error:\n" ++) . indent 2) $ PE.getErrorMessageAndCtx e
ValidationError (msg, ctx) -> ("Validation error:\n" ++ indent 2 msg, ctx)
TypeError e -> first (("Type error:\n" ++) . indent 2) $ TE.getErrorMessageAndCtx e TypeError e -> first (("Type error:\n" ++) . indent 2) $ TE.getErrorMessageAndCtx e
EvaluationError e -> first (("Evaluation error:\n" ++) . indent 2) $ EE.getErrorMessageAndCtx e EvaluationError e -> first (("Evaluation error:\n" ++) . indent 2) $ EE.getErrorMessageAndCtx e

View File

@ -0,0 +1,26 @@
module Wasp.Analyzer.Parser.Valid
( validateAst,
)
where
import Data.List (find)
import qualified Wasp.Analyzer.Parser as P
import Wasp.Analyzer.StdTypeDefinitions.Entity (entityDeclTypeName)
validateAst :: P.AST -> Either (String, P.Ctx) P.AST
validateAst = validateNoEntityDeclInWaspFile
validateNoEntityDeclInWaspFile :: P.AST -> Either (String, P.Ctx) P.AST
validateNoEntityDeclInWaspFile ast@(P.AST stmts) = case findEntityStmt stmts of
Just (P.WithCtx ctx _) -> Left (entitiesNoLongerSupportedError, ctx)
Nothing -> Right ast
where
findEntityStmt :: [P.WithCtx P.Stmt] -> Maybe (P.WithCtx P.Stmt)
findEntityStmt =
find
( \(P.WithCtx _ (P.Decl declTypeName _ _)) -> declTypeName == entityDeclTypeName
)
entitiesNoLongerSupportedError :: String
entitiesNoLongerSupportedError =
"Entities can no longer be defined in the .wasp file. You should migrate your entities to the schema.prisma file. Read more: https://wasp-lang.dev/docs/migrate-from-0-13-to-0-14#migrate-to-the-new-schemaprisma-file"

View File

@ -1,7 +1,10 @@
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-orphans #-}
module Wasp.Analyzer.StdTypeDefinitions.Entity () where module Wasp.Analyzer.StdTypeDefinitions.Entity
( entityDeclTypeName,
)
where
import Control.Arrow (left) import Control.Arrow (left)
import Wasp.Analyzer.Evaluator.EvaluationError (mkEvaluationError) import Wasp.Analyzer.Evaluator.EvaluationError (mkEvaluationError)
@ -16,7 +19,7 @@ import qualified Wasp.Psl.Parser.Model
instance IsDeclType Entity where instance IsDeclType Entity where
declType = declType =
DeclType DeclType
{ dtName = "entity", { dtName = entityDeclTypeName,
dtBodyType = Type.QuoterType "psl", dtBodyType = Type.QuoterType "psl",
dtEvaluate = \typeDefinitions bindings declName expr -> dtEvaluate = \typeDefinitions bindings declName expr ->
Decl.makeDecl @Entity declName <$> declEvaluate typeDefinitions bindings expr Decl.makeDecl @Entity declName <$> declEvaluate typeDefinitions bindings expr
@ -27,3 +30,6 @@ instance IsDeclType Entity where
left (ER.mkEvaluationError ctx . ER.ParseError . ER.EvaluationParseErrorParsec) $ left (ER.mkEvaluationError ctx . ER.ParseError . ER.EvaluationParseErrorParsec) $
makeEntity <$> Wasp.Psl.Parser.Model.parseBody pslString makeEntity <$> Wasp.Psl.Parser.Model.parseBody pslString
_ -> Left $ mkEvaluationError ctx $ ER.ExpectedType (Type.QuoterType "psl") (TC.AST.exprType expr) _ -> Left $ mkEvaluationError ctx $ ER.ExpectedType (Type.QuoterType "psl") (TC.AST.exprType expr)
entityDeclTypeName :: String
entityDeclTypeName = "entity"

View File

@ -27,15 +27,15 @@ data TypeError'
-- We use "unify" in the TypeChecker when trying to infer the common type for -- We use "unify" in the TypeChecker when trying to infer the common type for
-- typed expressions that we know should be of the same type (e.g. for -- typed expressions that we know should be of the same type (e.g. for
-- elements in the list). -- elements in the list).
= UnificationError TypeCoercionError = UnificationError TypeCoercionError
-- | Type coercion error that occurs when trying to use the typed expression -- | Type coercion error that occurs when trying to use the typed expression
-- of type T1 where T2 is expected. If T2 is a super type of T1 and T1 can be -- of type T1 where T2 is expected. If T2 is a super type of T1 and T1 can be
-- safely coerced to T2, no problem, but if not, we get this error. -- safely coerced to T2, no problem, but if not, we get this error.
| CoercionError TypeCoercionError | CoercionError TypeCoercionError
| NoDeclarationType TypeName | NoDeclarationType TypeName
| UndefinedIdentifier Identifier | UndefinedIdentifier Identifier
| QuoterUnknownTag QuoterTag | QuoterUnknownTag QuoterTag
| DictDuplicateField DictFieldName | DictDuplicateField DictFieldName
deriving (Eq, Show) deriving (Eq, Show)
{- ORMOLU_ENABLE -} {- ORMOLU_ENABLE -}

View File

@ -0,0 +1,71 @@
module Analyzer.ValidTest where
import Data.Either (fromRight, isRight)
import Test.Tasty.Hspec
import Wasp.Analyzer.Parser hiding (withCtx)
import qualified Wasp.Analyzer.Parser as P
import Wasp.Analyzer.Parser.Valid (validateAst)
import qualified Wasp.Version as WV
spec_ValidateAst :: Spec
spec_ValidateAst = do
it "Returns an error when entities are used" $ do
validateAndParseSource (waspSourceLines ++ entityDeclarationLines)
`shouldBe` Left
( "Entities can no longer be defined in the .wasp file. You should migrate your entities to the schema.prisma file. Read more: https://wasp-lang.dev/docs/migrate-from-0-13-to-0-14#migrate-to-the-new-schemaprisma-file",
P.Ctx
( P.SourceRegion
(P.SourcePosition 34 1)
(P.SourcePosition 37 5)
)
)
it "Returns AST when everything is correct" $ do
isRight (validateAndParseSource waspSourceLines) `shouldBe` True
where
validateAndParseSource = validateAst . parseSource
parseSource = fromRight (error "Parsing went wrong") . parseStatements . unlines
waspSourceLines =
[ "app Todo {",
" wasp: {",
" version: \"^" ++ show WV.waspVersion ++ "\",",
" },",
" title: \"Todo App\",",
" head: [\"foo\", \"bar\"],",
" auth: {",
" userEntity: User,",
" methods: {",
" usernameAndPassword: {",
" userSignupFields: import { getUserFields } from \"@src/auth/signup.js\",",
" }",
" },",
" onAuthFailedRedirectTo: \"/\",",
" },",
"}",
"",
"page HomePage {",
" component: import Home from \"@src/pages/Main\"",
"}",
"",
"route HomeRoute { path: \"/\", to: HomePage }",
"",
"query getUsers {",
" fn: import { getAllUsers } from \"@src/foo.js\",",
" entities: [User]",
"}",
"",
"action updateUser {",
" fn: import { updateUser } from \"@src/foo.js\",",
" entities: [User],",
" auth: true",
"}"
]
entityDeclarationLines =
[ "entity User {=psl",
" id Int @id @default(autoincrement())",
" email String @unique",
"psl=}"
]

View File

@ -201,6 +201,7 @@ library
Wasp.Analyzer.Parser.SourceSpan Wasp.Analyzer.Parser.SourceSpan
Wasp.Analyzer.Parser.Token Wasp.Analyzer.Parser.Token
Wasp.Analyzer.Parser.TokenSet Wasp.Analyzer.Parser.TokenSet
Wasp.Analyzer.Parser.Valid
Wasp.Analyzer.StdTypeDefinitions Wasp.Analyzer.StdTypeDefinitions
Wasp.Analyzer.StdTypeDefinitions.App.Dependency Wasp.Analyzer.StdTypeDefinitions.App.Dependency
Wasp.Analyzer.StdTypeDefinitions.Entity Wasp.Analyzer.StdTypeDefinitions.Entity
@ -612,6 +613,7 @@ test-suite waspc-test
Analyzer.TestUtil Analyzer.TestUtil
Analyzer.TypeChecker.InternalTest Analyzer.TypeChecker.InternalTest
Analyzer.TypeCheckerTest Analyzer.TypeCheckerTest
Analyzer.ValidTest
AnalyzerTest AnalyzerTest
AppSpec.ValidTest AppSpec.ValidTest
AppSpec.EntityTest AppSpec.EntityTest

View File

@ -108,6 +108,7 @@ waspErrorAsPrettyEditorMessage = Text.pack . fst . W.getErrorMessageAndCtx
waspErrorSource :: W.AnalyzeError -> Text waspErrorSource :: W.AnalyzeError -> Text
waspErrorSource (W.ParseError _) = "parse" waspErrorSource (W.ParseError _) = "parse"
waspErrorSource (W.ValidationError _) = "validate"
waspErrorSource (W.TypeError _) = "typecheck" waspErrorSource (W.TypeError _) = "typecheck"
waspErrorSource (W.EvaluationError _) = "evaluate" waspErrorSource (W.EvaluationError _) = "evaluate"