@rip-lang/server 1.2.11 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +740 -142
- package/api.rip +663 -0
- package/middleware.rip +558 -0
- package/package.json +17 -8
- package/server.rip +27 -41
- package/tests/read.test.rip +254 -0
package/server.rip
CHANGED
|
@@ -280,7 +280,7 @@ parseFlags = (argv) ->
|
|
|
280
280
|
httpIntent = hasHttpKeyword or httpPortToken?
|
|
281
281
|
httpsIntent = true unless httpsIntent or httpIntent
|
|
282
282
|
|
|
283
|
-
httpPort = if httpIntent then (httpPortToken ?? 0) else 0
|
|
283
|
+
httpPort = if httpIntent then (httpPortToken ?? bareIntPort ?? 0) else 0
|
|
284
284
|
httpsPortDerived = if not httpIntent then (bareIntPort or httpsPortToken or 0) else null
|
|
285
285
|
|
|
286
286
|
socketPrefixOverride = getKV('--socket-prefix=')
|
|
@@ -309,12 +309,8 @@ parseFlags = (argv) ->
|
|
|
309
309
|
# Debug mode
|
|
310
310
|
_debugMode = has('--debug')
|
|
311
311
|
|
|
312
|
-
# Watch mode:
|
|
313
|
-
|
|
314
|
-
return getKV('-w=') if getKV('-w=')?
|
|
315
|
-
return getKV('--watch=') if getKV('--watch=')?
|
|
316
|
-
return '*.rip' if has('-w') or has('--watch')
|
|
317
|
-
null
|
|
312
|
+
# Watch mode: on by default, --static disables everything
|
|
313
|
+
watch = getKV('--watch=') or '*.rip'
|
|
318
314
|
|
|
319
315
|
httpsPort = do ->
|
|
320
316
|
kv = getKV('--https-port=')
|
|
@@ -345,7 +341,7 @@ parseFlags = (argv) ->
|
|
|
345
341
|
readTimeoutMs: coerceInt(getKV('--read-timeout-ms='), coerceInt(process.env.RIP_READ_TIMEOUT_MS, 30000))
|
|
346
342
|
jsonLogging: has('--json-logging')
|
|
347
343
|
accessLog: not has('--no-access-log')
|
|
348
|
-
|
|
344
|
+
watch: watch
|
|
349
345
|
}
|
|
350
346
|
|
|
351
347
|
# ==============================================================================
|
|
@@ -401,7 +397,7 @@ runWorker = ->
|
|
|
401
397
|
|
|
402
398
|
unless h
|
|
403
399
|
try
|
|
404
|
-
api = import!('@rip-lang/
|
|
400
|
+
api = import!('@rip-lang/server')
|
|
405
401
|
h = api?.startHandler?.()
|
|
406
402
|
catch
|
|
407
403
|
null
|
|
@@ -544,11 +540,11 @@ class Manager
|
|
|
544
540
|
@rollingRestart().finally => @isRolling = false
|
|
545
541
|
, 50
|
|
546
542
|
|
|
547
|
-
# Watch files in app directory (
|
|
548
|
-
if @flags.
|
|
543
|
+
# Watch files in app directory (on by default, --static disables)
|
|
544
|
+
if @flags.watch
|
|
549
545
|
entryFile = @flags.appEntry
|
|
550
546
|
entryBase = basename(entryFile)
|
|
551
|
-
watchExt = if @flags.
|
|
547
|
+
watchExt = if @flags.watch.startsWith('*.') then @flags.watch.slice(1) else null
|
|
552
548
|
debounceMs = @flags.debounce or 250
|
|
553
549
|
try
|
|
554
550
|
watch @flags.appBaseDir, { recursive: true }, (event, filename) =>
|
|
@@ -556,7 +552,7 @@ class Manager
|
|
|
556
552
|
if watchExt
|
|
557
553
|
return unless filename.endsWith(watchExt)
|
|
558
554
|
else
|
|
559
|
-
return unless filename is @flags.
|
|
555
|
+
return unless filename is @flags.watch or filename.endsWith("/#{@flags.watch}")
|
|
560
556
|
return if filename is entryBase or filename.endsWith("/#{entryBase}")
|
|
561
557
|
# Touch entry file to trigger rolling restart
|
|
562
558
|
try
|
|
@@ -595,7 +591,7 @@ class Manager
|
|
|
595
591
|
broadcast('css')
|
|
596
592
|
watchers.push(w)
|
|
597
593
|
catch e
|
|
598
|
-
console.warn "rip-server: watch failed for #{dir}
|
|
594
|
+
console.warn "rip-server: watch failed for #{dir}:\n #{e.message}"
|
|
599
595
|
@appWatchers.set prefix, { watchers, timer }
|
|
600
596
|
|
|
601
597
|
spawnWorker: (version) ->
|
|
@@ -747,38 +743,29 @@ class Server
|
|
|
747
743
|
httpOnly = @flags.httpsPort is null
|
|
748
744
|
fetchFn = @fetch.bind(@)
|
|
749
745
|
|
|
750
|
-
# Helper to start server,
|
|
746
|
+
# Helper to start server, trying the given port first, then incrementing.
|
|
747
|
+
# Falls back to unprivileged range (3000 for HTTP, 3443 for HTTPS) if
|
|
748
|
+
# the initial port fails due to permission denied (EACCES).
|
|
751
749
|
startOnPort = (p, opts = {}) =>
|
|
752
750
|
port = p
|
|
753
751
|
while port < p + 100
|
|
754
752
|
try
|
|
755
753
|
return Bun.serve(Object.assign({ port, idleTimeout: 0, fetch: fetchFn }, opts))
|
|
756
754
|
catch e
|
|
755
|
+
if e?.code is 'EACCES' and port < 1024
|
|
756
|
+
port = if opts.tls then 3443 else 3000
|
|
757
|
+
p = port
|
|
758
|
+
continue
|
|
757
759
|
throw e unless e?.code is 'EADDRINUSE'
|
|
758
760
|
port++
|
|
759
761
|
throw new Error "No available port found (tried #{p}–#{p + 99})"
|
|
760
762
|
|
|
761
763
|
if httpOnly
|
|
762
|
-
|
|
763
|
-
try
|
|
764
|
-
@server = Bun.serve({ port: 80, idleTimeout: 0, fetch: fetchFn })
|
|
765
|
-
catch e
|
|
766
|
-
throw e unless e?.code in ['EADDRINUSE', 'EACCES']
|
|
767
|
-
@server = startOnPort(5700)
|
|
768
|
-
else
|
|
769
|
-
@server = startOnPort(@flags.httpPort)
|
|
764
|
+
@server = startOnPort(@flags.httpPort or 80)
|
|
770
765
|
@flags.httpPort = @server.port
|
|
771
766
|
else
|
|
772
767
|
tls = @loadTlsMaterial!
|
|
773
|
-
|
|
774
|
-
if not @flags.httpsPort or @flags.httpsPort is 0
|
|
775
|
-
try
|
|
776
|
-
@httpsServer = Bun.serve({ port: 443, idleTimeout: 0, tls, fetch: fetchFn })
|
|
777
|
-
catch e
|
|
778
|
-
throw e unless e?.code in ['EADDRINUSE', 'EACCES']
|
|
779
|
-
@httpsServer = startOnPort(5700, { tls })
|
|
780
|
-
else
|
|
781
|
-
@httpsServer = startOnPort(@flags.httpsPort, { tls })
|
|
768
|
+
@httpsServer = startOnPort(@flags.httpsPort or 443, { tls })
|
|
782
769
|
|
|
783
770
|
httpsPort = @httpsServer.port
|
|
784
771
|
@flags.httpsPort = httpsPort
|
|
@@ -1189,15 +1176,14 @@ main = ->
|
|
|
1189
1176
|
rip-server - Pure Rip application server
|
|
1190
1177
|
|
|
1191
1178
|
Usage:
|
|
1192
|
-
rip
|
|
1193
|
-
rip
|
|
1179
|
+
rip serve [options] [app-path] [app-name]
|
|
1180
|
+
rip serve [options] [app-path]@<alias1>,<alias2>,...
|
|
1194
1181
|
|
|
1195
1182
|
Options:
|
|
1196
1183
|
-h, --help Show this help
|
|
1197
1184
|
-v, --version Show version
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
--static Disable hot reload
|
|
1185
|
+
--watch=<glob> Watch glob pattern (default: *.rip)
|
|
1186
|
+
--static Disable hot reload and file watching
|
|
1201
1187
|
--env=<mode> Set environment (dev, production)
|
|
1202
1188
|
--debug Enable debug logging
|
|
1203
1189
|
|
|
@@ -1217,10 +1203,10 @@ main = ->
|
|
|
1217
1203
|
r:<n>,<s>s Restart policy: max requests, max seconds
|
|
1218
1204
|
|
|
1219
1205
|
Examples:
|
|
1220
|
-
rip
|
|
1221
|
-
rip
|
|
1222
|
-
rip
|
|
1223
|
-
rip
|
|
1206
|
+
rip serve Start with ./index.rip (watches *.rip)
|
|
1207
|
+
rip serve myapp Start with app name "myapp"
|
|
1208
|
+
rip serve http HTTP only (no TLS)
|
|
1209
|
+
rip serve --static w:8 Production: no reload, 8 workers
|
|
1224
1210
|
"""
|
|
1225
1211
|
return
|
|
1226
1212
|
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env rip
|
|
2
|
+
|
|
3
|
+
# ==============================================================================
|
|
4
|
+
# Comprehensive read() tests — validates all 37 built-in validators
|
|
5
|
+
# ==============================================================================
|
|
6
|
+
#
|
|
7
|
+
# Each test is self-contained: t(name, input, expected, fn)
|
|
8
|
+
# No HTTP server needed — uses requestContext.run() directly.
|
|
9
|
+
#
|
|
10
|
+
# Run: rip packages/server/tests/read.test.rip
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
import { read, requestContext } from '../api.rip'
|
|
14
|
+
|
|
15
|
+
# ==============================================================================
|
|
16
|
+
# Test harness
|
|
17
|
+
# ==============================================================================
|
|
18
|
+
|
|
19
|
+
stringify = (v) ->
|
|
20
|
+
try JSON.stringify(v) catch then String(v)
|
|
21
|
+
|
|
22
|
+
deepEq = (a, b) -> stringify(a) is stringify(b)
|
|
23
|
+
|
|
24
|
+
passed = 0
|
|
25
|
+
failed = 0
|
|
26
|
+
total = 0
|
|
27
|
+
|
|
28
|
+
# t(name, was, now, fn) — test read() with input 'was', expect 'now'
|
|
29
|
+
t = (name, was, now, fn) ->
|
|
30
|
+
total++
|
|
31
|
+
requestContext.run { data: { v: was } }, ->
|
|
32
|
+
try
|
|
33
|
+
actual = fn()
|
|
34
|
+
if deepEq(actual, now)
|
|
35
|
+
passed++
|
|
36
|
+
console.log "✓ #{name}"
|
|
37
|
+
else
|
|
38
|
+
failed++
|
|
39
|
+
console.log "✗ #{name}"
|
|
40
|
+
console.log " was: #{stringify(was)}"
|
|
41
|
+
console.log " expected: #{stringify(now)}"
|
|
42
|
+
console.log " actual: #{stringify(actual)}"
|
|
43
|
+
catch err
|
|
44
|
+
failed++
|
|
45
|
+
console.log "✗ #{name} — threw: #{err.message}"
|
|
46
|
+
|
|
47
|
+
# f(name, was, fn) — expect a throw
|
|
48
|
+
f = (name, was, fn) ->
|
|
49
|
+
total++
|
|
50
|
+
requestContext.run { data: { v: was } }, ->
|
|
51
|
+
try
|
|
52
|
+
fn()
|
|
53
|
+
failed++
|
|
54
|
+
console.log "✗ #{name} — expected throw"
|
|
55
|
+
catch
|
|
56
|
+
passed++
|
|
57
|
+
console.log "✓ #{name}"
|
|
58
|
+
|
|
59
|
+
# ==============================================================================
|
|
60
|
+
# read() basics (no validator)
|
|
61
|
+
# ==============================================================================
|
|
62
|
+
|
|
63
|
+
t "raw trims strings" , ' hi ' , 'hi' -> read 'v'
|
|
64
|
+
t "raw trims only edges" , ' ab cd ', 'ab cd' -> read 'v'
|
|
65
|
+
t "raw keeps numbers" , 42 , 42 -> read 'v'
|
|
66
|
+
t "raw missing -> null" , undefined , null -> read 'v'
|
|
67
|
+
t "raw uses default" , undefined , 'x' -> read 'v', null, 'x'
|
|
68
|
+
t "raw uses miss fn" , undefined , 'm' -> read 'v', null, -> 'm'
|
|
69
|
+
|
|
70
|
+
# ==============================================================================
|
|
71
|
+
# required (!) behavior
|
|
72
|
+
# ==============================================================================
|
|
73
|
+
|
|
74
|
+
f "required throws" , undefined -> read 'v', 'string!'
|
|
75
|
+
t "required miss fn" , undefined, 'fallback' -> read 'v', 'string!' -> 'fallback'
|
|
76
|
+
f "required blank" , ' ' -> read 'v', 'string!'
|
|
77
|
+
|
|
78
|
+
# ==============================================================================
|
|
79
|
+
# numbers
|
|
80
|
+
# ==============================================================================
|
|
81
|
+
|
|
82
|
+
t "id valid" , '123' , 123 -> read 'v', 'id'
|
|
83
|
+
t "id rejects 0-lead" , '012' , null -> read 'v', 'id'
|
|
84
|
+
t "whole accepts 0" , '0' , 0 -> read 'v', 'whole'
|
|
85
|
+
t "whole rejects neg" , '-5' , null -> read 'v', 'whole'
|
|
86
|
+
t "int positive" , '42' , 42 -> read 'v', 'int'
|
|
87
|
+
t "int negative" , '-5' , -5 -> read 'v', 'int'
|
|
88
|
+
t "int zero" , '0' , 0 -> read 'v', 'int'
|
|
89
|
+
t "int rejects leading 0" , '07' , null -> read 'v', 'int'
|
|
90
|
+
t "int rejects float" , '1.5' , null -> read 'v', 'int'
|
|
91
|
+
t "float signed" , '-1.25', -1.25 -> read 'v', 'float'
|
|
92
|
+
t "float dot" , '.5' , 0.5 -> read 'v', 'float'
|
|
93
|
+
t "float junk" , 'abc' , null -> read 'v', 'float'
|
|
94
|
+
|
|
95
|
+
# ==============================================================================
|
|
96
|
+
# money
|
|
97
|
+
# ==============================================================================
|
|
98
|
+
|
|
99
|
+
t "money basic" , '1.23' , 1.23 -> read 'v', 'money'
|
|
100
|
+
t "money rounds up" , '1.245' , 1.25 -> read 'v', 'money'
|
|
101
|
+
t "money neg half" , '-0.005' , -0.01 -> read 'v', 'money'
|
|
102
|
+
t "money strips $," , '$7,221.245' , 7221.25 -> read 'v', 'money'
|
|
103
|
+
t "money spaced" , '- $ 9,999.12', -9999.12 -> read 'v', 'money'
|
|
104
|
+
t "money_even basic" , '1.23' , 1.23 -> read 'v', 'money_even'
|
|
105
|
+
t "money_even rounds even" , '1.245' , 1.24 -> read 'v', 'money_even'
|
|
106
|
+
t "money_even neg half" , '-0.005' , 0 -> read 'v', 'money_even'
|
|
107
|
+
t "cents basic" , '1.23' , 123 -> read 'v', 'cents'
|
|
108
|
+
t "cents rounds up" , '1.245' , 125 -> read 'v', 'cents'
|
|
109
|
+
t "cents neg half" , '-0.005' , -1 -> read 'v', 'cents'
|
|
110
|
+
t "cents past half" , '1.2351' , 124 -> read 'v', 'cents'
|
|
111
|
+
t "cents strips $," , '$7,221.245' , 722125 -> read 'v', 'cents'
|
|
112
|
+
t "cents_even basic" , '1.23' , 123 -> read 'v', 'cents_even'
|
|
113
|
+
t "cents_even rounds even" , '1.245' , 124 -> read 'v', 'cents_even'
|
|
114
|
+
t "cents_even past half" , '1.2351' , 124 -> read 'v', 'cents_even'
|
|
115
|
+
t "cents_even neg half" , '-0.005' , 0 -> read 'v', 'cents_even'
|
|
116
|
+
|
|
117
|
+
# ==============================================================================
|
|
118
|
+
# formatting
|
|
119
|
+
# ==============================================================================
|
|
120
|
+
|
|
121
|
+
t "string collapses" , ' ab cd ' , 'ab cd' -> read 'v', 'string'
|
|
122
|
+
t "text collapses" , 'a b' , 'a b' -> read 'v', 'text'
|
|
123
|
+
t "name normalizes" , "al\t\nbeck" , 'Al Beck' -> read 'v', 'name'
|
|
124
|
+
t "name is magical" , " lArrY o'dOUl", "Larry O'Doul" -> read 'v', 'name'
|
|
125
|
+
t "address normalizes" , "1\t Main St", '1 Main St' -> read 'v', 'address'
|
|
126
|
+
t "address PO Box" , 'P. o. bOX #44', 'PO Box #44' -> read 'v', 'address'
|
|
127
|
+
|
|
128
|
+
# ==============================================================================
|
|
129
|
+
# time/date
|
|
130
|
+
# ==============================================================================
|
|
131
|
+
|
|
132
|
+
t "time HH:MM" , '09:30' , '09:30' -> read 'v', 'time'
|
|
133
|
+
t "time HH:MM:SS" , '23:59:59' , '23:59:59' -> read 'v', 'time'
|
|
134
|
+
t "time rejects 24" , '24:00' , null -> read 'v', 'time'
|
|
135
|
+
t "date ok" , '2026-01-18' , '2026-01-18' -> read 'v', 'date'
|
|
136
|
+
t "date rejects bad" , '2026-1-8' , null -> read 'v', 'date'
|
|
137
|
+
t "time ok" , '0:01' , '0:01' -> read 'v', 'time'
|
|
138
|
+
t "time 24 hr" , '16:05:30' , '16:05:30' -> read 'v', 'time'
|
|
139
|
+
t "time12 lowercased" , '9:05 PM' , '9:05 pm' -> read 'v', 'time12'
|
|
140
|
+
|
|
141
|
+
# ==============================================================================
|
|
142
|
+
# booleans
|
|
143
|
+
# ==============================================================================
|
|
144
|
+
|
|
145
|
+
t "truthy yes" , 'yes' , true -> read 'v', 'truthy'
|
|
146
|
+
t "truthy non-match" , 'maybe', null -> read 'v', 'truthy'
|
|
147
|
+
t "falsy no" , 'no' , true -> read 'v', 'falsy'
|
|
148
|
+
t "bool true" , 'ON' , true -> read 'v', 'bool'
|
|
149
|
+
t "bool false" , 'off' , false -> read 'v', 'bool'
|
|
150
|
+
t "bool junk" , 'wat' , null -> read 'v', 'bool'
|
|
151
|
+
|
|
152
|
+
# ==============================================================================
|
|
153
|
+
# identity/contact
|
|
154
|
+
# ==============================================================================
|
|
155
|
+
|
|
156
|
+
t "email ok" , 'a+b@ex.co' , 'a+b@ex.co' -> read 'v', 'email'
|
|
157
|
+
t "email bad" , 'nope@' , null -> read 'v', 'email'
|
|
158
|
+
t "state uppercases" , 'ca' , 'CA' -> read 'v', 'state'
|
|
159
|
+
t "zip reads 5" , '12345' , '12345' -> read 'v', 'zip'
|
|
160
|
+
t "zipplus4 formats" , '12345-6789' , '12345-6789' -> read 'v', 'zipplus4'
|
|
161
|
+
t "ssn strips punct" , '123-45-6789', '123456789' -> read 'v', 'ssn'
|
|
162
|
+
t "sex normalizes" , 'Female' , 'f' -> read 'v', 'sex'
|
|
163
|
+
|
|
164
|
+
# ==============================================================================
|
|
165
|
+
# technical/web
|
|
166
|
+
# ==============================================================================
|
|
167
|
+
|
|
168
|
+
t "username lower" , 'AbC_12',
|
|
169
|
+
'abc_12' -> read 'v', 'username'
|
|
170
|
+
t "username short" , 'ab',
|
|
171
|
+
null -> read 'v', 'username'
|
|
172
|
+
t "ip valid" , '192.168.1.1',
|
|
173
|
+
'192.168.1.1' -> read 'v', 'ip'
|
|
174
|
+
t "ip rejects 999" , '999.1.2.3',
|
|
175
|
+
null -> read 'v', 'ip'
|
|
176
|
+
t "mac normalizes" , 'AA-BB-CC-DD-EE-FF',
|
|
177
|
+
'aa:bb:cc:dd:ee:ff' -> read 'v', 'mac'
|
|
178
|
+
t "url ok" , 'https://example.com/a/b',
|
|
179
|
+
'https://example.com/a/b' -> read 'v', 'url'
|
|
180
|
+
t "color hash" , 'ABC',
|
|
181
|
+
'#abc' -> read 'v', 'color'
|
|
182
|
+
t "uuid normalizes" , 'A0B1C2D3E4F5A6B7C8D9E0F1A2B3C4D5',
|
|
183
|
+
'a0b1c2d3-e4f5-a6b7-c8d9-e0f1a2b3c4d5' -> read 'v', 'uuid'
|
|
184
|
+
t "semver ok" , '1.2.3-alpha.1+build.5',
|
|
185
|
+
'1.2.3-alpha.1+build.5' -> read 'v', 'semver'
|
|
186
|
+
t "slug ok" , 'a-b-c',
|
|
187
|
+
'a-b-c' -> read 'v', 'slug'
|
|
188
|
+
t "slug capitals" , 'A-B',
|
|
189
|
+
'a-b' -> read 'v', 'slug'
|
|
190
|
+
t "phone formats" , '555234-5678',
|
|
191
|
+
'(555) 234-5678' -> read 'v', 'phone'
|
|
192
|
+
t "phone short" , '123',
|
|
193
|
+
null -> read 'v', 'phone'
|
|
194
|
+
|
|
195
|
+
# ==============================================================================
|
|
196
|
+
# collections
|
|
197
|
+
# ==============================================================================
|
|
198
|
+
|
|
199
|
+
t "array ok" , [1, 2] , [1, 2] -> read 'v', 'array'
|
|
200
|
+
t "array not object" , { x: 1 } , null -> read 'v', 'array'
|
|
201
|
+
t "hash ok" , { x: 1 } , { x: 1 } -> read 'v', 'hash'
|
|
202
|
+
t "hash not array" , [] , null -> read 'v', 'hash'
|
|
203
|
+
t "json parses str" , '{"a":1}' , { a: 1 } -> read 'v', 'json'
|
|
204
|
+
t "json accepts obj" , { a: 1 } , { a: 1 } -> read 'v', 'json'
|
|
205
|
+
t "json rejects junk" , '{' , null -> read 'v', 'json'
|
|
206
|
+
t "ids sorts dedupes" , '3, 2 2 1', [1, 2, 3] -> read 'v', 'ids'
|
|
207
|
+
t "ids rejects 0" , '0 1' , null -> read 'v', 'ids'
|
|
208
|
+
t "ids empty null" , ' ' , null -> read 'v', 'ids'
|
|
209
|
+
|
|
210
|
+
# ==============================================================================
|
|
211
|
+
# regex type
|
|
212
|
+
# ==============================================================================
|
|
213
|
+
|
|
214
|
+
t "regex matches" , 'abc' , 'abc' -> read 'v', /^[a-z]+$/
|
|
215
|
+
t "regex non-match" , 'ABC' , null -> read 'v', /^[a-z]+$/
|
|
216
|
+
|
|
217
|
+
# ==============================================================================
|
|
218
|
+
# array range (numeric + string length + enum)
|
|
219
|
+
# ==============================================================================
|
|
220
|
+
|
|
221
|
+
t "range num ok str" , '2' , 2 -> read 'v', [1, 3]
|
|
222
|
+
t "range num ok val" , 3 , 3 -> read 'v', [1, 3]
|
|
223
|
+
t "range num low" , '0' , null -> read 'v', [1, 3]
|
|
224
|
+
t "range str ok" , 'abcd' , 'abcd' -> read 'v', [2, 4]
|
|
225
|
+
t "range str long" , 'abcde' , null -> read 'v', [2, 4]
|
|
226
|
+
t "enum ok" , 'green' , 'green' -> read 'v', ['red', 'green']
|
|
227
|
+
t "enum bad" , 'blue' , null -> read 'v', ['red', 'green']
|
|
228
|
+
|
|
229
|
+
# ==============================================================================
|
|
230
|
+
# object range (start/end + min/max)
|
|
231
|
+
# ==============================================================================
|
|
232
|
+
|
|
233
|
+
t "obj start/end ok" , '7' , 7 -> read 'v', { start: 5, end: 10 }
|
|
234
|
+
t "obj start/end dec" , '1.5' , null -> read 'v', { start: 0, end: 10 }
|
|
235
|
+
t "obj min/max ok" , '2' , 2 -> read 'v', { min: 1, max: 3 }
|
|
236
|
+
t "obj min/max bad" , '9' , null -> read 'v', { min: 1, max: 3 }
|
|
237
|
+
t "obj str ok" , 'abc' , 'abc' -> read 'v', { min: 2, max: 4 }
|
|
238
|
+
t "obj str short" , 'a' , null -> read 'v', { min: 2, max: 4 }
|
|
239
|
+
|
|
240
|
+
# ==============================================================================
|
|
241
|
+
# miss() edge cases
|
|
242
|
+
# ==============================================================================
|
|
243
|
+
|
|
244
|
+
t "miss uses default" , undefined, 'no' -> read 'v', 'email', 'no'
|
|
245
|
+
t "miss uses fn" , undefined, 'no' -> read 'v', 'email', -> 'no'
|
|
246
|
+
f "required w/ default" , undefined -> read 'v', 'email!', 'nope'
|
|
247
|
+
t "required w/ miss()" , undefined, 'ok' -> read 'v', 'email!', -> 'ok'
|
|
248
|
+
|
|
249
|
+
# ==============================================================================
|
|
250
|
+
# Results
|
|
251
|
+
# ==============================================================================
|
|
252
|
+
|
|
253
|
+
console.log "\n#{passed} passed, #{failed} failed (#{total} total)"
|
|
254
|
+
process.exit(if failed > 0 then 1 else 0)
|