Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modify Http.send to that it returns a Result #309

Merged
merged 2 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions crates/roc_host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ pub extern "C" fn roc_fx_send_request(
.unwrap_or_else(|_err| roc_http::ResponseToAndFromHost {
status: 408,
headers: RocList::empty(),
body: "Request Timeout".as_bytes().into(),
body: roc_http::REQUEST_TIMEOUT_BODY.into(),
}),
None => rt.block_on(async_send_request(request)),
}
Expand Down Expand Up @@ -570,7 +570,17 @@ async fn async_send_request(request: hyper::Request<String>) -> roc_http::Respon

let status = status.as_u16();

let bytes = hyper::body::to_bytes(response.into_body()).await.unwrap();
let bytes = match hyper::body::to_bytes(response.into_body()).await {
Ok(bytes) => bytes,
Err(_) => {
return roc_http::ResponseToAndFromHost {
status: 500,
headers: RocList::empty(),
body: roc_http::REQUEST_BAD_BODY.into(),
};
}
};

let body: RocList<u8> = RocList::from_iter(bytes);

roc_http::ResponseToAndFromHost {
Expand All @@ -584,19 +594,19 @@ async fn async_send_request(request: hyper::Request<String>) -> roc_http::Respon
roc_http::ResponseToAndFromHost {
status: 408,
headers: RocList::empty(),
body: "Request Timeout".as_bytes().into(),
body: roc_http::REQUEST_TIMEOUT_BODY.into(),
}
} else if err.is_connect() || err.is_closed() {
roc_http::ResponseToAndFromHost {
status: 500,
headers: RocList::empty(),
body: "Network Error".as_bytes().into(),
body: roc_http::REQUEST_NETWORK_ERR.into(),
}
} else {
roc_http::ResponseToAndFromHost {
status: 500,
headers: RocList::empty(),
body: err.to_string().as_bytes().into(),
body: format!("OTHER ERROR\n{}", err).as_bytes().into(),
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions crates/roc_http/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ use std::io::{BufRead, BufReader, ErrorKind, Read, Write};
use std::net::TcpStream;
use std::sync::OnceLock;

pub const REQUEST_TIMEOUT_BODY: &[u8] = "RequestTimeout".as_bytes();
pub const REQUEST_NETWORK_ERR: &[u8] = "Network Error".as_bytes();
pub const REQUEST_BAD_BODY: &[u8] = "Bad Body".as_bytes();

pub fn heap() -> &'static ThreadSafeRefcountedResourceHeap<BufReader<TcpStream>> {
// TODO: Should this be a BufReader and BufWriter of the tcp stream?
// like this: https://stackoverflow.com/questions/58467659/how-to-store-tcpstream-with-bufreader-and-bufwriter-in-a-data-structure/58491889#58491889
Expand Down
2 changes: 1 addition & 1 deletion examples/http-get.roc
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ main! = \_args ->
body: [],
timeout_ms: TimeoutMilliseconds(5000),
},
)
)?

body = (Str.from_utf8(response.body))?

Expand Down
37 changes: 25 additions & 12 deletions platform/Http.roc
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,32 @@ header = \(name, value) -> { name, value }
## ```
## # Prints out the HTML of the Roc-lang website.
## response =
## Http.send!({ Http.default_request & url: "https://www.roc-lang.org" })
## Http.send!({ Http.default_request & url: "https://www.roc-lang.org" })?
##
## response.body
## |> Str.from_utf8
##
## Str.from_utf8(response.body)
## |> Result.with_default("Invalid UTF-8")
## |> Stdout.line
## ```
send! : Request => Response
send! : Request => Result Response [HttpErr [Timeout, NetworkError, BadBody, Other (List U8)]]
send! = \request ->
request
|> InternalHttp.to_host_request
|> Host.send_request!
|> InternalHttp.from_host_response

host_request = InternalHttp.to_host_request(request)

response = Host.send_request!(host_request) |> InternalHttp.from_host_response

other_error_prefix = Str.to_utf8("OTHER ERROR\n")

if response.status == 408 && response.body == Str.to_utf8("Request Timeout") then
Err(HttpErr Timeout)
else if response.status == 500 && response.body == Str.to_utf8("Network Error") then
Err(HttpErr NetworkError)
else if response.status == 500 && response.body == Str.to_utf8("Bad Body") then
Err(HttpErr BadBody)
else if response.status == 500 && List.starts_with(response.body, other_error_prefix) then
Err(HttpErr (Other List.drop_first(response.body, List.len(other_error_prefix))))
else
Ok(response)

## Try to perform an HTTP get request and convert (decode) the received bytes into a Roc type.
## Very useful for working with Json.
Expand All @@ -79,16 +92,16 @@ send! = \request ->
## # On the server side we send `Encode.to_bytes {foo: "Hello Json!"} Json.utf8`
## { foo } = Http.get!("http://localhost:8000", Json.utf8)?
## ```
get! : Str, fmt => Result body [HttpDecodingFailed] where body implements Decoding, fmt implements DecoderFormatting
get! : Str, fmt => Result body [HttpDecodingFailed, HttpErr _] where body implements Decoding, fmt implements DecoderFormatting
get! = \uri, fmt ->
response = send!({ default_request & uri })
response = send!({ default_request & uri })?

Decode.from_bytes(response.body, fmt)
|> Result.map_err(\_ -> HttpDecodingFailed)

get_utf8! : Str => Result Str [BadBody Str]
get_utf8! : Str => Result Str [BadBody Str, HttpErr _]
get_utf8! = \uri ->
response = send!({ default_request & uri })
response = send!({ default_request & uri })?

response.body
|> Str.from_utf8
Expand Down