Repository: neovim/neovim
File: runtime/lua/vim/net.lua (Lua)

Code before:
local M = {}

--- Makes an HTTP GET request to the given URL.
---
--- This function operates in two modes:
---   - Synchronous (blocking): If `on_response` is omitted, it waits for the
---   request to complete and returns the result.
---   - Asynchronous (non-blocking): If `on_response` is provided, it returns
---   immediately and invokes the callback with the result later.
---
--- @param url string The URL for the request.
--- @param opts? table Optional parameters:
---   - `verbose` (boolean|nil): Enables curl verbose output.
---   - `retry`   (integer|nil): Number of retries on transient failures (default: 3).
---   - `output`  (string|nil): A file path to save the response body to. If set, the success value will be `true` instead of the response body.
--- @param on_response? fun(err?: string, content?: string|boolean) Optional callback for async execution.
---   It is invoked with `(err, nil)` on failure or `(nil, content)` on success.
--- @return string|boolean|nil In sync mode, returns the response body or `true` on success; otherwise `nil`
--- @return string|nil In sync mode, returns an error message on failure.
function M.request(url, opts, on_response)
  vim.validate({
    url = { url, 'string' },
    opts = { opts, 'table', true },
    on_response = { on_response, 'function', true },
  })

  opts = opts or {}
  local retry = opts.retry or 3

  -- Build curl command
  local args = { 'curl' }
  if opts.verbose then
    table.insert(args, '--verbose')
  else
    vim.list_extend(args, { '--silent', '--show-error', '--fail' })
  end
  table.insert(args, '--location')
  table.insert(args, '--retry')
  table.insert(args, tostring(retry))

  if opts.output then
    table.insert(args, '--output')
    table.insert(args, opts.output)
  end

Review comment (bug):
```suggestion
--- @return string|nil Error message on failure, or nil on success. (Always nil for async invocation.)
```

Code after:
local M = {}

--- Makes an HTTP GET request to the given URL (asynchronous).
---
--- This function operates in one mode:
---   - Asynchronous (non-blocking): Returns immediately and passes the response object to the
---   provided `on_response` handler on completetion.
---
--- @param url string The URL for the request.
--- @param opts? table Optional parameters:
---   - `verbose` (boolean|nil): Enables verbose output.
---   - `retry`   (integer|nil): Number of retries on transient failures (default: 3).
---   - `outpath`  (string|nil): File path to save the response body to. If set, the `body` value in the Response Object will be `true` instead of the response body.
--- @param on_response fun(err?: string, response?: { body: string|boolean }) Callback invoked on request
--- completetion. The `body` field in the response object contains the raw response data (text or binary).
--- Called with (err, nil) on failure, or (nil, { body = string|boolean }) on success.
function M.request(url, opts, on_response)
  vim.validate({
    url = { url, 'string' },
    opts = { opts, 'table', true },
    on_response = { on_response, 'function' },
  })

  opts = opts or {}
  local retry = opts.retry or 3

  -- Build curl command
  local args = { 'curl' }
  if opts.verbose then
    table.insert(args, '--verbose')
  else
    vim.list_extend(args, { '--silent', '--show-error', '--fail' })
  end
  vim.list_extend(args, { '--location', '--retry', tostring(retry) })

  if opts.outpath then
    vim.list_extend(args, { '--output', opts.outpath })
  end

  table.insert(args, url)

  local function on_exit(res)
    local err_msg = nil
    local response = nil