From 614b96ff058a7a8949c799c89b718377a3ae3673 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Fri, 27 Sep 2024 19:59:14 +0200 Subject: [PATCH 1/2] Validation for @default attribute on userEntity --- waspc/src/Wasp/AppSpec/Entity.hs | 13 ++++--------- waspc/src/Wasp/AppSpec/Valid.hs | 15 ++++++++++++--- waspc/test/AppSpec/EntityTest.hs | 14 +++----------- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/waspc/src/Wasp/AppSpec/Entity.hs b/waspc/src/Wasp/AppSpec/Entity.hs index ca5ed93a63..343dd3e3b8 100644 --- a/waspc/src/Wasp/AppSpec/Entity.hs +++ b/waspc/src/Wasp/AppSpec/Entity.hs @@ -7,8 +7,6 @@ module Wasp.AppSpec.Entity getPslModelBody, getIdField, getIdBlockAttribute, - isFieldUnique, - -- only for testing: doesFieldHaveAttribute, ) where @@ -54,18 +52,15 @@ getPslModelBody = pslModelBody getIdField :: Entity -> Maybe Psl.Model.Field getIdField = findIdField . getPslModelBody -isFieldUnique :: String -> Entity -> Maybe Bool -isFieldUnique fieldName = doesFieldHaveAttribute fieldName "unique" - -doesFieldHaveAttribute :: String -> String -> Entity -> Maybe Bool -doesFieldHaveAttribute fieldName attrName entity = +doesFieldHaveAttribute :: Entity -> String -> String -> Maybe Bool +doesFieldHaveAttribute entity attrName fieldName = doesPslFieldHaveAttribute attrName <$> findPslFieldByName fieldName entity findPslFieldByName :: String -> Entity -> Maybe Psl.Model.Field findPslFieldByName fieldName Entity {pslModelBody = Psl.Model.Body elements} = - find isField [field | (Psl.Model.ElementField field) <- elements] + find isTargetField [field | (Psl.Model.ElementField field) <- elements] where - isField Psl.Model.Field {_name = name} = name == fieldName + isTargetField Psl.Model.Field {_name = name} = name == fieldName getIdBlockAttribute :: Entity -> Maybe Psl.Attribute.Attribute getIdBlockAttribute = findIdBlockAttribute . getPslModelBody diff --git a/waspc/src/Wasp/AppSpec/Valid.hs b/waspc/src/Wasp/AppSpec/Valid.hs index 264f35be04..faef16fb3c 100644 --- a/waspc/src/Wasp/AppSpec/Valid.hs +++ b/waspc/src/Wasp/AppSpec/Valid.hs @@ -31,6 +31,7 @@ import qualified Wasp.AppSpec.App.Wasp as Wasp import Wasp.AppSpec.Core.Decl (takeDecls) import Wasp.AppSpec.Core.IsDecl (IsDecl) import qualified Wasp.AppSpec.Crud as AS.Crud +import Wasp.AppSpec.Entity (doesFieldHaveAttribute) import qualified Wasp.AppSpec.Entity as Entity import qualified Wasp.AppSpec.Entity.Field as Entity.Field import qualified Wasp.AppSpec.Operation as AS.Operation @@ -128,12 +129,20 @@ validateUserEntity spec = case App.auth (snd $ getApp spec) of Nothing -> [] Just auth -> - [ GenericValidationError $ "Entity '" ++ userEntityName ++ "' (referenced by app.auth.userEntity) must have an ID field (specified with the '@id' attribute)" - | isNothing idFieldType - ] + concat + [ [ GenericValidationError $ "Entity '" ++ userEntityName ++ "' (referenced by app.auth.userEntity) must have an ID field (specified with the '@id' attribute)" + | isNothing idFieldType + ], + [ GenericValidationError $ "Entity '" ++ userEntityName ++ "' (referenced by app.auth.userEntity) must have an ID field (specified with the '@id' attribute) with a default value" + | doesIdFieldHaveDefaultAttribute /= Just True + ] + ] where idFieldType = Entity.getIdField userEntity + doesIdFieldHaveDefaultAttribute = + idFieldType >>= doesFieldHaveAttribute userEntity "default" . Psl.Model._name + (userEntityName, userEntity) = AS.resolveRef spec (Auth.userEntity auth) validateAppAuthIsSetIfAnyPageRequiresAuth :: AppSpec -> [ValidationError] diff --git a/waspc/test/AppSpec/EntityTest.hs b/waspc/test/AppSpec/EntityTest.hs index 99c5036ecd..e694d4aae6 100644 --- a/waspc/test/AppSpec/EntityTest.hs +++ b/waspc/test/AppSpec/EntityTest.hs @@ -14,21 +14,13 @@ spec_AppSpecEntityTest = do it "Returns Nothing if primary field doesn't exist" $ do getIdField entityWithoutIdField `shouldBe` Nothing - describe "isFieldUnique" $ do - it "Returns Nothing if the field doesn't exist on the entity" $ do - Entity.isFieldUnique "nonExistingField" entityWithoutIdField `shouldBe` Nothing - it "Returns Just False if the field exists on the entity but isn't unique" $ do - Entity.isFieldUnique "description" entityWithIdField `shouldBe` Just False - it "Returns Just True if the field exists and is unique" $ do - Entity.isFieldUnique "id" entityWithIdField `shouldBe` Just True - describe "doesFieldHaveAttribute" $ do it "Returns Nothing if the field doesn't exist on the entity" $ do - Entity.doesFieldHaveAttribute "nonExistingField" "unique" entityWithoutIdField `shouldBe` Nothing + Entity.doesFieldHaveAttribute entityWithoutIdField "unique" "nonExistingField" `shouldBe` Nothing it "Returns Just False if the field exists on the entity but doesn't have the required attribute" $ do - Entity.doesFieldHaveAttribute "description" "id" entityWithIdField `shouldBe` Just False + Entity.doesFieldHaveAttribute entityWithIdField "id" "description" `shouldBe` Just False it "Returns Just True if the field exists on the entity and has the required attribute" $ do - Entity.doesFieldHaveAttribute "id" "id" entityWithIdField `shouldBe` Just True + Entity.doesFieldHaveAttribute entityWithIdField "id" "id" `shouldBe` Just True where entityWithIdField = Entity.makeEntity $ From 1c3e228beb31d2069028828d1a07eedcf9a6e610 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Sat, 28 Sep 2024 04:12:55 +0200 Subject: [PATCH 2/2] Refactor --- waspc/src/Wasp/AppSpec/Entity.hs | 14 +----------- waspc/src/Wasp/AppSpec/Valid.hs | 24 +++++++++------------ waspc/test/AppSpec/EntityTest.hs | 8 ------- waspc/test/AppSpec/ValidTest.hs | 37 ++++++++++++++++++++++++++++---- 4 files changed, 44 insertions(+), 39 deletions(-) diff --git a/waspc/src/Wasp/AppSpec/Entity.hs b/waspc/src/Wasp/AppSpec/Entity.hs index 343dd3e3b8..d6642b49ab 100644 --- a/waspc/src/Wasp/AppSpec/Entity.hs +++ b/waspc/src/Wasp/AppSpec/Entity.hs @@ -7,19 +7,17 @@ module Wasp.AppSpec.Entity getPslModelBody, getIdField, getIdBlockAttribute, - doesFieldHaveAttribute, ) where import Data.Aeson (FromJSON (parseJSON)) import Data.Data (Data) -import Data.List (find) import Wasp.AppSpec.Core.IsDecl (IsDecl) import Wasp.AppSpec.Entity.Field (Field) import qualified Wasp.AppSpec.Entity.Field as Field import qualified Wasp.Psl.Ast.Attribute as Psl.Attribute import qualified Wasp.Psl.Ast.Model as Psl.Model -import Wasp.Psl.Util (doesPslFieldHaveAttribute, findIdBlockAttribute, findIdField) +import Wasp.Psl.Util (findIdBlockAttribute, findIdField) data Entity = Entity { fields :: ![Field], @@ -52,15 +50,5 @@ getPslModelBody = pslModelBody getIdField :: Entity -> Maybe Psl.Model.Field getIdField = findIdField . getPslModelBody -doesFieldHaveAttribute :: Entity -> String -> String -> Maybe Bool -doesFieldHaveAttribute entity attrName fieldName = - doesPslFieldHaveAttribute attrName <$> findPslFieldByName fieldName entity - -findPslFieldByName :: String -> Entity -> Maybe Psl.Model.Field -findPslFieldByName fieldName Entity {pslModelBody = Psl.Model.Body elements} = - find isTargetField [field | (Psl.Model.ElementField field) <- elements] - where - isTargetField Psl.Model.Field {_name = name} = name == fieldName - getIdBlockAttribute :: Entity -> Maybe Psl.Attribute.Attribute getIdBlockAttribute = findIdBlockAttribute . getPslModelBody diff --git a/waspc/src/Wasp/AppSpec/Valid.hs b/waspc/src/Wasp/AppSpec/Valid.hs index faef16fb3c..6368b895ef 100644 --- a/waspc/src/Wasp/AppSpec/Valid.hs +++ b/waspc/src/Wasp/AppSpec/Valid.hs @@ -31,7 +31,6 @@ import qualified Wasp.AppSpec.App.Wasp as Wasp import Wasp.AppSpec.Core.Decl (takeDecls) import Wasp.AppSpec.Core.IsDecl (IsDecl) import qualified Wasp.AppSpec.Crud as AS.Crud -import Wasp.AppSpec.Entity (doesFieldHaveAttribute) import qualified Wasp.AppSpec.Entity as Entity import qualified Wasp.AppSpec.Entity.Field as Entity.Field import qualified Wasp.AppSpec.Operation as AS.Operation @@ -41,6 +40,7 @@ import Wasp.Generator.Crud (crudDeclarationToOperationsList) import Wasp.Node.Version (oldestWaspSupportedNodeVersion) import qualified Wasp.Node.Version as V import qualified Wasp.Psl.Ast.Model as Psl.Model +import qualified Wasp.Psl.Util as Psl.Util import Wasp.Psl.Valid (getValidDbSystemFromPrismaSchema) import qualified Wasp.SemanticVersion as SV import qualified Wasp.SemanticVersion.VersionBound as SVB @@ -129,22 +129,18 @@ validateUserEntity spec = case App.auth (snd $ getApp spec) of Nothing -> [] Just auth -> - concat - [ [ GenericValidationError $ "Entity '" ++ userEntityName ++ "' (referenced by app.auth.userEntity) must have an ID field (specified with the '@id' attribute)" - | isNothing idFieldType - ], - [ GenericValidationError $ "Entity '" ++ userEntityName ++ "' (referenced by app.auth.userEntity) must have an ID field (specified with the '@id' attribute) with a default value" - | doesIdFieldHaveDefaultAttribute /= Just True - ] - ] + case Entity.getIdField userEntity of + Nothing -> [userEntityMissingIdFieldError] + Just idField -> + if Psl.Util.doesPslFieldHaveAttribute "default" idField + then [] + else [userEntityIdFieldMissingDefaultAttrError] where - idFieldType = Entity.getIdField userEntity - - doesIdFieldHaveDefaultAttribute = - idFieldType >>= doesFieldHaveAttribute userEntity "default" . Psl.Model._name - (userEntityName, userEntity) = AS.resolveRef spec (Auth.userEntity auth) + userEntityMissingIdFieldError = GenericValidationError $ "Entity '" ++ userEntityName ++ "' (referenced by app.auth.userEntity) must have an ID field (specified with the '@id' attribute)" + userEntityIdFieldMissingDefaultAttrError = GenericValidationError $ "Entity '" ++ userEntityName ++ "' (referenced by app.auth.userEntity) must have an ID field (specified with the '@id' attribute) with a default value" + validateAppAuthIsSetIfAnyPageRequiresAuth :: AppSpec -> [ValidationError] validateAppAuthIsSetIfAnyPageRequiresAuth spec = [ GenericValidationError diff --git a/waspc/test/AppSpec/EntityTest.hs b/waspc/test/AppSpec/EntityTest.hs index e694d4aae6..a383956617 100644 --- a/waspc/test/AppSpec/EntityTest.hs +++ b/waspc/test/AppSpec/EntityTest.hs @@ -13,14 +13,6 @@ spec_AppSpecEntityTest = do getIdField entityWithIdField `shouldBe` Just idField it "Returns Nothing if primary field doesn't exist" $ do getIdField entityWithoutIdField `shouldBe` Nothing - - describe "doesFieldHaveAttribute" $ do - it "Returns Nothing if the field doesn't exist on the entity" $ do - Entity.doesFieldHaveAttribute entityWithoutIdField "unique" "nonExistingField" `shouldBe` Nothing - it "Returns Just False if the field exists on the entity but doesn't have the required attribute" $ do - Entity.doesFieldHaveAttribute entityWithIdField "id" "description" `shouldBe` Just False - it "Returns Just True if the field exists on the entity and has the required attribute" $ do - Entity.doesFieldHaveAttribute entityWithIdField "id" "id" `shouldBe` Just True where entityWithIdField = Entity.makeEntity $ diff --git a/waspc/test/AppSpec/ValidTest.hs b/waspc/test/AppSpec/ValidTest.hs index 7859f655ce..6b69285987 100644 --- a/waspc/test/AppSpec/ValidTest.hs +++ b/waspc/test/AppSpec/ValidTest.hs @@ -31,6 +31,7 @@ import qualified Wasp.AppSpec.Page as AS.Page import qualified Wasp.AppSpec.Query as AS.Query import qualified Wasp.AppSpec.Route as AS.Route import qualified Wasp.AppSpec.Valid as ASV +import qualified Wasp.Psl.Ast.Argument as Psl.Argument import qualified Wasp.Psl.Ast.Attribute as Psl.Attribute import qualified Wasp.Psl.Ast.Model as Psl.Model import qualified Wasp.SemanticVersion as SV @@ -260,22 +261,44 @@ spec_AppSpecValid = do AS.Decl.makeDecl userEntityName (userEntity :: AS.Entity.Entity) ] } - let invalidUserEntity = + let invalidUserEntityWithoutIdField = AS.Entity.makeEntity ( Psl.Model.Body [] ) + let invalidUserEntityWithoutDefaultAttr = + AS.Entity.makeEntity + ( Psl.Model.Body + [ Psl.Model.ElementField $ + Psl.Model.Field + { Psl.Model._name = "id", + Psl.Model._type = Psl.Model.String, + Psl.Model._typeModifiers = [], + Psl.Model._attrs = + [ Psl.Attribute.Attribute + { Psl.Attribute._attrName = "id", + Psl.Attribute._attrArgs = [] + } + ] + } + ] + ) it "returns no error if app.auth is not set, regardless of shape of user entity" $ do - ASV.validateAppSpec (makeSpec Nothing invalidUserEntity) `shouldBe` [] + ASV.validateAppSpec (makeSpec Nothing invalidUserEntityWithoutIdField) `shouldBe` [] ASV.validateAppSpec (makeSpec Nothing validUserEntity) `shouldBe` [] it "returns no error if app.auth is set and user entity is of valid shape" $ do ASV.validateAppSpec (makeSpec (Just validAppAuth) validUserEntity) `shouldBe` [] - it "returns an error if app.auth is set and user entity is of invalid shape" $ do - ASV.validateAppSpec (makeSpec (Just validAppAuth) invalidUserEntity) + it "returns an error if app.auth is set and user entity wihtout an ID field" $ do + ASV.validateAppSpec (makeSpec (Just validAppAuth) invalidUserEntityWithoutIdField) `shouldBe` [ Valid.GenericValidationError "Entity 'User' (referenced by app.auth.userEntity) must have an ID field (specified with the '@id' attribute)" ] + it "returns an error if app.auth is set and user entity with an ID field that is missing the default attr" $ do + ASV.validateAppSpec (makeSpec (Just validAppAuth) invalidUserEntityWithoutDefaultAttr) + `shouldBe` [ Valid.GenericValidationError + "Entity 'User' (referenced by app.auth.userEntity) must have an ID field (specified with the '@id' attribute) with a default value" + ] describe "should validate email sender setup." $ do let emailAuthConfig = @@ -414,6 +437,12 @@ spec_AppSpecValid = do [ Psl.Attribute.Attribute { Psl.Attribute._attrName = "id", Psl.Attribute._attrArgs = [] + }, + Psl.Attribute.Attribute + { Psl.Attribute._attrName = "default", + Psl.Attribute._attrArgs = + [ Psl.Argument.ArgUnnamed (Psl.Argument.FuncExpr "autoincrement" []) + ] } ] }