/
Validators.hs
146 lines (137 loc) · 5.58 KB
/
Validators.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
{-# LANGUAGE QuasiQuotes #-}
module Nirum.Targets.Python.Validators
( Validator (..)
, ValueValidator (..)
, compilePrimitiveTypeValidator
, compileValidator
) where
import Data.Text (Text, intercalate)
import Text.InterpolatedString.Perl6 (qq)
import Nirum.Constructs.Identifier
import Nirum.Constructs.TypeDeclaration
import Nirum.Constructs.TypeExpression
import {-# SOURCE #-} Nirum.Targets.Python ()
import Nirum.Targets.Python.CodeGen
import Nirum.Targets.Python.TypeExpression
import Nirum.TypeInstance.BoundModule
data Validator = Validator
{ typePredicateCode :: Code
, valueValidators :: [ValueValidator]
} deriving (Eq, Show)
data ValueValidator = ValueValidator
{ predicateCode :: Code
, errorMessage :: Text
} deriving (Eq, Show)
compileValidator :: BoundModule Python
-> TypeExpression
-> Code
-> CodeGen Validator
compileValidator mod' (OptionModifier typeExpr) pythonVar = do
Validator typePred vvs <- compileValidator mod' typeExpr pythonVar
let typeValidator = [qq|(($pythonVar) is None or $typePred)|]
valueValidators' =
[ ValueValidator [qq|(($pythonVar) is None or ($vPredCode))|] msg
| ValueValidator vPredCode msg <- vvs
]
return $ Validator typeValidator valueValidators'
compileValidator mod' (SetModifier typeExpr) pythonVar = do
abc <- collectionsAbc
Validator typePred vvs <-
multiplexValidators mod' pythonVar [(typeExpr, "elem")]
return $ Validator
[qq|(isinstance($pythonVar, $abc.Set) and $typePred)|]
vvs
compileValidator mod' (ListModifier typeExpr) pythonVar = do
abc <- collectionsAbc
Validator typePred vvs <-
multiplexValidators mod' pythonVar [(typeExpr, "item")]
return $ Validator
[qq|(isinstance($pythonVar, $abc.Sequence) and $typePred)|]
vvs
compileValidator mod' (MapModifier keyTypeExpr valueTypeExpr) pythonVar = do
abc <- collectionsAbc
Validator typePred vvs <-
multiplexValidators mod' [qq|(($pythonVar).items())|]
[(keyTypeExpr, "key"), (valueTypeExpr, "value")]
return $ Validator
[qq|(isinstance($pythonVar, $abc.Mapping) and $typePred)|]
vvs
compileValidator mod' (TypeIdentifier typeId) pythonVar =
case lookupType typeId mod' of
Missing -> return $ Validator "False" [] -- must never happen
Local (Alias typeExpr') -> compileValidator mod' typeExpr' pythonVar
Imported modulePath' _ (Alias typeExpr') ->
case resolveBoundModule modulePath' (boundPackage mod') of
Nothing -> return $ Validator "False" [] -- must never happen
Just foundMod -> compileValidator foundMod typeExpr' pythonVar
Local PrimitiveType { primitiveTypeIdentifier = pId } ->
compilePrimitiveTypeValidator pId pythonVar
Imported _ _ PrimitiveType { primitiveTypeIdentifier = pId } ->
compilePrimitiveTypeValidator pId pythonVar
_ ->
compileInstanceValidator mod' typeId pythonVar
compilePrimitiveTypeValidator :: PrimitiveTypeIdentifier
-> Code
-> CodeGen Validator
compilePrimitiveTypeValidator primitiveTypeId pythonVar = do
typeName <- compilePrimitiveType primitiveTypeId
return $ Validator
[qq|(isinstance(($pythonVar), ($typeName)))|]
(vv primitiveTypeId pythonVar)
where
vv :: PrimitiveTypeIdentifier -> Code -> [ValueValidator]
vv Int32 var =
[ ValueValidator [qq|(-0x80000000 <= ($var) < 0x80000000)|]
"out of range of 32-bit integer"
]
vv Int64 var =
[ ValueValidator
[qq|(-0x8000000000000000 <= ($var) < 0x8000000000000000)|]
"out of range of 64-bit integer"
]
vv Datetime var =
[ ValueValidator [qq|(($var).tzinfo is not None)|]
"naive datetime (lacking tzinfo)"
]
vv Uri var =
[ ValueValidator [qq|('\\n' not in ($var))|]
"URI cannot contain new line characters"
]
vv _ _ = []
compileInstanceValidator :: BoundModule Python
-> Identifier
-> Code
-> CodeGen Validator
compileInstanceValidator mod' typeId pythonVar = do
cls <- compileTypeExpression mod' (Just (TypeIdentifier typeId))
return $ Validator [qq|(isinstance(($pythonVar), ($cls)))|] []
collectionsAbc :: CodeGen Code
collectionsAbc = do
ver <- getPythonVersion
importStandardLibrary $ case ver of
Python2 -> "collections"
Python3 -> "collections.abc"
multiplexValidators :: BoundModule Python
-> Code
-> [(TypeExpression, Code)]
-> CodeGen Validator
multiplexValidators mod' iterableExpr elements = do
validators <- sequence
[ do
v <- compileValidator mod' tExpr elemVar
return (elemVar, v)
| (tExpr, var) <- elements
, elemVar <- [mangleVar iterableExpr var]
]
let csElemVars = intercalate "," [v | (v, _) <- validators]
typePredLogicalAnds = intercalate
" and "
[typePred | (_, Validator typePred _) <- validators]
return $ Validator
[qq|(all(($typePredLogicalAnds) for ($csElemVars) in $iterableExpr))|]
[ ValueValidator
[qq|(all(($typePred) for ($csElemVars) in $iterableExpr))|]
[qq|invalid elements ($msg)|]
| (_, Validator _ vvs) <- validators
, ValueValidator typePred msg <- vvs
]