mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-09-11 07:16:09 +03:00
Make sure entity
declaration is not used in the Wasp file (#2152)
This commit is contained in:
parent
0eef00d5d9
commit
8f136fb5cd
@ -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)
|
||||||
|
@ -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
|
||||||
|
26
waspc/src/Wasp/Analyzer/Parser/Valid.hs
Normal file
26
waspc/src/Wasp/Analyzer/Parser/Valid.hs
Normal 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"
|
@ -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"
|
||||||
|
@ -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 -}
|
||||||
|
|
||||||
|
71
waspc/test/Analyzer/ValidTest.hs
Normal file
71
waspc/test/Analyzer/ValidTest.hs
Normal 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=}"
|
||||||
|
]
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user