federicotdn / verb

Organize and send HTTP requests from Emacs
https://melpa.org/#/verb
GNU General Public License v3.0
523 stars 21 forks source link

Code tag with multi-line result #67

Closed ongoingrv closed 4 months ago

ongoingrv commented 4 months ago

I have a use case where the authorization headers for an API can be one of two sets:

  1. Only Authorization header.
  2. Access-Token and Client-Secret header

However I am unable to use code tags and have the result contain more than one line.

For example, evaluating {{(format "Access-Token: some-token\nClient-Secret: very-secret")}}
results in Access-Token: some-token instead of the expected

Access-Token: some-token
Client-Secret: very-secret

I would very much like to be able to evaluate elisp functions returning multi line strings, since all the API endpoints are otherwise the same and it would be very inconvenient to have to keep two different setups up to date.

Might be that I am missing something and that what I am trying to achieve is possible, if so I apologize for my ignorance.

ongoingrv commented 4 months ago

Note that I recently started using verb when I found out that restclient, which I have been using until now, has been archived. I am very happy with verb so far :)

federicotdn commented 4 months ago

Hi ! Would it be possible to just use two separate code tags?

Access-Token: {{( ... )}}
Client-Secret: {{( ... )}}

So you would have to pieces of code, each one for generating the contents of each header.

The problem is that currently the request spec parser first scans all the lines of headers, and then expands code tags. However, each line is still considered one header, even the expansion has generated extra lines. The reason tags are not expanded before reading the lines is to avoid evaluating code tags for lines that have been commented out with #.

ongoingrv commented 4 months ago

Thanks for the quick respone!

I have tried to use separate tags now but I am not able to get it to work. My problem is that I would either want the headers Access-Token and Client-Secret, or Authorization. For example for my old restclient setup I have the following function that I use to extract header values:

(defun foo-headers (token)
  "Return"
  (interactive "sToken: ")
  (let ((bearerp (> (length token) 5)))
    (format
     (if bearerp
         (format "Authorization: Bearer %s" token)
       (format "Access-Token: %s\nClient-Secret: <secret>" token)))))

That means I can make only one call to the function to setup the required headers as (foo-headers "XXXX") would result in

Access-Token: XXXX
Client-Secret: <secret>

and (foo-headers "X-Bearer-X") would result in

Authorization: Bearer X-Bearer-X

I tried to set up separate tags, but it seems like the API do not like the empty header values. The setup I tried was the following:

(defun foo-bearer-p (token)
  "Return t if TOKEN is a bearer token."
  (interactive "sToken: ")
  (> (length token) 36))

(defun foo-client-secret (token)
  "Return value for Client-Secret header."
  (interactive "sToken: ")
  (if (foo-bearer-p token)
      ""
    "secret"))

(defun foo-access-token (token)
  "Return value for Access-Token header."
  (interactive "sToken: ")
  (if (foo-bearer-p token)
      ""
    token))

(defun foo-authorization (token)
  "Return value for Authorization header."
  (interactive "sToken: ")
  (if (foo-bearer-p token)
      (concat "Bearer " token)
    ""))

And I setup the headers as

Authorization: {{(foo-authorization (verb-var token))}}
Access-Token: {{(foo-access-token (verb-var token))}}
Client-Secret: {{(foo-client-secret (verb-var token))}}

That means I would end up with headers looking like

Authorization: Bearer someValue
Access-Token:
Client-Secret:

or

Authorization:
Access-Token: someTokenValue
Client-Secret: secret

I also tried to rewrite my logic a bit so that a function would return the whole header for Authorization or Access-Token in one function, and then have a separate function that would return the Client-Secret header or nothing at all. I.e.

(defun foo-auth-or-access-token (token)
  (if (foo-bearer-p token)
      (concat "Authorization: Bearer " token)
    (concat "Access-Token: " token)))

(defun foo-client-secret (token)
  (if (foo-bearer-p token)
      ""
    (concat "Client-Secret: " secret)))

But when using that togeter with

{{(foo-auth-or-access-token (verb-var token))}}
{{(foo-client-secret (verb-var token))}}

I get the error message

Invalid HTTP header: ""
Make sure there’s a blank line between the headers and the request body

Edit: Fix string in defun

ongoingrv commented 4 months ago

Sorry for my huge reply, just wanted to let you know exactly what I tried.

federicotdn commented 4 months ago

No worries, thank you for the lengthy details! I've pushed a commit to main that changes the header parsing. Now it is like this:

  1. Lines with # are removed
  2. Code tags are expanded (which may generate more lines)
  3. Lines are parsed, searching for HEADER: VALUE

The update should be in MELPA soon-ish. Let me know if it works for you! Also added a test: https://github.com/federicotdn/verb/blob/main/test/verb-test.el#L947

ongoingrv commented 4 months ago

Works like a charm, thank you very much! Now I will be able to completely replace restclient.

Thanks for the quick help, I appreciate it very much as I will use this package almost daily at work :)

Edit: Fix broken link.

federicotdn commented 4 months ago

No problem 👍🏻 Thanks for the information regarding restclient.el btw, I wasn't aware.