Skip to content
This repository was archived by the owner on Jul 10, 2025. It is now read-only.

Commit ec6aafd

Browse files
author
Radu M
authored
Merge pull request #8 from phated/main
2 parents af3d878 + 3a383e7 commit ec6aafd

File tree

10 files changed

+374
-157
lines changed

10 files changed

+374
-157
lines changed

.github/workflows/grain.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
- uses: engineerd/configurator@v0.0.8
1414
with:
1515
name: "grain"
16-
url: "https://github.com/grain-lang/grain/releases/download/grain-v0.3.2/grain-linux-x64"
16+
url: "https://github.com/grain-lang/grain/releases/download/grain-v0.4.3/grain-linux-x64"
1717
- uses: engineerd/configurator@v0.0.8
1818
with:
1919
name: "wasmtime"

Makefile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,16 @@ test:
2727

2828
.PHONY: push
2929
push:
30-
hippofactory -s ${BINDLE_SERVER_URL} .
30+
hippofactory -s ${BINDLE_SERVER_URL} .
31+
32+
doc: lib/*.gr
33+
grain doc lib/env.gr -o lib/env.md
34+
grain doc lib/mediatype.gr -o lib/mediatype.md
35+
grain doc lib/stringutil.gr -o lib/stringutil.md
36+
37+
fmt: *.gr lib/*.gr
38+
grain format fileserver.gr --in-place
39+
grain format tests.gr --in-place
40+
grain format lib/env.gr --in-place
41+
grain format lib/mediatype.gr --in-place
42+
grain format lib/stringutil.gr --in-place

fileserver.gr

Lines changed: 70 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,62 +5,86 @@ import Map from "map"
55
import Option from "option"
66
import File from "sys/file"
77
import String from "string"
8+
import Result from "result"
89
import Mediatype from "./lib/mediatype"
910
import Stringutil from "./lib/stringutil"
1011

11-
let serve = (abs_path) => {
12-
// Trim the leading /
13-
let path = String.slice(1, String.length(abs_path), abs_path)
14-
File.fdWrite(File.stderr, "Fileserver: Loading file ")
15-
File.fdWrite(File.stderr, path)
16-
File.fdWrite(File.stderr, "\n")
12+
// Utility wrapper around a Result.expect that ignores the return value
13+
// so we don't need to worry about things returning non-Void types
14+
let validateResult = (msg, res) => {
15+
ignore(Result.expect(msg, res))
16+
}
17+
18+
let internalError = () => {
19+
validateResult(
20+
"Unexpected error when writing Internal Server Error response",
21+
File.fdWrite(File.stdout, "Status: 500\n\nInternal Server Error"),
22+
)
23+
}
1724

18-
// Open file
19-
// TODO: This throws an error when something goes wrong.
20-
// But Grain 0.3 does not have a try/catch yet.
21-
// When we figure out how, we need to catch the error here
22-
// and do a 404 or 500.
23-
let input = File.pathOpen(
24-
File.pwdfd,
25-
[],
26-
path,
27-
[],
28-
[File.FdRead],
29-
[],
30-
[],
31-
)
25+
let notFound = () => {
26+
validateResult(
27+
"Unexpected error when writing Not Found response",
28+
File.fdWrite(File.stdout, "Status: 404\n\nNot Found"),
29+
)
30+
}
3231

33-
File.fdWrite(File.stdout, "Content-Type: ")
34-
File.fdWrite(File.stdout, Mediatype.guess(path))
35-
File.fdWrite(File.stdout, "\n\n")
36-
37-
// Pipe output to STDOUT
38-
let rec pipe = (in, out) => {
39-
let (d, len) = File.fdRead(in, 1024)
40-
File.fdWrite(out, d)
41-
if (len > 0) {
42-
pipe(in, out)
43-
}
44-
}
45-
pipe(input, File.stdout)
46-
File.fdClose(input)
32+
// Pipe output to STDOUT
33+
let rec pipe = (in, out) => {
34+
let res = File.fdRead(in, 1024)
35+
match (res) {
36+
Err(err) => Err(err),
37+
Ok((d, len)) => {
38+
let res = File.fdWrite(out, d)
39+
if (len > 0) {
40+
pipe(in, out)
41+
} else {
42+
res
43+
}
44+
},
45+
}
4746
}
4847

49-
let guestpath = (env) => {
48+
let serve = abs_path => {
49+
// Trim the leading /
50+
let path = String.slice(1, String.length(abs_path), abs_path)
51+
// Explicitly ignoring any Ok or Err that happens on this log
52+
// The `ignore` can be removed if you don't want to be explicit about this behavior
53+
ignore(File.fdWrite(File.stderr, "Fileserver: Loading file " ++ path ++ "\n"))
54+
55+
// Open file
56+
let result = File.pathOpen(File.pwdfd, [], path, [], [File.FdRead], [], [])
5057

51-
// Backward compat for an older version of Wagi that had PATH_INFO wrong.
52-
// X_RELATIVE_PATH was removed before Wagi 0.4
53-
match (Map.get("X_RELATIVE_PATH", env)) {
54-
Some(p) => String.concat("/", p),
55-
None => {
56-
Option.unwrap(Map.get("PATH_INFO", env))
57-
}
58-
}
59-
58+
match (result) {
59+
Err(_err) => notFound(),
60+
Ok(input) => {
61+
validateResult(
62+
"Unexpected error when writing Content-Type",
63+
File.fdWrite(
64+
File.stdout,
65+
"Content-Type: " ++ Mediatype.guess(path) ++ "\n\n",
66+
),
67+
)
68+
69+
validateResult(
70+
"Unexpected error when streaming file body",
71+
pipe(input, File.stdout),
72+
)
73+
// This validation may be able to be removed if it doesn't matter if the fdClose fails
74+
validateResult("Unexpected error when closing file", File.fdClose(input))
75+
},
76+
}
6077
}
6178

62-
let notFound = () => {
63-
File.fdWrite(File.stdout, "Status: 404\n\nNot Found")
79+
let guestpath = env => {
80+
// Backward compat for an older version of Wagi that had PATH_INFO wrong.
81+
// X_RELATIVE_PATH was removed before Wagi 0.4
82+
match (Map.get("X_RELATIVE_PATH", env)) {
83+
Some(p) => String.concat("/", p),
84+
None => {
85+
Option.unwrap(Map.get("PATH_INFO", env))
86+
},
87+
}
6488
}
6589

6690
let kv = Env.envMap()

lib/env.gr

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,44 @@ import Array from "array"
44
import String from "string"
55
import Option from "option"
66

7-
// Split an environment variable at the first equals sign.
8-
// @param item: An environment variable pair, separated by an equals sign (=).
9-
// @return (String, String) A tuple key/value pair.
10-
export let splitEnvVar = (item) => {
11-
let offsetOpt = String.indexOf("=", item)
7+
/**
8+
* Split an environment variable at the first equals sign.
9+
*
10+
* @param item: An environment variable pair, separated by an equals sign (=).
11+
* @returns A tuple key/value pair.
12+
*/
13+
export let splitEnvVar = item => {
14+
let offsetOpt = String.indexOf("=", item)
1215

13-
// For now, fail if the env var is malformed.
14-
let offset = Option.unwrap(offsetOpt)
16+
// For now, fail if the env var is malformed.
17+
let offset = Option.unwrap(offsetOpt)
1518

16-
let key = String.slice(0, offset, item)
17-
let val = String.slice(offset+1, String.length(item), item)
18-
(key, val)
19+
let key = String.slice(0, offset, item)
20+
let val = String.slice(offset + 1, String.length(item), item)
21+
(key, val)
1922
}
2023

21-
// Get the environment variables as a Map<String, String>
22-
//
23-
// @return Map<String, String> A map of all environment variables.
24-
export let envMap = () => {
25-
let parsed = Map.make()
26-
let env = Process.env()
27-
let pairs = Array.map(splitEnvVar,env)
24+
/**
25+
* Get the environment variables as a Map<String, String>
26+
*
27+
* @returns A map of all environment variables.
28+
*/
29+
export let envMap = () => {
30+
let parsed = Map.make()
31+
let env = Process.env()
32+
match (env) {
33+
Err(err) => parsed,
34+
Ok(env) => {
35+
let pairs = Array.map(splitEnvVar, env)
2836

29-
Array.forEach((item) => {
30-
let (k, v) = item
31-
Map.set(k, v, parsed)
32-
}, pairs)
33-
parsed
37+
Array.forEach(
38+
item => {
39+
let (k, v) = item
40+
Map.set(k, v, parsed)
41+
},
42+
pairs,
43+
)
44+
parsed
45+
},
46+
}
3447
}
35-
36-

lib/env.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
### Env.**splitEnvVar**
2+
3+
```grain
4+
splitEnvVar : String -> (String, String)
5+
```
6+
7+
Split an environment variable at the first equals sign.
8+
9+
Parameters:
10+
11+
|param|type|description|
12+
|-----|----|-----------|
13+
|`item`|`String`|An environment variable pair, separated by an equals sign (=).|
14+
15+
Returns:
16+
17+
|type|description|
18+
|----|-----------|
19+
|`(String, String)`|A tuple key/value pair.|
20+
21+
### Env.**envMap**
22+
23+
```grain
24+
envMap : () -> Map.Map<a, b>
25+
```
26+
27+
Get the environment variables as a Map<String, String>
28+
29+
Returns:
30+
31+
|type|description|
32+
|----|-----------|
33+
|`Map.Map<a, b>`|A map of all environment variables.|
34+

lib/mediatype.gr

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import String from "string"
22
import Array from "array"
33
import Option from "option"
44
import Map from "map"
5-
import {lastIndexOf, reverse} from "./lib/stringutil"
5+
import { lastIndexOf, reverse } from "./stringutil"
66

77
export let default_mt = "application/octet-stream"
88

@@ -82,18 +82,30 @@ Map.set("7z", "application/x-7z-compressed", mediatypes)
8282
Map.set("azw", "application/vnd.amazon.ebook", mediatypes)
8383
Map.set("bin", "application/octet-stream", mediatypes)
8484
Map.set("doc", "application/msword", mediatypes)
85-
Map.set("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", mediatypes)
85+
Map.set(
86+
"docx",
87+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
88+
mediatypes,
89+
)
8690
Map.set("epub", "application/epub+zip", mediatypes)
8791
Map.set("odp", "application/vnd.oasis.opendocument.presentation", mediatypes)
8892
Map.set("ods", "application/vnd.oasis.opendocument.spreadsheet", mediatypes)
8993
Map.set("odt", "application/vnd.oasis.opendocument.text", mediatypes)
9094
Map.set("pdf", "application/pdf", mediatypes)
9195
Map.set("ppt", "application/vnd.ms-powerpoint", mediatypes)
92-
Map.set("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation", mediatypes)
96+
Map.set(
97+
"pptx",
98+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
99+
mediatypes,
100+
)
93101
Map.set("rtf", "application/rtf", mediatypes)
94102
Map.set("vsd", "application/vnd.visio", mediatypes)
95103
Map.set("xls", "application/vnd.ms-excel", mediatypes)
96-
Map.set("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", mediatypes)
104+
Map.set(
105+
"xlsx",
106+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
107+
mediatypes,
108+
)
97109

98110
// Fonts
99111
Map.set("eot", "application/vnd.ms-fontobject", mediatypes)
@@ -102,19 +114,21 @@ Map.set("ttf", "font/ttf", mediatypes)
102114
Map.set("woff", "font/woff", mediatypes)
103115
Map.set("woff2", "font/woff2", mediatypes)
104116

105-
// Guess the media type of this file
106-
//
107-
// Per recommendation, if no media type is found for an extension,
108-
// this returns `application/octet-stream`.
109-
//
110-
// @param filename: The name of the file
111-
// @returns String a media type
117+
/**
118+
* Guess the media type of this file
119+
*
120+
* Per recommendation, if no media type is found for an extension,
121+
* this returns `application/octet-stream`.
122+
*
123+
* @param filename: The name of the file
124+
* @returns A media type
125+
*/
112126
export let guess = (filename: String) => {
113-
match (lastIndexOf(".", filename)) {
114-
Some(extOffset) => {
115-
let ext = String.slice(extOffset + 1, String.length(filename), filename)
116-
Option.unwrapWithDefault(default_mt, Map.get(ext, mediatypes))
117-
},
118-
None => default_mt
119-
}
127+
match (lastIndexOf(".", filename)) {
128+
Some(extOffset) => {
129+
let ext = String.slice(extOffset + 1, String.length(filename), filename)
130+
Option.unwrapWithDefault(default_mt, Map.get(ext, mediatypes))
131+
},
132+
None => default_mt,
133+
}
120134
}

lib/mediatype.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
### Mediatype.**default_mt**
2+
3+
```grain
4+
default_mt : String
5+
```
6+
7+
### Mediatype.**guess**
8+
9+
```grain
10+
guess : String -> String
11+
```
12+
13+
Guess the media type of this file
14+
15+
Per recommendation, if no media type is found for an extension,
16+
this returns `application/octet-stream`.
17+
18+
Parameters:
19+
20+
|param|type|description|
21+
|-----|----|-----------|
22+
|`filename`|`String`|The name of the file|
23+
24+
Returns:
25+
26+
|type|description|
27+
|----|-----------|
28+
|`String`|A media type|
29+

0 commit comments

Comments
 (0)