-
Notifications
You must be signed in to change notification settings - Fork 3
/
proptypes-1.0.0.tm
215 lines (204 loc) · 7.97 KB
/
proptypes-1.0.0.tm
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
if 0 {
@ proptypes @
| Validate property types of a dict in an efficient manner
| while taking care to provide clear stack traces when
| errors do occur.
> Disabling proptypes checking
Checking proptypes can become an intensive task. By defining
the TCL_ENV environment variable, proptypes checking can be
toggled on and off.
If TCL_ENV is set and it is not "development" then the proptypes
check will be an empty proc accepting any number of args.
If TCL_ENV is set after the package is required, it will still
be checked and we will immediately return whenever proptypes
is called.
@arg dict {dict<key, mixed>}
The dict to compare against
@arg args {[dict] | ...[dict]}
Either a single element or multiple elements which make up a
valid dict. Each key should map to the desired type.
@shape
Some types allow for nested type checking. These values accept either
2 or 3 arguments where the second argument is the shape and the third
is the optional boolean value defined in the {@optional} section.
@shapes {shape|list|tuple}
@shape {dict}
{
# expects [dict create foo [dict create bar $entierValue]]
proptypes $mydict {
foo {shape {
bar entier
}}
}
}
@shape {list}
{
# expects a list of dicts that contains a key "foo" with an entier value.
proptypes $myDict {
foo {list {shape {foo entier}}}
}
}
@shape {tuple}
{
# expects a tuple (an exact list) containing 3 elements of types any entier boolean
proptypes $myDict {
foo {tuple {any entier boolean}}
}
}
@optional
If a value should be optional, the value can be a list with its
second value being a boolean indicating whether or not the value is
required.
Optional values will be type checked only if they exist in the dict.
@example
{
proc typedProc args {
proptypes $args {
foo dict
bar entier
baz {boolean false}
qux {shape {
foo {tuple {string boolean entier}}
} false}
}
puts "Validated!"
}
typedProc foo [dict create one two] bar 2
# OK: Validated!
typedProc foo [dict create one two] bar hi
# ERROR: invalid type for prop bar, expected entier
typedProc foo [dict create one two] baz true
# ERROR: required value bar not found
typedProc foo [dict create one two] bar 2 baz 2
# ERROR: invalid type for prop baz, expected boolean
}
}
if {[info exists ::env(TCL_ENV)]} {
if {$::env(TCL_ENV) != "development"} {
proc proptypes args {}
}
}
if {![info exists ::env(TCL_ENV)] || [info command proptypes] eq {}} {
proc proptypes {dict args} {
if {[info exists ::env(TCL_ENV)] && $::env(TCL_ENV) != "development"} {
# check in case the env var was defined later
return
}
if {[llength $args] == 1} {
set args [lindex $args 0]
}
dict for {prop type} $args {
set config [lassign $type type]
if {$type in [list shape tuple list]} {
lassign $config shape required
} else {
lassign $config required
}
if {$required eq {}} {
set required true
}
if {[dict exists $dict $prop]} {
set value [dict get $dict $prop]
switch -- $type {
any - string { break }
number {
if {![string is double -strict $value]} {
tailcall return -code error -errorCode [list INVALID_PROP $prop] " invalid type for prop ${prop}, expected $type"
}
}
dict {
if {[catch {dict size $value} err]} {
tailcall return -code error -errorCode [list INVALID_PROP $prop] " invalid type for prop ${prop}, expected dict but received value with [llength $value] elements"
}
}
bool {
if {![string is boolean -strict $value]} {
tailcall return -code error -errorCode [list INVALID_PROP $prop] " invalid type for prop ${prop}, expected $type"
}
}
tuple {
# exact # of elements matching the given type
# tuple {string entier boolean}
if {[llength $shape] != [llength $value]} {
tailcall return -code error -errorCode [list INVALID_PROP $prop WRONG_NUMBER_ELEMENTS] " invalid type for prop ${prop}, expected tuple with [llength $shape] elements but received a value with [llenght $value] elements"
} else {
upvar 1 level plevel
if {![info exists plevel]} {
set level 0
set plevel 0
} else {
set level [expr {$plevel + 1}]
}
set e 0
foreach tupleType $shape {
set cname ${prop}.$e
set cval [dict create $cname [lindex $value $e]]
set cshape [dict create $cname $tupleType]
if {[catch {proptypes $cval $cshape} err]} {
if {$level == 1} {
tailcall return -code error -errorCode [list INVALID_PROP $cname INVALID_TUPLE] "invalid element (${e}) for tuple ${cname} \n[string repeat { } $level]$err"
} else {
return -code error -errorCode [list INVALID_PROP $cname INVALID_TUPLE] "invalid element (${e}) for tuple ${cname} \n[string repeat { } $level]$err"
}
}
incr e
}
}
}
list {
# exact # of elements matching the given type
# tuple {string entier boolean}
if {[llength $value] == 0} {
tailcall return -code error -errorCode [list INVALID_PROP $prop WRONG_NUMBER_ELEMENTS] " invalid type for prop ${prop}, expected list with at least one element of type $shape"
} else {
upvar 1 level plevel
if {![info exists plevel]} {
set level 0
set plevel 0
} else {
set level [expr {$plevel + 1}]
}
set e 0
foreach larg $value {
set cname ${prop}.$e
set cval [dict create $cname [lindex $value $e]]
set cshape [dict create $cname $shape]
if {[catch {proptypes $cval $cshape} err]} {
if {$level == 1} {
tailcall return -code error -errorCode [list INVALID_PROP $cname INVALID_LIST] "invalid element (${e}) for list ${prop} \n[string repeat { } $level]$err"
} else {
return -code error -errorCode [list INVALID_PROP $cname INVALID_LIST] "invalid element (${e}) for list ${prop} \n[string repeat { } $level]$err"
}
}
incr e
}
}
}
shape {
upvar 1 level plevel
if {![info exists plevel]} {
set level 1
set plevel 0
} else {
set level [expr {$plevel + 1}]
}
if {[catch {proptypes $value $shape} err]} {
if {$level == 1} {
tailcall return -code error -errorCode [list INVALID_PROP $prop INVALID_SHAPE] "invalid shape for prop ${prop} \n[string repeat { } $level]$err"
} else {
return -code error -errorCode [list INVALID_PROP $prop INVALID_SHAPE] "invalid shape for prop ${prop} \n[string repeat { } $level]$err"
}
}
}
default {
if {![string is $type -strict $value]} {
tailcall return -code error -errorCode [list INVALID_PROP $prop] " invalid type for prop ${prop}, expected $type"
}
}
}
} elseif {$required} {
tailcall return -code error -errorCode [list PROP_REQUIRED $prop] " required value $prop not found"
}
}
}
}