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

Feature: redirected requests #443

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 109 additions & 31 deletions lua/rest-nvim/client/curl/cli.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,65 @@ end
---@private
local parser = {}

---@class rest.Result
---@field requests rest.Response_[]
---@field statistics table<string,string> Response statistics

---@class rest.Response_
---@field request rest.RequestCore
---@field response rest.Response

---@class rest.RequestCore
---@field method string
---@field url string
---@field http_version string
---@field headers table<string,string[]>

---@package
---@param str string
---@return rest.Response.status
function parser.parse_verbose_status(str)
local version, code, text = str:match("^(%S+) (%d+) ?(.*)")
---@return rest.RequestCore?
function parser.parse_req_info(str)
local method, url, version = str:match("^([A-Z]+) (.+) (HTTP/[%d%.]+)")
if not method then
return
end
return {
method = method,
url = url,
http_version = version,
headers = {},
}
end

function parser.parse_req_header(str, requests)
local info = parser.parse_req_info(str)
if info then
table.insert(requests, {
request = info,
response = {},
})
return
end
local req = requests[#requests].request
local key, value = parser.parse_header_pair(str)
if key then
if not req.headers[key] then
req.headers[key] = {}
end
table.insert(req.headers[key], value)
else
log.error("Error while parsing verbose curl output header:", str)
end
end

---@package
---@param str string
---@return rest.Response.status?
function parser.parse_res_status(str)
local version, code, text = str:match("^(HTTP/[%d%.]+) (%d+) ?(.*)")
if not version then
return
end
return {
version = version,
code = tonumber(code),
Expand All @@ -73,22 +127,49 @@ function parser.parse_header_pair(str)
end

---@package
---@param str string
function parser.parse_res_header(str, requests)
local status = parser.parse_res_status(str)
if status then
-- reset response object
requests[#requests].response = {
status = status,
headers = {},
}
return
end
local res = requests[#requests].response
local key, value = parser.parse_header_pair(str)
if key then
if not res.headers[key] then
res.headers[key] = {}
end
table.insert(res.headers[key], value)
else
log.error("Error while parsing verbose curl output header:", str)
end
end

---@package
---@param idx number
---@param line string
---@return {prefix:string,str:string?}|nil
function parser.parse_verbose_line(line)
---@return {idx:number,prefix:string,str:string?}|nil
function parser.lex_verbose_line(idx, line)
local prefix, str = line:match("(.) ?(.*)")
log.debug("line", idx, line)
if not prefix then
log.error("Error while parsing verbose curl output:\n" .. line)
log.error(("Error while parsing verbose curl output at line %d:"):format(idx), line)
return
end
return {
idx = idx,
prefix = prefix,
str = str,
}
end

local _VERBOSE_PREFIX_META = "*"
local _VERBOSE_PREFIX_REQ_HEADER = ">"
local VERBOSE_PREFIX_REQ_HEADER = ">"
local _VERBOSE_PREFIX_REQ_BODY = "}"
local VERBOSE_PREFIX_RES_HEADER = "<"
-- NOTE: we don't parse response body with trace output. response body will
Expand All @@ -114,35 +195,32 @@ function parser.parse_stat_pair(str)
end

---@param lines string[]
---@return rest.Response
---@return rest.Result
function parser.parse_verbose(lines)
local response = {
headers = {},
---@type rest.Result
local result = {
---@type rest.Response_[]
requests = {},
---@type table<string,string> Response statistics
statistics = {},
}
vim.iter(lines):map(parser.parse_verbose_line):each(function(ln)
if ln.prefix == VERBOSE_PREFIX_RES_HEADER then
if not response.status then
-- response status
response.status = parser.parse_verbose_status(ln.str)
else
-- response header
local key, value = parser.parse_header_pair(ln.str)
if key then
if not response.headers[key] then
response.headers[key] = {}
end
table.insert(response.headers[key], value)
end
end
-- ignore last newline
if lines[#lines] == "" then
lines[#lines] = nil
end
vim.iter(lines):enumerate():map(parser.lex_verbose_line):each(function(ln)
if ln.prefix == VERBOSE_PREFIX_REQ_HEADER then
parser.parse_req_header(ln.str, result.requests)
elseif ln.prefix == VERBOSE_PREFIX_RES_HEADER then
parser.parse_res_header(ln.str, result.requests)
elseif ln.prefix == VERBOSE_PREFIX_STAT then
local key, value = parser.parse_stat_pair(ln.str)
if key then
response.statistics[key] = value
result.statistics[key] = value
end
end
end)
return response
return result
end

--- Builder ---
Expand Down Expand Up @@ -330,7 +408,7 @@ end

---Send request via `curl` cli
---@param request rest.Request Request data to be passed to cURL
---@return nio.control.Future future Future containing rest.Response
---@return nio.control.Future future Future containing rest.Result
function curl.request(request)
local progress_handle = progress.handle.create({
title = "Executing",
Expand All @@ -352,9 +430,9 @@ function curl.request(request)
progress_handle:report({
message = "Parsing response...",
})
local response = parser.parse_verbose(vim.split(sc.stderr, "\n"))
response.body = sc.stdout
future.set(response)
local result = parser.parse_verbose(vim.split(sc.stderr, "\n"))
result.requests[#result.requests].response.body = sc.stdout
future.set(result)
progress_handle:report({
message = "Success",
})
Expand Down
26 changes: 22 additions & 4 deletions lua/rest-nvim/client/curl/libcurl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ end
---Execute an HTTP request using cURL
---return return nil if execution failed
---@param req rest.Request Request data to be passed to cURL
---@return rest.Response? info The request information (url, method, headers, body, etc)
---@return rest.Result?
function client.request(req)
logger.info("sending request to: " .. req.url)
if not found_curl then
Expand Down Expand Up @@ -162,8 +162,13 @@ function client.request(req)
logger.error("Something went wrong when making the request with cURL:\n" .. curl_utils.curl_error(err:no()))
return
end
local status_str = table.remove(res_raw_headers, 1)
---@diagnostic disable-next-line: invisible
local status = curl_cli.parser.parse_verbose_status(table.remove(res_raw_headers, 1))
local status = curl_cli.parser.parse_res_status(status_str)
if not status then
logger.error("can't parse response status:", status_str)
return
end
local res_headers = {}
for _, header in ipairs(res_raw_headers) do
---@diagnostic disable-next-line: invisible
Expand All @@ -180,12 +185,25 @@ function client.request(req)
status = status,
headers = res_headers,
body = table.concat(res_result),
statistics = get_stats(req_, {}),
}
logger.debug(vim.inspect(res.headers))
res.status.text = vim.trim(res.status.text)
req_:close()
return res
---@type rest.Result
return {
requests = {
{
request = {
method = req.method,
url = req.url,
http_version = req.http_version or "HTTP/1.1",
headers = req.headers,
},
response = res,
},
},
statistics = get_stats(req_, {}),
}
end

return client
11 changes: 6 additions & 5 deletions lua/rest-nvim/request.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ local Context = require("rest-nvim.context").Context
---@field status rest.Response.status Status information from response
---@field body string? Raw response body
---@field headers table<string,string[]> Response headers
---@field statistics table<string,string> Response statistics

---@class rest.Response.status
---@field code number
Expand Down Expand Up @@ -69,28 +68,30 @@ local function run_request(req)
vim.notify("request failed. See `:Rest logs` for more info", vim.log.levels.ERROR, { title = "rest.nvim" })
return
end
---@cast res rest.Response
---@cast res rest.Result
logger.info("request success")

local last_response = res.requests[#res.requests].response

-- run request handler scripts
vim.iter(req.handlers):each(function(f)
f(res)
end)
logger.info("handler done")

_G.rest_request = req
_G.rest_response = res
_G.rest_response = last_response
vim.api.nvim_exec_autocmds("User", {
pattern = { "RestResponse", "RestResponsePre" },
})
_G.rest_request = nil
_G.rest_response = nil

-- update cookie jar
jar.update_jar(req.url, res)
jar.update_jar(req.url, last_response)

-- update result UI
ui.update({ response = res })
ui.update({ response = last_response, statistics = res.statistics })
end)
-- FIXME(boltless): return future to pass the command state
end
Expand Down
25 changes: 9 additions & 16 deletions lua/rest-nvim/ui/result.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ local data = {
request = nil,
---@type rest.Response?
response = nil,
---@type table<string,string>?
statistics = nil,
}

---@param req rest.Request
Expand Down Expand Up @@ -76,8 +78,9 @@ local panes = {
)
)
local content_type = data.response.headers["content-type"]
table.insert(lines, "")
table.insert(lines, "# @_RES")
local body = vim.split(data.response.body, "\n")
local body_meta = {}
if content_type then
local base_type, res_type = content_type[1]:match("(.*)/([^;]+)")
if base_type == "image" then
Expand All @@ -86,19 +89,9 @@ local panes = {
body = { "Binary answer" }
elseif config.response.hooks.format then
-- NOTE: format hook runs here because it should be done last.
local ok
body, ok = utils.gq_lines(body, res_type)
if ok then
table.insert(body_meta, "formatted")
end
body = utils.gq_lines(body, res_type)
end
end
local meta_str = ""
if #body_meta > 0 then
meta_str = " (" .. table.concat(body_meta, ",") .. ")"
end
table.insert(lines, "")
table.insert(lines, "# @_RES" .. meta_str)
vim.list_extend(lines, body)
table.insert(lines, "# @_END")
else
Expand Down Expand Up @@ -166,14 +159,14 @@ local panes = {
return
end
local lines = {}
if not data.response.statistics then
if not data.statistics then
set_lines(self.bufnr, { "No Statistics" })
return
end
syntax_highlight(self.bufnr, "http_stat")
for _, style in ipairs(config.clients.curl.statistics) do
local title = style.title or style.id
local value = data.response.statistics[style.id] or ""
local value = data.statistics[style.id] or ""
table.insert(lines, ("%s: %s"):format(title, value))
end
set_lines(self.bufnr, lines)
Expand All @@ -191,7 +184,7 @@ winbar = winbar .. "%#RestText#Press %#Keyword#?%#RestText# for help%#Normal# "
---@return string
function ui.stat_winbar()
local content = ""
if not data.response then
if not data.statistics then
return "Loading...%#Normal#"
end
for _, style in ipairs(config.clients.curl.statistics) do
Expand All @@ -200,7 +193,7 @@ function ui.stat_winbar()
if title ~= "" then
title = title .. ": "
end
local value = data.response.statistics[style.id] or ""
local value = data.statistics[style.id] or ""
content = content .. " %#RestText#" .. title .. "%#Normal#" .. value
end
end
Expand Down
Loading