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

(feat): Add request.set_headers and request.prepend_headers #53

Closed
wants to merge 5 commits into from

Conversation

dmmulroy
Copy link
Contributor

This PR adds pluralized versions for set_header and prepend_header that will each take a list of #(String, String) and apply them to the request in the same fashion as the singular versions. In addition, tests have been added to cover the new functions.

@lpil
Copy link
Member

lpil commented Mar 13, 2024

Thank you for this, but seeing as one can pipe the existing function I don't yet know of a strong case for their additions.

What was the situation which prompted you to implement these?

@dmmulroy
Copy link
Contributor Author

I like constructing headers in their own function, generally, into a list and adding them to requests as such. Here's an example

fn to_http_request(
  client: Client,
  method: Method,
  req: Request,
) -> HttpRequest(String) {
  let headers =
    client
    |> headers
    |> merge_headers(req.headers)

  let body =
    req.body
    |> option.map(json.to_string)
    |> option.unwrap("")

  let query =
    option.map(req.query, fn(params) {
      list.map(params, fn(param) {
        let key = pair.first(param)
        let value = pair.second(param)

        #(uri.percent_encode(key), uri.percent_encode(value))
      })
    })
    |> option.unwrap([])

  request.new()
  |> request.set_method(method)
  |> request_ext.set_headers(headers)
  |> request.set_scheme(Https)
  |> request.set_host(host)
  |> request.set_body(body)
  |> request.set_path(req.path)
  |> request.set_query(query)
}

Full code: https://github.com/dmmulroy/glitch/blob/main/src/glitch/api/client.gleam#L97

@dmmulroy
Copy link
Contributor Author

dmmulroy commented Mar 14, 2024

I'd also add there is kind of a precedence for this type of API in the library already with set_query which takes a list of query params rather than adding one at a time.

pub fn set_query(
  req: Request(a),
  query: List(#(String, String)),
) -> Request(a)

@lpil
Copy link
Member

lpil commented Mar 14, 2024

In this specific case I think it'd be best to use the Gleam HTTP request type rather than having a custom one

@dmmulroy
Copy link
Contributor Author

I don't think I'm fully following what you're suggesting here - could you clarify?

I concur that I definitely don't need the extra Request and the HttpRequest alias type. I'l probably get rid those tomorrow and just use the gleam_http Request. That being said, I don't want to expose the Request type to users so at some point I need to translate input data to headers. My choices currently are:

  1. Repeatedly calling request.set_header, but this isn't great because the number of headers I may add is dynamic so its clashes with using a single chain of functions via the pipe operator.

  2. Do something like this where I map input data to a gleam_http Request

  Request(..request.new(), headers: headers)
  |> request.set_method(method)
  |> request.set_scheme(Https)
  |> request.set_host(host)
  |> request.set_body(body)
  |> request.set_path(req.path)
  |> request.set_query(query)
  1. Use an "extension module" like request_ext.set_headers

@lpil
Copy link
Member

lpil commented Mar 15, 2024

Let's keep this being something in your codebase for now, and we can see if it's something that there's a demand for later. Time often brings more clarity.

@dmmulroy
Copy link
Contributor Author

Sounds good - thanks for engaging and thanks for your time, always appreciate it.

@lpil
Copy link
Member

lpil commented Mar 15, 2024

No, thank you!! Contributions are super important

@dmmulroy
Copy link
Contributor Author

dmmulroy commented Mar 19, 2024

Okay I'm back, and I feel more strongly now that this should be added.

I just ran into a really confusing "bug" where I had a http request (using httpc) fail because gleam_http did not properly lowercase my headers and when httpc invoked this expression1:

|> request.get_header("content-type")

It does not find the header because I add my headers dynamically and as a list. Right now, the obvious way to do this, as we discussed above, is to supply the header list to the constructor like this2:

Request(..request.new(), method: Get, headers: headers)

Since using a "builder" pattern is fairly idiomatic in Gleam, and appears to be the preferred api with gleam_http along with request.new(). I think we should really consider adding set_headers so that other users can easily add a list of headers via the build pattern/api and have gleam_http take care of the lowercasing for them

I am also opening a second PR3 that I think is more important than this one to make get_header case_insensitive during the search, but I still think we should make it as easy as possibly to do the right thing.

Footnotes

  1. https://github.com/gleam-lang/httpc/blob/v2.1.2/src/gleam/httpc.gleam#L80

  2. https://github.com/dmmulroy/glitch/blob/a508cb1b915934b004d64dae8bdc31fe05064a1b/src/glitch/api/client.gleam#L56-L68

  3. https://github.com/gleam-lang/http/pull/54

@dmmulroy
Copy link
Contributor Author

See #54 for additional context on why this was closed

@dmmulroy dmmulroy closed this Mar 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants