tangled
alpha
login
or
join now
keii.dev
/
wisp
3
fork
atom
🧚 A practical web framework for Gleam
3
fork
atom
overview
issues
pulls
pipelines
Further tests!
Louis Pilfold
2 years ago
275a1102
8e1225f3
+159
-126
2 changed files
expand all
collapse all
unified
split
src
wisp.gleam
test
wisp_test.gleam
+43
-122
src/wisp.gleam
···
95
95
}
96
96
97
97
fn mist_send_file(path: String) -> mist.ResponseData {
98
98
-
mist.send_file(path, offset: 0, limit: option.None)
99
99
-
|> result.lazy_unwrap(fn() {
100
100
-
// TODO: log error
101
101
-
mist.Bytes(bit_builder.new())
102
102
-
})
98
98
+
case mist.send_file(path, offset: 0, limit: option.None) {
99
99
+
Ok(body) -> body
100
100
+
Error(error) -> {
101
101
+
// TODO: log error
102
102
+
io.println("ERROR: " <> string.inspect(error))
103
103
+
// TODO: return 500
104
104
+
mist.Bytes(bit_builder.new())
105
105
+
}
106
106
+
}
103
107
}
104
108
105
109
//
···
291
295
pub type Request =
292
296
HttpRequest(Connection)
293
297
294
294
-
// TODO: test
295
298
// TODO: document
296
299
pub fn require_method(
297
300
request: HttpRequest(t),
···
347
350
|> result.unwrap(request)
348
351
}
349
352
350
350
-
// TODO: test
353
353
+
// TODO: don't always return entity to large. Other errors are possible, such as
354
354
+
// network errors.
351
355
// TODO: document
352
356
pub fn require_string_body(
353
357
request: Request,
···
505
509
Ok(value) -> Ok(value)
506
510
Error(error) -> {
507
511
// TODO: log error
508
508
-
io.debug(error)
512
512
+
io.println("ERROR: " <> string.inspect(error))
509
513
Error(internal_server_error())
510
514
}
511
515
}
···
613
617
list.sort(pairs, fn(a, b) { string.compare(a.0, b.0) })
614
618
}
615
619
616
616
-
// TODO: test
617
620
// TODO: document
618
621
pub fn require(
619
622
result: Result(value, error),
···
640
643
// MIME types
641
644
//
642
645
643
643
-
// TODO: test
644
644
-
// TODO: move to another package
645
645
-
pub fn mime_type_to_extensions(mime_type: String) -> List(String) {
646
646
-
case mime_type {
647
647
-
"application/atom+xml" -> ["atom"]
648
648
-
"application/epub+zip" -> ["epub"]
649
649
-
"application/gzip" -> ["gz"]
650
650
-
"application/java-archive" -> ["jar"]
651
651
-
"application/javascript" -> ["js"]
652
652
-
"application/json" -> ["json"]
653
653
-
"application/json-patch+json" -> ["json-patch"]
654
654
-
"application/ld+json" -> ["jsonld"]
655
655
-
"application/manifest+json" -> ["webmanifest"]
656
656
-
"application/msword" -> ["doc"]
657
657
-
"application/octet-stream" -> ["bin"]
658
658
-
"application/ogg" -> ["ogx"]
659
659
-
"application/pdf" -> ["pdf"]
660
660
-
"application/postscript" -> ["ps", "eps", "ai"]
661
661
-
"application/rss+xml" -> ["rss"]
662
662
-
"application/rtf" -> ["rtf"]
663
663
-
"application/vnd.amazon.ebook" -> ["azw"]
664
664
-
"application/vnd.api+json" -> ["json-api"]
665
665
-
"application/vnd.apple.installer+xml" -> ["mpkg"]
666
666
-
"application/vnd.etsi.asic-e+zip" -> ["asice", "sce"]
667
667
-
"application/vnd.etsi.asic-s+zip" -> ["asics", "scs"]
668
668
-
"application/vnd.mozilla.xul+xml" -> ["xul"]
669
669
-
"application/vnd.ms-excel" -> ["xls"]
670
670
-
"application/vnd.ms-fontobject" -> ["eot"]
671
671
-
"application/vnd.ms-powerpoint" -> ["ppt"]
672
672
-
"application/vnd.oasis.opendocument.presentation" -> ["odp"]
673
673
-
"application/vnd.oasis.opendocument.spreadsheet" -> ["ods"]
674
674
-
"application/vnd.oasis.opendocument.text" -> ["odt"]
675
675
-
"application/vnd.openxmlformats-officedocument.presentationml.presentation" -> [
676
676
-
"pptx",
677
677
-
]
678
678
-
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" -> [
679
679
-
"xlsx",
680
680
-
]
681
681
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document" -> [
682
682
-
"docx",
683
683
-
]
684
684
-
"application/vnd.rar" -> ["rar"]
685
685
-
"application/vnd.visio" -> ["vsd"]
686
686
-
"application/wasm" -> ["wasm"]
687
687
-
"application/x-7z-compressed" -> ["7z"]
688
688
-
"application/x-abiword" -> ["abw"]
689
689
-
"application/x-bzip" -> ["bz"]
690
690
-
"application/x-bzip2" -> ["bz2"]
691
691
-
"application/x-cdf" -> ["cda"]
692
692
-
"application/x-csh" -> ["csh"]
693
693
-
"application/x-freearc" -> ["arc"]
694
694
-
"application/x-httpd-php" -> ["php"]
695
695
-
"application/x-msaccess" -> ["mdb"]
696
696
-
"application/x-sh" -> ["sh"]
697
697
-
"application/x-shockwave-flash" -> ["swf"]
698
698
-
"application/x-tar" -> ["tar"]
699
699
-
"application/xhtml+xml" -> ["xhtml"]
700
700
-
"application/xml" -> ["xml"]
701
701
-
"application/zip" -> ["zip"]
702
702
-
"audio/3gpp" -> ["3gp"]
703
703
-
"audio/3gpp2" -> ["3g2"]
704
704
-
"audio/aac" -> ["aac"]
705
705
-
"audio/midi" -> ["mid", "midi"]
706
706
-
"audio/mpeg" -> ["mp3"]
707
707
-
"audio/ogg" -> ["oga"]
708
708
-
"audio/opus" -> ["opus"]
709
709
-
"audio/wav" -> ["wav"]
710
710
-
"audio/webm" -> ["weba"]
711
711
-
"font/otf" -> ["otf"]
712
712
-
"font/ttf" -> ["ttf"]
713
713
-
"font/woff" -> ["woff"]
714
714
-
"font/woff2" -> ["woff2"]
715
715
-
"image/avif" -> ["avif"]
716
716
-
"image/bmp" -> ["bmp"]
717
717
-
"image/gif" -> ["gif"]
718
718
-
"image/heic" -> ["heic"]
719
719
-
"image/heif" -> ["heif"]
720
720
-
"image/jpeg" -> ["jpg", "jpeg"]
721
721
-
"image/jxl" -> ["jxl"]
722
722
-
"image/png" -> ["png"]
723
723
-
"image/svg+xml" -> ["svg", "svgz"]
724
724
-
"image/tiff" -> ["tiff", "tif"]
725
725
-
"image/vnd.adobe.photoshop" -> ["psd"]
726
726
-
"image/vnd.microsoft.icon" -> ["ico"]
727
727
-
"image/webp" -> ["webp"]
728
728
-
"text/calendar" -> ["ics"]
729
729
-
"text/css" -> ["css"]
730
730
-
"text/csv" -> ["csv"]
731
731
-
"text/html" -> ["html", "htm"]
732
732
-
"text/javascript" -> ["js", "mjs"]
733
733
-
"text/markdown" -> ["md", "markdown"]
734
734
-
"text/plain" -> ["txt", "text"]
735
735
-
"text/xml" -> ["xml"]
736
736
-
"video/3gpp" -> ["3gp"]
737
737
-
"video/3gpp2" -> ["3g2"]
738
738
-
"video/mp2t" -> ["ts"]
739
739
-
"video/mp4" -> ["mp4"]
740
740
-
"video/mpeg" -> ["mpeg", "mpg"]
741
741
-
"video/ogg" -> ["ogv"]
742
742
-
"video/quicktime" -> ["mov"]
743
743
-
"video/webm" -> ["webm"]
744
744
-
"video/x-ms-wmv" -> ["wmv"]
745
745
-
"video/x-msvideo" -> ["avi"]
746
746
-
_ -> []
747
747
-
}
748
748
-
}
749
749
-
750
750
-
// TODO: test
751
646
// TODO: move to another package
752
647
fn extension_to_mime_type(extension: String) -> String {
753
648
case extension {
···
862
757
// Middleware
863
758
//
864
759
865
865
-
// TODO: test
866
760
// TODO: document
867
761
pub fn rescue_crashes(service: fn() -> Response) -> Response {
868
762
case erlang.rescue(service) {
869
763
Ok(response) -> response
870
764
Error(error) -> {
871
765
// TODO: log the error
872
872
-
io.debug(error)
766
766
+
io.println("ERROR: " <> string.inspect(error))
873
767
internal_server_error()
874
768
}
875
769
}
···
892
786
response
893
787
}
894
788
789
789
+
fn remove_preceeding_slashes(string: String) -> String {
790
790
+
case string {
791
791
+
"/" <> rest -> remove_preceeding_slashes(rest)
792
792
+
_ -> string
793
793
+
}
794
794
+
}
795
795
+
796
796
+
// TODO: replace with simplifile function when it exists
797
797
+
fn join_path(a: String, b: String) -> String {
798
798
+
let b = remove_preceeding_slashes(b)
799
799
+
case string.ends_with(a, "/") {
800
800
+
True -> a <> b
801
801
+
False -> a <> "/" <> b
802
802
+
}
803
803
+
}
804
804
+
895
805
// TODO: test
896
806
// TODO: document
897
807
// TODO: remove requirement for preceeding slash on prefix
···
901
811
from directory: String,
902
812
next service: fn() -> Response,
903
813
) -> Response {
904
904
-
case req.method, string.starts_with(req.path, prefix) {
814
814
+
let path = remove_preceeding_slashes(req.path)
815
815
+
let prefix = remove_preceeding_slashes(prefix)
816
816
+
case req.method, string.starts_with(path, prefix) {
905
817
http.Get, True -> {
906
818
let path =
907
907
-
req.path
819
819
+
path
908
820
|> string.drop_left(string.length(prefix))
909
821
|> string.replace(each: "..", with: "")
910
822
|> string.replace(each: "//", with: "/")
911
911
-
|> string.append(directory, _)
823
823
+
|> remove_preceeding_slashes
824
824
+
|> join_path(directory, _)
912
825
913
826
let mime_type =
914
827
req.path
···
972
885
make_connection(fn(_size) {
973
886
Ok(Chunk(body, fn(_size) { Ok(ReadingFinished) }))
974
887
})
888
888
+
}
889
889
+
890
890
+
// TODO: better API
891
891
+
// TODO: test
892
892
+
// TODO: document
893
893
+
pub fn test_request(body: BitString) -> Request {
894
894
+
request.new()
895
895
+
|> request.set_body(test_connection(body))
975
896
}
976
897
977
898
// TODO: test
+116
-4
test/wisp_test.gleam
···
83
83
}
84
84
85
85
pub fn set_get_max_body_size_test() {
86
86
-
let request =
87
87
-
request.new()
88
88
-
|> request.set_body(wisp.test_connection(<<>>))
86
86
+
let request = wisp.test_request(<<>>)
89
87
90
88
request
91
89
|> wisp.get_max_body_size
···
202
200
{
203
201
let request = request.set_method(request.new(), http.Post)
204
202
use <- wisp.require_method(request, http.Get)
205
205
-
wisp.ok()
203
203
+
panic as "should be unreachable"
206
204
}
207
205
|> should.equal(wisp.method_not_allowed([http.Get]))
208
206
}
207
207
+
208
208
+
pub fn require_ok_test() {
209
209
+
{
210
210
+
use x <- wisp.require(Ok(1))
211
211
+
x
212
212
+
|> should.equal(1)
213
213
+
wisp.accepted()
214
214
+
}
215
215
+
|> should.equal(wisp.accepted())
216
216
+
}
217
217
+
218
218
+
pub fn require_error_test() {
219
219
+
{
220
220
+
use _ <- wisp.require(Error(1))
221
221
+
panic as "should be unreachable"
222
222
+
}
223
223
+
|> should.equal(wisp.bad_request())
224
224
+
}
225
225
+
226
226
+
pub fn require_string_body_test() {
227
227
+
{
228
228
+
let request = wisp.test_request(<<"Hello, Joe!":utf8>>)
229
229
+
use body <- wisp.require_string_body(request)
230
230
+
body
231
231
+
|> should.equal("Hello, Joe!")
232
232
+
wisp.accepted()
233
233
+
}
234
234
+
|> should.equal(wisp.accepted())
235
235
+
}
236
236
+
237
237
+
pub fn require_string_body_invalid_test() {
238
238
+
{
239
239
+
let request = wisp.test_request(<<254>>)
240
240
+
use _ <- wisp.require_string_body(request)
241
241
+
panic as "should be unreachable"
242
242
+
}
243
243
+
|> should.equal(wisp.bad_request())
244
244
+
}
245
245
+
246
246
+
pub fn rescue_crashes_error_test() {
247
247
+
{
248
248
+
use <- wisp.rescue_crashes
249
249
+
panic as "we need to crash to test the middleware"
250
250
+
}
251
251
+
|> should.equal(wisp.internal_server_error())
252
252
+
}
253
253
+
254
254
+
pub fn rescue_crashes_ok_test() {
255
255
+
{
256
256
+
use <- wisp.rescue_crashes
257
257
+
wisp.ok()
258
258
+
}
259
259
+
|> should.equal(wisp.ok())
260
260
+
}
261
261
+
262
262
+
pub fn serve_static_test() {
263
263
+
let request =
264
264
+
wisp.test_request(<<>>)
265
265
+
|> request.set_path("/stuff/README.md")
266
266
+
let response = {
267
267
+
use <- wisp.serve_static(request, under: "/stuff", from: "./")
268
268
+
wisp.ok()
269
269
+
}
270
270
+
response.status
271
271
+
|> should.equal(200)
272
272
+
response.headers
273
273
+
|> should.equal([#("content-type", "text/markdown")])
274
274
+
response.body
275
275
+
|> should.equal(wisp.File("./README.md"))
276
276
+
}
277
277
+
278
278
+
pub fn serve_static_under_has_no_preceeding_slash_test() {
279
279
+
let request =
280
280
+
wisp.test_request(<<>>)
281
281
+
|> request.set_path("/stuff/README.md")
282
282
+
let response = {
283
283
+
use <- wisp.serve_static(request, under: "stuff", from: "./")
284
284
+
wisp.ok()
285
285
+
}
286
286
+
response.status
287
287
+
|> should.equal(200)
288
288
+
response.headers
289
289
+
|> should.equal([#("content-type", "text/markdown")])
290
290
+
response.body
291
291
+
|> should.equal(wisp.File("./README.md"))
292
292
+
}
293
293
+
294
294
+
pub fn serve_static_from_has_no_trailing_slash_test() {
295
295
+
let request =
296
296
+
wisp.test_request(<<>>)
297
297
+
|> request.set_path("/stuff/README.md")
298
298
+
let response = {
299
299
+
use <- wisp.serve_static(request, under: "stuff", from: ".")
300
300
+
wisp.ok()
301
301
+
}
302
302
+
response.status
303
303
+
|> should.equal(200)
304
304
+
response.headers
305
305
+
|> should.equal([#("content-type", "text/markdown")])
306
306
+
response.body
307
307
+
|> should.equal(wisp.File("./README.md"))
308
308
+
}
309
309
+
310
310
+
pub fn serve_static_not_found_test() {
311
311
+
let request =
312
312
+
wisp.test_request(<<>>)
313
313
+
|> request.set_path("/stuff/credit_card_details.txt")
314
314
+
{
315
315
+
use <- wisp.serve_static(request, under: "/stuff", from: "./")
316
316
+
wisp.ok()
317
317
+
}
318
318
+
|> should.equal(wisp.ok())
319
319
+
}
320
320
+
// TODO: More tests for serve_static!