diff --git a/waspc/data/Generator/templates/db/schema.prisma b/waspc/data/Generator/templates/db/schema.prisma index c5e1c2631a..18d19b22be 100644 --- a/waspc/data/Generator/templates/db/schema.prisma +++ b/waspc/data/Generator/templates/db/schema.prisma @@ -14,3 +14,11 @@ {=& . =} {=/ enumSchemas =} +{=# viewSchemas =} +{=& . =} + +{=/ viewSchemas =} +{=# typeSchemas =} +{=& . =} + +{=/ typeSchemas =} diff --git a/waspc/src/Wasp/Generator/DbGenerator.hs b/waspc/src/Wasp/Generator/DbGenerator.hs index fe72a770f9..ac9b0c6a35 100644 --- a/waspc/src/Wasp/Generator/DbGenerator.hs +++ b/waspc/src/Wasp/Generator/DbGenerator.hs @@ -74,6 +74,8 @@ genPrismaSchema spec = do object [ "modelSchemas" .= (entityToPslModelSchema <$> entities), "enumSchemas" .= enumSchemas, + "viewSchemas" .= viewSchemas, + "typeSchemas" .= typeSchemas, "datasourceSchema" .= generateConfigBlockSchema (getDatasource datasourceProvider), "generatorSchemas" .= (generateConfigBlockSchema <$> generators) ] @@ -85,6 +87,10 @@ genPrismaSchema spec = do enumSchemas = Psl.Generator.Schema.generateSchemaBlock . Psl.Schema.EnumBlock <$> Psl.Schema.getEnums prismaSchemaAst + viewSchemas = Psl.Generator.Schema.generateSchemaBlock . Psl.Schema.ViewBlock <$> Psl.Schema.getViews prismaSchemaAst + + typeSchemas = Psl.Generator.Schema.generateSchemaBlock . Psl.Schema.TypeBlock <$> Psl.Schema.getTypes prismaSchemaAst + generateConfigBlockSchema = Psl.Generator.Schema.generateSchemaBlock . Psl.Schema.ConfigBlock getDatasource datasourceProvider = diff --git a/waspc/src/Wasp/Psl/Ast/Schema.hs b/waspc/src/Wasp/Psl/Ast/Schema.hs index 33fb98ad4e..df551c9ea8 100644 --- a/waspc/src/Wasp/Psl/Ast/Schema.hs +++ b/waspc/src/Wasp/Psl/Ast/Schema.hs @@ -2,6 +2,8 @@ module Wasp.Psl.Ast.Schema ( Schema (..), Block (..), getModels, + getViews, + getTypes, getEnums, getDatasources, getGenerators, @@ -12,6 +14,8 @@ import Wasp.Psl.Ast.ConfigBlock (ConfigBlock) import qualified Wasp.Psl.Ast.ConfigBlock as Psl.ConfigBlock import Wasp.Psl.Ast.Enum (Enum) import Wasp.Psl.Ast.Model (Model) +import Wasp.Psl.Ast.Type (Type) +import Wasp.Psl.Ast.View (View) import Prelude hiding (Enum) data Schema = Schema [Block] @@ -19,6 +23,8 @@ data Schema = Schema [Block] data Block = ModelBlock Model + | ViewBlock View + | TypeBlock Type | EnumBlock Enum | ConfigBlock ConfigBlock deriving (Show, Eq) @@ -26,6 +32,12 @@ data Block getModels :: Schema -> [Model] getModels (Schema blocks) = [model | ModelBlock model <- blocks] +getViews :: Schema -> [View] +getViews (Schema blocks) = [view | ViewBlock view <- blocks] + +getTypes :: Schema -> [Type] +getTypes (Schema blocks) = [typeBlock | TypeBlock typeBlock <- blocks] + getEnums :: Schema -> [Enum] getEnums (Schema blocks) = [enum | EnumBlock enum <- blocks] diff --git a/waspc/src/Wasp/Psl/Ast/Type.hs b/waspc/src/Wasp/Psl/Ast/Type.hs new file mode 100644 index 0000000000..f42654a5f9 --- /dev/null +++ b/waspc/src/Wasp/Psl/Ast/Type.hs @@ -0,0 +1,13 @@ +module Wasp.Psl.Ast.Type + ( Type (..), + ) +where + +import Wasp.Psl.Ast.Common (Name) +import Wasp.Psl.Ast.Model (Body) + +data Type + = Type + Name + Body + deriving (Show, Eq) diff --git a/waspc/src/Wasp/Psl/Ast/View.hs b/waspc/src/Wasp/Psl/Ast/View.hs new file mode 100644 index 0000000000..ca1df04e35 --- /dev/null +++ b/waspc/src/Wasp/Psl/Ast/View.hs @@ -0,0 +1,13 @@ +module Wasp.Psl.Ast.View + ( View (..), + ) +where + +import Wasp.Psl.Ast.Common (Name) +import Wasp.Psl.Ast.Model (Body) + +data View + = View + Name + Body + deriving (Show, Eq) diff --git a/waspc/src/Wasp/Psl/Generator/Schema.hs b/waspc/src/Wasp/Psl/Generator/Schema.hs index c37ff7ba50..b312344d4d 100644 --- a/waspc/src/Wasp/Psl/Generator/Schema.hs +++ b/waspc/src/Wasp/Psl/Generator/Schema.hs @@ -7,6 +7,8 @@ import qualified Wasp.Psl.Ast.ConfigBlock as Psl.ConfigBlock import qualified Wasp.Psl.Ast.Enum as Psl.Enum import qualified Wasp.Psl.Ast.Model as Psl.Model import qualified Wasp.Psl.Ast.Schema as Psl.Schema +import qualified Wasp.Psl.Ast.Type as Psl.Type +import qualified Wasp.Psl.Ast.View as Psl.View import Wasp.Psl.Generator.Common (PslSource) import Wasp.Psl.Generator.ConfigBlock (generateConfigBlockKeyValuePairs) import Wasp.Psl.Generator.Enum (generateEnumBody) @@ -15,6 +17,8 @@ import Wasp.Psl.Generator.Model (generateModelBody) generateSchemaBlock :: Psl.Schema.Block -> PslSource generateSchemaBlock = \case Psl.Schema.ModelBlock (Psl.Model.Model name body) -> "model " ++ name ++ " {\n" ++ generateModelBody body ++ "}" + Psl.Schema.ViewBlock (Psl.View.View name body) -> "view " ++ name ++ " {\n" ++ generateModelBody body ++ "}" + Psl.Schema.TypeBlock (Psl.Type.Type name body) -> "type " ++ name ++ " {\n" ++ generateModelBody body ++ "}" Psl.Schema.EnumBlock (Psl.Enum.Enum name values) -> "enum " ++ name ++ " {\n" ++ generateEnumBody values ++ "}" Psl.Schema.ConfigBlock (Psl.ConfigBlock.ConfigBlock Psl.ConfigBlock.Datasource name content) -> "datasource " ++ name ++ " {\n" ++ generateConfigBlockKeyValuePairs content ++ "}" Psl.Schema.ConfigBlock (Psl.ConfigBlock.ConfigBlock Psl.ConfigBlock.Generator name content) -> "generator " ++ name ++ " {\n" ++ generateConfigBlockKeyValuePairs content ++ "}" diff --git a/waspc/src/Wasp/Psl/Parser/Model.hs b/waspc/src/Wasp/Psl/Parser/Model.hs index 87d25fe7e7..d75cad86ff 100644 --- a/waspc/src/Wasp/Psl/Parser/Model.hs +++ b/waspc/src/Wasp/Psl/Parser/Model.hs @@ -1,6 +1,7 @@ module Wasp.Psl.Parser.Model ( parseBody, model, + body, ) where diff --git a/waspc/src/Wasp/Psl/Parser/Schema.hs b/waspc/src/Wasp/Psl/Parser/Schema.hs index 19a1a37eb3..f9e4db01c0 100644 --- a/waspc/src/Wasp/Psl/Parser/Schema.hs +++ b/waspc/src/Wasp/Psl/Parser/Schema.hs @@ -17,6 +17,8 @@ import Wasp.Psl.Parser.Common (SourceCode, whiteSpace) import Wasp.Psl.Parser.ConfigBlock (configBlock) import Wasp.Psl.Parser.Enum (enum) import Wasp.Psl.Parser.Model (model) +import Wasp.Psl.Parser.Type (typeBlock) +import Wasp.Psl.Parser.View (view) parsePrismaSchema :: SourceCode -> Either Parsec.ParseError Psl.Schema.Schema parsePrismaSchema = Parsec.parse schema "" @@ -32,6 +34,8 @@ schema = do many $ choice [ Psl.Schema.ModelBlock <$> model, + Psl.Schema.ViewBlock <$> view, + Psl.Schema.TypeBlock <$> typeBlock, Psl.Schema.EnumBlock <$> enum, Psl.Schema.ConfigBlock <$> configBlock ] diff --git a/waspc/src/Wasp/Psl/Parser/Type.hs b/waspc/src/Wasp/Psl/Parser/Type.hs new file mode 100644 index 0000000000..56f706d95b --- /dev/null +++ b/waspc/src/Wasp/Psl/Parser/Type.hs @@ -0,0 +1,32 @@ +module Wasp.Psl.Parser.Type + ( typeBlock, + ) +where + +import Text.Parsec.String (Parser) +import qualified Wasp.Psl.Ast.Type as Psl.Type +import Wasp.Psl.Parser.Common + ( braces, + identifier, + reserved, + ) +import Wasp.Psl.Parser.Model (body) + +-- | Parses PSL (Prisma Schema Language) type. +-- Example of PSL type: +-- type Photo { +-- height Int @default(200) +-- width Int @default(100) +-- url String +-- } +-- +-- PSL type blocks have the same syntax as +-- Prisma model blocks, but they are prefixed with +-- `type` keyword. That's why we are reusing the +-- `body` parser from `Model` to parse the body +-- of the type block. +typeBlock :: Parser Psl.Type.Type +typeBlock = do + reserved "type" + typeName <- identifier + Psl.Type.Type typeName <$> braces body diff --git a/waspc/src/Wasp/Psl/Parser/View.hs b/waspc/src/Wasp/Psl/Parser/View.hs new file mode 100644 index 0000000000..e81d03095a --- /dev/null +++ b/waspc/src/Wasp/Psl/Parser/View.hs @@ -0,0 +1,31 @@ +module Wasp.Psl.Parser.View + ( view, + ) +where + +import Text.Parsec.String (Parser) +import qualified Wasp.Psl.Ast.View as Psl.View +import Wasp.Psl.Parser.Common + ( braces, + identifier, + reserved, + ) +import Wasp.Psl.Parser.Model (body) + +-- | Parses PSL (Prisma Schema Language) view. +-- Example of PSL view: +-- view User { +-- id Int @id +-- name String +-- } +-- +-- PSL view blocks have the same syntax as +-- Prisma model blocks, but they are prefixed with +-- `view` keyword. That's why we are reusing the +-- `body` parser from `Model` to parse the body +-- of the type block. +view :: Parser Psl.View.View +view = do + reserved "view" + viewName <- identifier + Psl.View.View viewName <$> braces body diff --git a/waspc/test/Psl/Generator/SchemaTest.hs b/waspc/test/Psl/Generator/SchemaTest.hs index 9007b5873a..29e984ada7 100644 --- a/waspc/test/Psl/Generator/SchemaTest.hs +++ b/waspc/test/Psl/Generator/SchemaTest.hs @@ -12,6 +12,8 @@ import qualified Wasp.Psl.Ast.ConfigBlock as Psl.ConfigBlock import qualified Wasp.Psl.Ast.Enum as Psl.Enum import qualified Wasp.Psl.Ast.Model as Psl.Model import qualified Wasp.Psl.Ast.Schema as Psl.Schema +import qualified Wasp.Psl.Ast.Type as Psl.Type +import qualified Wasp.Psl.Ast.View as Psl.View import Wasp.Psl.Generator.Schema (generateSchemaBlock) import qualified Wasp.Psl.Parser.Schema as Psl.Parser.Schema @@ -24,6 +26,8 @@ instance Arbitrary Psl.Schema.Block where arbitrary = oneof [ Psl.Schema.ModelBlock <$> arbitrary, + Psl.Schema.ViewBlock <$> arbitrary, + Psl.Schema.TypeBlock <$> arbitrary, Psl.Schema.EnumBlock <$> arbitrary, Psl.Schema.ConfigBlock <$> arbitrary ] @@ -34,6 +38,12 @@ instance Arbitrary Psl.Schema.Schema where instance Arbitrary Psl.Model.Model where arbitrary = Psl.Model.Model <$> arbitraryIdentifier <*> arbitrary +instance Arbitrary Psl.View.View where + arbitrary = Psl.View.View <$> arbitraryIdentifier <*> arbitrary + +instance Arbitrary Psl.Type.Type where + arbitrary = Psl.Type.Type <$> arbitraryIdentifier <*> arbitrary + instance Arbitrary Psl.Model.Body where arbitrary = do fieldElement <- Psl.Model.ElementField <$> arbitrary diff --git a/waspc/test/Psl/Parser/SchemaTest.hs b/waspc/test/Psl/Parser/SchemaTest.hs index e0b579dd6b..ab61f9f4e0 100644 --- a/waspc/test/Psl/Parser/SchemaTest.hs +++ b/waspc/test/Psl/Parser/SchemaTest.hs @@ -11,6 +11,8 @@ import qualified Wasp.Psl.Ast.ConfigBlock as Psl.ConfigBlock import qualified Wasp.Psl.Ast.Enum as Psl.Enum import qualified Wasp.Psl.Ast.Model as Psl.Model import qualified Wasp.Psl.Ast.Schema as Psl.Schema +import qualified Wasp.Psl.Ast.Type as Psl.Type +import qualified Wasp.Psl.Ast.View as Psl.View import qualified Wasp.Psl.Parser.Schema as Psl.Parser spec_parsePslSchema :: Spec @@ -84,6 +86,21 @@ spec_parsePslSchema = do } + view UserInfo { + id Int? + email String? + name String? + bio String? + + @@ignore + } + + type Photo { + height Int @default(200) + width Int @default(100) + url String + } + // Some comments at the end |] expectedAst = @@ -291,7 +308,70 @@ spec_parsePslSchema = do "Role" [ Psl.Enum.ElementValue "USER" [], Psl.Enum.ElementValue "ADMIN" [] - ] + ], + Psl.Schema.ViewBlock $ + Psl.View.View + "UserInfo" + ( Psl.Model.Body + [ Psl.Model.ElementField $ + Psl.Model.Field + "id" + Psl.Model.Int + [Psl.Model.Optional] + [], + Psl.Model.ElementField $ + Psl.Model.Field + "email" + Psl.Model.String + [Psl.Model.Optional] + [], + Psl.Model.ElementField $ + Psl.Model.Field + "name" + Psl.Model.String + [Psl.Model.Optional] + [], + Psl.Model.ElementField $ + Psl.Model.Field + "bio" + Psl.Model.String + [Psl.Model.Optional] + [], + Psl.Model.ElementBlockAttribute $ + Psl.Attribute.Attribute "ignore" [] + ] + ), + Psl.Schema.TypeBlock $ + Psl.Type.Type + "Photo" + ( Psl.Model.Body + [ Psl.Model.ElementField $ + Psl.Model.Field + "height" + Psl.Model.Int + [] + [ Psl.Attribute.Attribute + "default" + [ Psl.Argument.ArgUnnamed $ Psl.Argument.NumberExpr "200" + ] + ], + Psl.Model.ElementField $ + Psl.Model.Field + "width" + Psl.Model.Int + [] + [ Psl.Attribute.Attribute + "default" + [Psl.Argument.ArgUnnamed $ Psl.Argument.NumberExpr "100"] + ], + Psl.Model.ElementField $ + Psl.Model.Field + "url" + Psl.Model.String + [] + [] + ] + ) ] it "Prisma file is correctly parsed" $ do diff --git a/waspc/test/Psl/Parser/TypeTest.hs b/waspc/test/Psl/Parser/TypeTest.hs new file mode 100644 index 0000000000..465ba04e6a --- /dev/null +++ b/waspc/test/Psl/Parser/TypeTest.hs @@ -0,0 +1,91 @@ +module Psl.Parser.TypeTest where + +import qualified Data.Text as T +import NeatInterpolation (trimming) +import Test.Tasty.Hspec +import qualified Text.Parsec as Parsec +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.Psl.Ast.Type as Psl.Type +import qualified Wasp.Psl.Parser.Type as Psl.Parser + +spec_parsePslType :: Spec +spec_parsePslType = do + describe "Type parsing" $ do + it "Basic example" $ do + let source = + T.unpack + [trimming| + type Photo { + height Int @default(200) + width Int @default(100) + url String + } + |] + expectedAst = + Psl.Type.Type + "Photo" + ( Psl.Model.Body + [ Psl.Model.ElementField + ( Psl.Model.Field + "height" + Psl.Model.Int + [] + [ Psl.Attribute.Attribute + "default" + [ Psl.Argument.ArgUnnamed (Psl.Argument.NumberExpr "200") + ] + ] + ), + Psl.Model.ElementField + ( Psl.Model.Field + "width" + Psl.Model.Int + [] + [ Psl.Attribute.Attribute + "default" + [ Psl.Argument.ArgUnnamed (Psl.Argument.NumberExpr "100") + ] + ] + ), + Psl.Model.ElementField + ( Psl.Model.Field "url" Psl.Model.String [] [] + ) + ] + ) + + Parsec.parse Psl.Parser.typeBlock "" source `shouldBe` Right expectedAst + + it "Commented out fields" $ do + let source = + T.unpack + [trimming| + type Photo { + height Int @default(200) + // width Int @default(100) + url String + } + |] + expectedAst = + Psl.Type.Type + "Photo" + ( Psl.Model.Body + [ Psl.Model.ElementField + ( Psl.Model.Field + "height" + Psl.Model.Int + [] + [ Psl.Attribute.Attribute + "default" + [ Psl.Argument.ArgUnnamed (Psl.Argument.NumberExpr "200") + ] + ] + ), + Psl.Model.ElementField + ( Psl.Model.Field "url" Psl.Model.String [] [] + ) + ] + ) + + Parsec.parse Psl.Parser.typeBlock "" source `shouldBe` Right expectedAst diff --git a/waspc/test/Psl/Parser/ViewTest.hs b/waspc/test/Psl/Parser/ViewTest.hs new file mode 100644 index 0000000000..367301e8e0 --- /dev/null +++ b/waspc/test/Psl/Parser/ViewTest.hs @@ -0,0 +1,99 @@ +module Psl.Parser.ViewTest where + +import qualified Data.Text as T +import NeatInterpolation (trimming) +import Test.Tasty.Hspec +import qualified Text.Parsec as Parsec +import qualified Wasp.Psl.Ast.Attribute as Psl.Attribute +import qualified Wasp.Psl.Ast.Model as Psl.Model +import qualified Wasp.Psl.Ast.View as Psl.View +import qualified Wasp.Psl.Parser.View as Psl.Parser + +spec_parsePslView :: Spec +spec_parsePslView = do + describe "View parsing" $ do + it "Basic example" $ do + let source = + T.unpack + [trimming| + view UserInfo { + id Int? + email String? + name String? + bio String? + + @@ignore + } + |] + expectedAst = + Psl.View.View + "UserInfo" + ( Psl.Model.Body + [ Psl.Model.ElementField + ( Psl.Model.Field "id" Psl.Model.Int [Psl.Model.Optional] [] + ), + Psl.Model.ElementField + ( Psl.Model.Field "email" Psl.Model.String [Psl.Model.Optional] [] + ), + Psl.Model.ElementField + ( Psl.Model.Field "name" Psl.Model.String [Psl.Model.Optional] [] + ), + Psl.Model.ElementField + ( Psl.Model.Field "bio" Psl.Model.String [Psl.Model.Optional] [] + ), + Psl.Model.ElementBlockAttribute + ( Psl.Attribute.Attribute "ignore" [] + ) + ] + ) + + Parsec.parse Psl.Parser.view "" source `shouldBe` Right expectedAst + + it "Commented out fields" $ do + let source = + T.unpack + [trimming| + view UserInfo { + id Int? + email String? + // name String? + bio String? + + @@ignore + } + |] + expectedAst = + Psl.View.View + "UserInfo" + ( Psl.Model.Body + [ Psl.Model.ElementField + ( Psl.Model.Field + "id" + Psl.Model.Int + [ Psl.Model.Optional + ] + [] + ), + Psl.Model.ElementField + ( Psl.Model.Field + "email" + Psl.Model.String + [ Psl.Model.Optional + ] + [] + ), + Psl.Model.ElementField + ( Psl.Model.Field + "bio" + Psl.Model.String + [ Psl.Model.Optional + ] + [] + ), + Psl.Model.ElementBlockAttribute + ( Psl.Attribute.Attribute "ignore" [] + ) + ] + ) + + Parsec.parse Psl.Parser.view "" source `shouldBe` Right expectedAst diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index fe0ea50f57..aa60d6ab97 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -372,6 +372,8 @@ library Wasp.Psl.Ast.Model Wasp.Psl.Ast.Enum Wasp.Psl.Ast.ConfigBlock + Wasp.Psl.Ast.Type + Wasp.Psl.Ast.View Wasp.Psl.Format Wasp.Psl.Generator.Argument Wasp.Psl.Generator.Attribute @@ -387,6 +389,8 @@ library Wasp.Psl.Parser.Enum Wasp.Psl.Parser.Model Wasp.Psl.Parser.Schema + Wasp.Psl.Parser.Type + Wasp.Psl.Parser.View Wasp.Psl.Util Wasp.Psl.Valid Wasp.SemanticVersion @@ -638,6 +642,8 @@ test-suite waspc-test Psl.Parser.ConfigBlockTest Psl.Parser.EnumTest Psl.Parser.ModelTest + Psl.Parser.TypeTest + Psl.Parser.ViewTest Psl.Parser.SchemaTest Psl.ValidTest Test.Util @@ -739,4 +745,4 @@ test-suite e2e-test Tests.WaspJobTest Tests.WaspMigrateTest Tests.WaspNewTest - Tests.WaspComplexTest + Tests.WaspComplexTest \ No newline at end of file