Skip to content

Commit 10515fc

Browse files
committed
fix issues found in testing
1 parent 5bfce3a commit 10515fc

File tree

3 files changed

+90
-92
lines changed

3 files changed

+90
-92
lines changed

persistent-test/src/HtmlTest.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Init
1515
share [mkPersist persistSettings { mpsGeneric = True }, mkMigrate "htmlMigrate"] [persistLowerCase|
1616
HtmlTable
1717
html Html
18-
deriving
18+
deriving Show
1919
|]
2020

2121
cleanDB :: Runner backend m => ReaderT backend m ()

persistent/Database/Persist/Quasi/Internal/ModelParser.hs

Lines changed: 40 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ module Database.Persist.Quasi.Internal.ModelParser
1717
, EntityField (..)
1818
, entityField
1919
, entityFieldContent
20+
, FieldName (..)
21+
, fieldName
2022
, ParsedEntityDef (..)
2123
, parseSource
2224
, memberEntityFields
@@ -257,7 +259,7 @@ data SourceLoc = SourceLoc
257259
}
258260
deriving (Show, Lift)
259261

260-
-- | An attribute of an entity field definition.
262+
-- | An attribute of an entity field definition or a directive.
261263
--
262264
-- @since 2.17.1.0
263265
data Attribute
@@ -268,16 +270,6 @@ data Attribute
268270
Quotation Text
269271
deriving (Eq, Ord, Show)
270272

271-
-- | An argument to an entity field directive.
272-
--
273-
-- @since 2.17.1.0
274-
data DirectiveArgument
275-
= -- This is too unstructured. We should rework directive parsing and make this smarter.
276-
DText Text
277-
| -- | Quoted directive arguments are deprecated since 2.17.1.0.
278-
DQuotation Text
279-
deriving (Eq, Ord, Show)
280-
281273
-- | The name of an entity block or extra block.
282274
--
283275
-- @since 2.17.1.0
@@ -308,7 +300,9 @@ attributeContent = \case
308300
--
309301
-- @since 2.17.1.0
310302
directiveContent :: Directive -> [Text]
311-
directiveContent d = directiveArgumentContent <$> directiveArguments d
303+
directiveContent d =
304+
[directiveNameContent $ directiveName d]
305+
<> (attributeContent <$> directiveAttributes d)
312306

313307
entityFieldContent :: EntityField -> [Text]
314308
entityFieldContent f =
@@ -320,6 +314,9 @@ entityFieldContent f =
320314
blockKeyContent :: BlockKey -> Text
321315
blockKeyContent (BlockKey t) = t
322316

317+
directiveNameContent :: DirectiveName -> Text
318+
directiveNameContent (DirectiveName t) = t
319+
323320
-- | Generates the field name of an EntityField, accompanied by
324321
-- its strictness sigil, if one is present.
325322
-- This is only needed temporarily, and can eventually be refactored away.
@@ -342,7 +339,7 @@ commentContent = \case
342339
DocComment s -> s
343340

344341
quotedAttributeErrorMessage :: String
345-
quotedAttributeErrorMessage = "Unexpected quotation mark in entity field attribute"
342+
quotedAttributeErrorMessage = "Unexpected quotation mark in field or directive attribute"
346343

347344
attribute :: Parser Attribute
348345
attribute = do
@@ -501,13 +498,18 @@ assignment = label "assignment expression" $ do
501498
[ quotation
502499
, sqlLiteral
503500
, parentheticalInner
501+
, try sqlFunctionApplication
504502
, some $ contentChar <|> char '(' <|> char ')'
505503
]
506504
pure $ Assignment (Text.pack lhs) (Text.pack rhs)
507505
where
508506
parentheticalInner = do
509507
str <- parenthetical'
510508
pure . init . drop 1 $ str
509+
sqlFunctionApplication = do
510+
fn <- some contentChar
511+
argString <- parentheticalInner
512+
pure $ mconcat [fn, "(", argString, ")"]
511513

512514
sqlTypeName :: Parser String
513515
sqlTypeName =
@@ -565,15 +567,18 @@ fieldStrictness = label "strictness sigil" $ do
565567
fieldName :: Parser FieldName
566568
fieldName = label "field name" $ do
567569
fl <- lowerChar
568-
rl <- many alphaNumChar
570+
rl <- many fieldNameChar
569571
pure . FieldName . Text.pack $ fl : rl
572+
where
573+
fieldNameChar =
574+
choice
575+
[ alphaNumChar
576+
, char '_'
577+
]
570578

571579
ptext :: Parser Attribute
572580
ptext = label "plain attribute" $ do
573-
str <- L.lexeme spaceConsumer $ do
574-
first <- alphaNumChar
575-
rest <- many contentChar
576-
pure (first : rest)
581+
str <- L.lexeme spaceConsumer $ some contentChar
577582
pure . PText . Text.pack $ str
578583

579584
data ParsedEntityDef = ParsedEntityDef
@@ -659,7 +664,10 @@ data FieldStrictness = Strict | Lazy
659664
deriving (Show)
660665

661666
newtype FieldName = FieldName Text
662-
deriving (Show)
667+
deriving (Show, Eq)
668+
669+
newtype DirectiveName = DirectiveName Text
670+
deriving (Show, Eq)
663671

664672
data EntityField = EntityField
665673
{ entityFieldDocCommentBlock :: Maybe DocCommentBlock
@@ -673,7 +681,8 @@ data EntityField = EntityField
673681

674682
data Directive = Directive
675683
{ directiveDocCommentBlock :: Maybe DocCommentBlock
676-
, directiveArguments :: [DirectiveArgument]
684+
, directiveName :: DirectiveName
685+
, directiveAttributes :: [Attribute]
677686
, directivePos :: SourcePos
678687
}
679688
deriving (Show)
@@ -829,92 +838,34 @@ entityField = do
829838
, entityFieldPos = pos
830839
}
831840

832-
directiveName :: Parser String
833-
directiveName =
841+
directiveNameP :: Parser DirectiveName
842+
directiveNameP =
834843
label "directive name" $
835-
choice
836-
[ string "deriving"
837-
, directiveName'
838-
]
844+
DirectiveName . Text.pack
845+
<$> choice
846+
[ string "deriving"
847+
, directiveName'
848+
]
839849
where
840850
directiveName' = do
841851
fl <- upperChar
842852
rl <- many alphaNumChar
843853
pure (fl : rl)
844854

845-
quotedArgumentErrorMessage :: String
846-
quotedArgumentErrorMessage = "Unexpected quotation mark in directive argument"
847-
848-
-- Parses an argument to an entity definition directive. It's somewhat naive about it,
849-
-- and we should refine this in the future.
850-
directiveArgument :: Parser DirectiveArgument
851-
directiveArgument = do
852-
quotedArgumentErrorLevel <- asks psQuotedArgumentErrorLevel
853-
tryOrReport
854-
quotedArgumentErrorLevel
855-
"Quoted directive arguments are deprecated since 2.17.1.0, and will be removed in or after 2.18.0.0"
856-
isQuotedArgumentError
857-
directiveArgument'
858-
(DQuotation . Text.pack <$> quotation)
859-
where
860-
isQuotedArgumentError (FancyError _ s) = s == Set.singleton (ErrorFail quotedArgumentErrorMessage)
861-
isQuotedArgumentError _ = False
862-
directiveArgument' = do
863-
q <- lookAhead (optional $ char '"')
864-
case q of
865-
Just _ -> fail quotedArgumentErrorMessage
866-
Nothing ->
867-
choice
868-
[ DText . Text.pack <$> parenthetical'
869-
, DText . Text.pack <$> some directiveArgumentChar
870-
]
871-
-- This big random-looking character class is a sign that the parser is too lax.
872-
-- When we improve directive parsing, we'll eliminate this.
873-
-- Note that this character class is not identical to the class parsed by the top-level function `contentChar`.
874-
directiveArgumentChar =
875-
choice
876-
[ alphaNumChar
877-
, char '.'
878-
, char '['
879-
, char ']'
880-
, char '_'
881-
, char '\''
882-
, char '!'
883-
, char '~'
884-
, char '-'
885-
, char ':'
886-
, char ','
887-
, char '='
888-
, do
889-
backslash <- char '\\'
890-
nextChar <- lookAhead anySingle
891-
if nextChar == '(' || nextChar == ')'
892-
then single nextChar
893-
else pure backslash
894-
]
895-
896-
-- | Converts a directive argument into a Text representation for second-stage
897-
-- parsing or presentation to the user
898-
--
899-
-- @since 2.17.1.0
900-
directiveArgumentContent :: DirectiveArgument -> Text
901-
directiveArgumentContent = \case
902-
DText t -> t
903-
DQuotation t -> t
904-
905855
directive :: Parser Member
906856
directive = do
907857
dcb <- getDcb
908858
pos <- getSourcePos
909-
dn <- L.lexeme spaceConsumer directiveName
910-
args <- some $ L.lexeme spaceConsumer directiveArgument
859+
dn <- L.lexeme spaceConsumer directiveNameP
860+
args <- some $ L.lexeme spaceConsumer attribute
911861
_ <- setLastDocumentablePosition
912862
lookAhead (void newline <|> eof)
913863
pure $
914864
MemberDirective
915865
Directive
916866
{ directiveDocCommentBlock = dcb
917-
, directiveArguments = DText (Text.pack dn) : args
867+
, directiveName = dn
868+
, directiveAttributes = args
918869
, directivePos = pos
919870
}
920871

persistent/test/Database/Persist/QuasiSpec.hs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,25 @@ spec = describe "Quasi" $ do
172172
it "parses promoted type constructors" $ do
173173
"'Maybe" `isType` TypeLitPromotedConstructor (TypeConstructor "Maybe")
174174

175+
describe "field name parsing" $ do
176+
let
177+
parseFieldName :: String -> ParseResult FieldName
178+
parseFieldName s = do
179+
let
180+
(warnings, res) = runConfiguredParser defaultPersistSettings initialExtraState fieldName "" s
181+
case res of
182+
Left peb ->
183+
(warnings, Left peb)
184+
Right (fn, _acc) -> (warnings, Right fn)
185+
186+
it "parses alphanumeric field names" $
187+
parseFieldName "asdf100"
188+
`shouldBe` ([], Right (FieldName "asdf100"))
189+
190+
it "parses alphanumeric field names with underscores" $
191+
parseFieldName "asdf_100"
192+
`shouldBe` ([], Right (FieldName "asdf_100"))
193+
175194
describe "attribute parsing" $ do
176195
let
177196
parseAttributes :: String -> ParseResult [Attribute]
@@ -193,6 +212,16 @@ spec = describe "Quasi" $ do
193212
)
194213
)
195214

215+
it "handles bangs" $
216+
parseAttributes "foo !bar baz"
217+
`shouldBe` ([], Right
218+
( [ PText "foo"
219+
, PText "!bar"
220+
, PText "baz"
221+
]
222+
)
223+
)
224+
196225
it "handles numbers" $
197226
parseAttributes "one (Finite 1)"
198227
`shouldBe` ([], Right
@@ -234,6 +263,24 @@ spec = describe "Quasi" $ do
234263
)
235264
)
236265

266+
it "handles single quotes in tokens" $
267+
parseAttributes "x=blorp('blap') baz"
268+
`shouldBe` ([], Right
269+
( [ Assignment "x" "blorp('blap')"
270+
, PText "baz"
271+
]
272+
)
273+
)
274+
275+
it "handles spaces in assignment RHSes" $
276+
parseAttributes "sql=blorp('blap', 'blip') baz"
277+
`shouldBe` ([], Right
278+
( [ Assignment "sql" "blorp('blap', 'blip')"
279+
, PText "baz"
280+
]
281+
)
282+
)
283+
237284
it "handles quotes mid-token" $
238285
parseAttributes "x=\"foo bar\" baz"
239286
`shouldBe` ([], Right
@@ -436,7 +483,7 @@ spec = describe "Quasi" $ do
436483
it "rejects quoted attributes" $
437484
evaluate (unboundEntityDef user)
438485
`shouldErrorWithMessage`
439-
"2:14:\n |\n2 | name String \"Maybe\"\n | ^\nUnexpected quotation mark in entity field attribute\n"
486+
"2:14:\n |\n2 | name String \"Maybe\"\n | ^\nUnexpected quotation mark in field or directive attribute\n"
440487

441488
describe "and the definition has quotation marks in the type" $ do
442489
let definitionsWithTypeLevelString = T.pack "User\n name \"String\"\n deriving Show"
@@ -464,7 +511,7 @@ spec = describe "Quasi" $ do
464511
it "rejects quoted arguments" $
465512
evaluate (unboundEntityDef user)
466513
`shouldErrorWithMessage`
467-
"3:11:\n |\n3 | deriving \"Show\"\n | ^\nUnexpected quotation mark in directive argument\n"
514+
"3:11:\n |\n3 | deriving \"Show\"\n | ^\nUnexpected quotation mark in field or directive attribute\n"
468515

469516
describe "parse" $ do
470517
let

0 commit comments

Comments
 (0)