Stream: beginners

Topic: Http.send causes exits program early on 400 code?


view this post on Zulip Ian McLerran (May 07 2024 at 16:29):

So I'm trying to ping an API with a post request, and the API is giving me a 400 status code. However it seems that the send function is forcing my program to exit early, rather than letting me handle the 400 code myself.

So in the source below, the dbg statement is never hit. Instead, the program exits with the following message:

Program exited early with error: (HttpError (BadStatus 400))

Looking over the source from basic-cli, I don't see anything that obviously looks like it should do this. Anyone know anything about this what I'm doing wrong?

resp = Http.send! (buildRequest apiKey model messages)
dbg resp
output =
    when resp |> Http.handleStringResponse is
        Err err -> crash (Http.errorToString err)
        Ok body -> body

view this post on Zulip Anton (May 07 2024 at 16:40):

Hi Ian,
I believe you are creating a Task chain with error accumulation that short circuits when an error is encountered. I'll see if I can modify this so it works as you intend.

view this post on Zulip Anton (May 07 2024 at 16:46):

I'm going to get diner but I can think you can follow this approach with Task.onErr

view this post on Zulip Karakatiza (May 07 2024 at 17:56):

Should be

when ... is
   Err (HttpError (BadStatus 404)) -> "Error 404"
   Err err -> crash ...
   Ok body -> body

Note that in your design the successful result of when ... is is a Str, so this implies you will provide a valid string in the case of an 404 error

You might want to change that to return a Task Str ... instead, so that you can do some other action and not just return a value

view this post on Zulip Karakatiza (May 07 2024 at 18:29):

Or, you could return a Result ( -> Err ExampleBadRequestTag and -> Ok body) instead, and handle the two options later in your code. What is more ergonomic depends on whether you can provide a replacement value when an error occurs right away

view this post on Zulip Anton (May 07 2024 at 18:37):

Yeah there are multiple ways to go. This is a working example in old-style Roc (I don't have much experience with ! yet):

app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.11.0/SY4WWMhWQ9NvQgvIthcv15AUeA7rAIJHAHgiaSHGhdY.tar.br" }

import pf.Http
import pf.Task exposing [Task]
import pf.Stdout
import pf.Stderr

main =
    request = {
        method: Get,
        headers: [],
        url: "http://localhost:8080",
        mimeType: "",
        body: [],
        timeout: TimeoutMilliseconds 5000,
    }

    sendT = Http.send request

    Task.attempt sendT \sendResult ->
        when sendResult is
            Ok response ->
                when response |> Http.handleStringResponse is
                    Ok body -> Stdout.line "Response body: $(body)."
                    # Body contained Invalid UTF-8
                    Err err -> Stderr.line (Http.errorToString err)

            Err err -> Stderr.line (Inspect.toStr err)

I'll write a clean ! version tomorrow.
I'm also going to improve the error Program exited early with error:.

view this post on Zulip Ian McLerran (May 07 2024 at 21:19):

Awesome, thanks for this @Anton . This is a great help! :thank_you:

One complaint that I have with the way Http errors are structured with the basic cli platform is that in the case of a BadStatus error, there is no access provided to the response body. In order to debug my error from the API, I had to modify the basic-cli platform with to allow the BadStatus error to return the response body. Might be nice if the error was a tagged record which included both the error code # as well as the response body.

view this post on Zulip Richard Feldman (May 07 2024 at 21:24):

we can do that! Can you open an issue on the repo?

view this post on Zulip Ian McLerran (May 07 2024 at 22:27):

I sure can! I'd love to handle the PR too... Does this look good for the error types?

Error : [
    BadRequest Str,
    Timeout U64,
    NetworkError,
    BadStatus { code: U16, body: List U8 }, # changed from: BadStatus U16,
    BadBody Str,
]

view this post on Zulip Richard Feldman (May 07 2024 at 22:36):

looks good to me! :raised_hands:

view this post on Zulip Ian McLerran (May 08 2024 at 16:11):

So I'm seeing one downside to using a List U8 for the body in the Error. There may be many reasons to keep the body in List U8 format that I'm not aware of, but I'll just call this out here.

In the case that the error goes unhandled and the program exits early and the platform attempts to print the error to the terminal, the List U8 is not particularly readable/helpful to the user. If the error is handled by the user they can of course handle the U8 List however they want.

But for example, you might get an output like this:

Program exited early with error: (HttpErr (BadStatus {body: [123, 34, 101, 114, 114, 111, 114, 34, 58, 123, 34, 109, 101, 115, 115, 97, 103, 101, 34, 58, 34, 77, 111, 100, 101, 108, 32, 109, 105, 115, 116, 114, 97, 108, 47, 109, 105, 115, 116, 114, 97, 108, 45, 115, 109, 97, 108, 108, 32, 105, 115, 32, 110, 111, 116, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 34, 44, 34, 99, 111, 100, 101, 34, 58, 52, 48, 48, 125, 44, 34, 117, 115, 101, 114, 95, 105, 100, 34, 58, 34, 117, 115, 101, 114, 95, 50, 80, 111, 52, 88, 104, 97, 81, 78, 114, 73, 89, 115, 99, 88, 67, 89, 80, 57, 121, 113, 121, 118, 105, 49, 113, 78, 34, 125], code: 400}))

Maybe including the body as a Str instead of a List U8 would be better for this reason?

view this post on Zulip Anton (May 08 2024 at 18:36):

That is indeed a shortcoming, can the conversion from List U8 to Str fail in this case?

view this post on Zulip Anton (May 08 2024 at 18:43):

@Ian McLerran I've made the ! example here, I also asked for input for potential improvements.

view this post on Zulip Karakatiza (May 08 2024 at 18:46):

There is Http.errorToString, it can be adjusted to try to convert the new List U8 to Utf8 string

view this post on Zulip Ian McLerran (May 08 2024 at 18:51):

@Anton I'm sure there are cases where it could, my thought would be to use Result.withDefault "". You'll still get the status code, but if the Utf8 conversion fails, you'll miss out on the body. Probably a rare edge case, but that risk is definitely a downside to string conversion.

@Karakatiza Only problem is Http.errorToString is not called when the platform does an Inspect.toStr err from main.

view this post on Zulip Agus Zubiaga (May 08 2024 at 19:52):

We could make the body an opaque type and implement Inspect on it in a way that first tries converting to string and defaults to printing the raw bytes if that fails.

view this post on Zulip Agus Zubiaga (May 08 2024 at 19:55):

One downside is that sometimes you’ll see the string version just because the result happens to be valid UTF8

view this post on Zulip Karakatiza (May 08 2024 at 19:57):

As a dev I feel like adding Http.errorToString in a manual error handling is trivial, as well as decoding raw body bytes to utf8 if needed.
It may make sense to have a new Http.sendUtf8 return Str body, and keep Http.send that returns List U8. Trying to implicitly decode a bytestring as a utf8 string feels wrong.

Now that I think of it, I don't think having Inspect.toStr return entire body of Http error makes sense. It should either return just the error code, or maybe a preview of the body - maybe first ~50 bytes/characters. So that If you want to see the body in case of an error you are forced to add some manual handling, atleast Http.errorToString
Its Inspect, not Serialize after all (although I do not know the original philosophy of this builtin)

view this post on Zulip Ian McLerran (May 08 2024 at 20:13):

@Karakatiza I think you’re right on the money there… the inspect probably shouldn’t return the entire body. Maybe not even return the body at all? Definitely think it could be truncated. When handling the error manually, the body info will be there, but for implicit handling of the error, or any time when inspected, the status code (and *maybe a chunk of the body) is probably adequate.

view this post on Zulip Ian McLerran (May 08 2024 at 20:13):

Curious what others think…

view this post on Zulip Anton (May 10 2024 at 08:59):

I would indeed add a truncated body, I think it's good to remove friction for debugging where possible

view this post on Zulip Ian McLerran (May 23 2024 at 17:43):

I've submitted a PR for this here. Curious what folks think.

view this post on Zulip Norbert Hajagos (May 24 2024 at 08:45):

I'm in favour of truncating the List U8. I also think implicit str conversion and a separate function to receive strings from http responses are not the "Rocy" way to do things (having one way to do the same thing). We do have functions that do the same thing as 2 functions combined, but they are in place for performace reasons (Like List.WalkBackwards Instead of List.reverse |> List.walk), and we are trying to get rid of them by making the composition of functions just as performant with Iterator - like builtin list functions.

view this post on Zulip Ian McLerran (May 24 2024 at 15:38):

To be fair, there is no implicit Str conversion happening to the body value attached to BadStatus error tag. This is still a List U8, just wrapped in an opaque type. So the user has full control over how to handle those bytes.

The only Str conversion happening "implicitly" is in inspect, which is ultimately a glorified string conversion anyway.

view this post on Zulip Norbert Hajagos (May 24 2024 at 17:53):

Okay, sorry I didn't get it the first time. This sounds pretty nice then, since if the conversion fails with invalid utf8 bytes, the user probably doesn't expect the response body to be a valid string anyways. Yes, it would produce strings vs List U8 depending on the content, which isn't elegant, but I see it as a good solution in practice.


Last updated: Jul 06 2025 at 12:14 UTC