Skip to content

Commit 9bc283c

Browse files
committed
middleware.method_override, http.put_req_query
1 parent 72b62a7 commit 9bc283c

File tree

4 files changed

+143
-3
lines changed

4 files changed

+143
-3
lines changed

CHANGELOG.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
`get_req_header`, `get_resp_header`, `prepend_req_header`, `seq_req_host`,
99
`set_req_path`, `prepend_resp_header`, `set_req_method`, `set_req_body`,
1010
`set_resp_body`, `map_resp_body`, `map_req_body` `try_map_resp_body`,
11-
`req_from_uri`, `default_req`, and `redirect`.
11+
`set_req_query`, `req_from_uri`, `default_req`, and `redirect`.
1212
- Created the `gleam/http/middleware` module with the `Middleware` type and
13-
the `prepend_resp_header`, `map_resp_body` type.
13+
the `method_override`, `prepend_resp_header`, `map_resp_body` middleware
14+
functions.
1415

1516
## v1.0.0 - 2020-06-30
1617

src/gleam/http.gleam

+41
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import gleam/list
1414
import gleam/option.{None, Option, Some}
1515
import gleam/string
16+
import gleam/string_builder
1617
import gleam/uri.{Uri}
1718
import gleam/dynamic.{Dynamic}
1819

@@ -432,3 +433,43 @@ pub fn set_req_path(req: Request(body), path: String) -> Request(body) {
432433
query: query,
433434
)
434435
}
436+
437+
// TODO: test
438+
// TODO: escape
439+
// TODO: record update syntax
440+
/// Set the query of the request.
441+
///
442+
pub fn set_req_query(
443+
req: Request(body),
444+
query: List(tuple(String, String)),
445+
) -> Request(body) {
446+
let pair = fn(t: tuple(String, String)) {
447+
string_builder.from_strings([t.0, "=", t.1])
448+
}
449+
let query = query
450+
|> list.map(pair)
451+
|> list.intersperse(string_builder.from_string("&"))
452+
|> string_builder.concat
453+
|> string_builder.to_string
454+
|> Some
455+
let Request(
456+
method: method,
457+
headers: headers,
458+
body: body,
459+
scheme: scheme,
460+
host: host,
461+
port: port,
462+
path: path,
463+
query: _,
464+
) = req
465+
Request(
466+
method: method,
467+
headers: headers,
468+
body: body,
469+
scheme: scheme,
470+
host: host,
471+
port: port,
472+
path: path,
473+
query: query,
474+
)
475+
}

src/gleam/http/middleware.gleam

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import gleam/http.{Request, Response, Service}
1+
import gleam/http.{Delete, Patch, Post, Put, Request, Response, Service}
2+
import gleam/list
3+
import gleam/result
24

35
pub type Middleware(before_req, before_resp, after_req, after_resp) =
46
fn(Service(before_req, before_resp)) -> Service(after_req, after_resp)
@@ -30,3 +32,45 @@ pub fn prepend_resp_header(
3032
|> http.prepend_resp_header(key, value)
3133
}
3234
}
35+
36+
fn ensure_post(req: Request(a)) {
37+
case req.method {
38+
Post -> Ok(req)
39+
_ -> Error(Nil)
40+
}
41+
}
42+
43+
fn get_override_method(req) {
44+
try query_params = http.req_query(req)
45+
try method = list.key_find(query_params, "_method")
46+
try method = http.parse_method(method)
47+
case method {
48+
Put | Patch | Delete -> Ok(method)
49+
_ -> Error(Nil)
50+
}
51+
}
52+
53+
/// A middleware that overrides an incoming POST request with a method given in
54+
/// the request's `_method` query paramerter. This is useful as web browsers
55+
/// typically only support GET and POST requests, but our application may
56+
/// expect other HTTP methods that are more semantically correct.
57+
///
58+
/// The methods PUT, PATCH, and DELETE are accepted for overriding, all others
59+
/// are ignored.
60+
///
61+
/// The `_method` query paramerter can be specified in a HTML form like so:
62+
///
63+
/// <form method="POST" action="/item/1?_method=DELETE">
64+
/// <button type="submit">Delete item</button>
65+
/// </form>
66+
///
67+
pub fn method_override(service: Service(req, resp)) -> Service(req, resp) {
68+
fn(req) {
69+
req
70+
|> ensure_post
71+
|> result.then(get_override_method)
72+
|> result.map(http.set_req_method(req, _))
73+
|> result.unwrap(req)
74+
|> service
75+
}
76+
}

test/gleam/http/middleware_test.gleam

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import gleam/should
2+
import gleam/http.{Delete, Get, Patch, Post, Put, Request, Response}
3+
import gleam/http/middleware
4+
5+
pub fn method_override_test() {
6+
let service = fn(req: Request(a)) {
7+
http.response(200)
8+
|> http.set_resp_body(req.method)
9+
}
10+
|> middleware.method_override
11+
12+
// No overriding without the query param
13+
http.default_req()
14+
|> http.set_req_method(Get)
15+
|> service
16+
|> should.equal(Response(200, [], Get))
17+
18+
http.default_req()
19+
|> http.set_req_method(Post)
20+
|> service
21+
|> should.equal(Response(200, [], Post))
22+
23+
// Can override
24+
http.default_req()
25+
|> http.set_req_method(Post)
26+
|> http.set_req_query([tuple("_method", "DELETE")])
27+
|> service
28+
|> should.equal(Response(200, [], Delete))
29+
30+
http.default_req()
31+
|> http.set_req_method(Post)
32+
|> http.set_req_query([tuple("_method", "PATCH")])
33+
|> service
34+
|> should.equal(Response(200, [], Patch))
35+
36+
http.default_req()
37+
|> http.set_req_method(Post)
38+
|> http.set_req_query([tuple("_method", "PUT")])
39+
|> service
40+
|> should.equal(Response(200, [], Put))
41+
42+
// Cannot override with other methods
43+
http.default_req()
44+
|> http.set_req_method(Post)
45+
|> http.set_req_query([tuple("_method", "OPTIONS")])
46+
|> service
47+
|> should.equal(Response(200, [], Post))
48+
49+
http.default_req()
50+
|> http.set_req_method(Post)
51+
|> http.set_req_query([tuple("_method", "SOMETHING")])
52+
|> service
53+
|> should.equal(Response(200, [], Post))
54+
}

0 commit comments

Comments
 (0)