Adds support for view and type blocks (#2179)

This commit is contained in:
Mihovil Ilakovac 2024-07-15 13:57:53 +02:00 committed by GitHub
parent 24920c0bf5
commit e62f89a774
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 412 additions and 2 deletions

View File

@ -14,3 +14,11 @@
{=& . =}
{=/ enumSchemas =}
{=# viewSchemas =}
{=& . =}
{=/ viewSchemas =}
{=# typeSchemas =}
{=& . =}
{=/ typeSchemas =}

View File

@ -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 =

View File

@ -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]

View File

@ -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)

View File

@ -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)

View File

@ -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 ++ "}"

View File

@ -1,6 +1,7 @@
module Wasp.Psl.Parser.Model
( parseBody,
model,
body,
)
where

View File

@ -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
]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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