Skip to content

Commit

Permalink
Merge pull request #309 from roc-lang/http-send-result
Browse files Browse the repository at this point in the history
Modify `Http.send` to that it returns a `Result`
  • Loading branch information
Anton-4 authored Jan 10, 2025
2 parents c89c762 + 743129c commit 49701fc
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 18 deletions.
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

0 comments on commit 49701fc

Please sign in to comment.