Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 7 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions waspc/src/Wasp/Analyzer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ import Wasp.Analyzer.AnalyzeError
)
import Wasp.Analyzer.Evaluator (Decl, evaluate, takeDecls)
import Wasp.Analyzer.Parser (parseStatements)
import Wasp.Analyzer.Parser.Valid (validateAst)
import Wasp.Analyzer.Prisma (injectEntitiesFromPrismaSchema)
import Wasp.Analyzer.StdTypeDefinitions (stdTypes)
import Wasp.Analyzer.TypeChecker (typeCheck)
Expand All @@ -138,6 +139,25 @@ import qualified Wasp.Psl.Ast.Schema as Psl.Schema
analyze :: Psl.Schema.Schema -> String -> Either [AnalyzeError] [Decl]
analyze prismaSchemaAst =
(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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imagine opening up this PR after reviewing and writing TypeScript all day. And the first thing you see is this line here.

Oh well, time to gaslight myself into thinking Haskell is readable again 😄

>=> injectEntitiesFromPrismaSchema prismaSchemaAst
>=> (left ((: []) . TypeError) . typeCheck stdTypes)
>=> (left ((: []) . EvaluationError) . evaluate stdTypes)
2 changes: 2 additions & 0 deletions waspc/src/Wasp/Analyzer/AnalyzeError.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ data AnalyzeError
= ParseError PE.ParseError
| TypeError TE.TypeError
| EvaluationError EE.EvaluationError
| ValidationError (String, Ctx)
deriving (Show, Eq)

getErrorMessageAndCtx :: AnalyzeError -> (String, Ctx)
getErrorMessageAndCtx = \case
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
EvaluationError e -> first (("Evaluation error:\n" ++) . indent 2) $ EE.getErrorMessageAndCtx e
26 changes: 26 additions & 0 deletions waspc/src/Wasp/Analyzer/Parser/Valid.hs
Original file line number Diff line number Diff line change
@@ -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"
10 changes: 8 additions & 2 deletions waspc/src/Wasp/Analyzer/StdTypeDefinitions/Entity.hs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Wasp.Analyzer.StdTypeDefinitions.Entity () where
module Wasp.Analyzer.StdTypeDefinitions.Entity
( entityDeclTypeName,
)
where

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

entityDeclTypeName :: String
entityDeclTypeName = "entity"
12 changes: 6 additions & 6 deletions waspc/src/Wasp/Analyzer/TypeChecker/TypeError.hs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ data TypeError'
-- 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
-- elements in the list).
= UnificationError TypeCoercionError
= UnificationError TypeCoercionError
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting

-- | 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
-- safely coerced to T2, no problem, but if not, we get this error.
| CoercionError TypeCoercionError
| NoDeclarationType TypeName
| UndefinedIdentifier Identifier
| QuoterUnknownTag QuoterTag
| DictDuplicateField DictFieldName
| CoercionError TypeCoercionError
| NoDeclarationType TypeName
| UndefinedIdentifier Identifier
| QuoterUnknownTag QuoterTag
| DictDuplicateField DictFieldName
deriving (Eq, Show)
{- ORMOLU_ENABLE -}

Expand Down
71 changes: 71 additions & 0 deletions waspc/test/Analyzer/ValidTest.hs
Original file line number Diff line number Diff line change
@@ -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=}"
]
2 changes: 2 additions & 0 deletions waspc/waspc.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ library
Wasp.Analyzer.Parser.SourceSpan
Wasp.Analyzer.Parser.Token
Wasp.Analyzer.Parser.TokenSet
Wasp.Analyzer.Parser.Valid
Wasp.Analyzer.StdTypeDefinitions
Wasp.Analyzer.StdTypeDefinitions.App.Dependency
Wasp.Analyzer.StdTypeDefinitions.Entity
Expand Down Expand Up @@ -612,6 +613,7 @@ test-suite waspc-test
Analyzer.TestUtil
Analyzer.TypeChecker.InternalTest
Analyzer.TypeCheckerTest
Analyzer.ValidTest
AnalyzerTest
AppSpec.ValidTest
AppSpec.EntityTest
Expand Down
1 change: 1 addition & 0 deletions waspc/waspls/src/Wasp/LSP/Diagnostic.hs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ waspErrorAsPrettyEditorMessage = Text.pack . fst . W.getErrorMessageAndCtx

waspErrorSource :: W.AnalyzeError -> Text
waspErrorSource (W.ParseError _) = "parse"
waspErrorSource (W.ValidationError _) = "validate"
waspErrorSource (W.TypeError _) = "typecheck"
waspErrorSource (W.EvaluationError _) = "evaluate"

Expand Down
Loading