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

Fix(parser): replay and comments #422

Merged
merged 4 commits into from
Mar 20, 2025
Merged
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
14 changes: 10 additions & 4 deletions lua/kulala/cmd/init.lua
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ local process_request
local reset_task_queue = function()
TASK_QUEUE = {} -- Clear the task queue and stop processing
RUNNING_TASK = false
return true
end

local function run_next_task()
@@ -45,8 +46,10 @@ local function run_next_task()
end)
end

local function offload_task(fn, callback)
local function offload_task(fn, pos, callback)
pos = pos or #TASK_QUEUE + 1
table.insert(TASK_QUEUE, { fn = fn, callback = callback })

if not RUNNING_TASK then run_next_task() end
end

@@ -167,9 +170,12 @@ local function process_response(request_status, parsed_request, callback)
process_metadata(parsed_request)
process_internal(parsed_request)

if process_external(parsed_request) then
process_request({ parsed_request }, parsed_request, {}, callback)
return true
if process_external(parsed_request) then -- replay request
parsed_request.processed = true

offload_task(function()
process_request({ parsed_request }, parsed_request, {}, callback)
end, 1)
end

response_status = save_response(request_status, parsed_request)
51 changes: 28 additions & 23 deletions lua/kulala/parser/document.lua
Original file line number Diff line number Diff line change
@@ -5,49 +5,53 @@ local PARSER_UTILS = require("kulala.parser.utils")

local M = {}

---@class Scripts
---@field pre_request ScriptData
---@field post_request ScriptData

---@class ScriptData
---@field inline string[]
---@field files string[]

---@class DocumentRequest
---@field metadata table<{name: string, value: string}>
---@field variables DocumentVariables
---@field method string
---@field url string
---@field http_version string
---@field headers table<string, string>
---@field headers_raw table<string, string>
---@field cookie string
---@field metadata table<{name: string, value: string}>
---@field body string
---@field body_display string
---@field start_line number
---@field end_line number
---@field show_icon_line_number number
---@field variables DocumentVariables
---@field redirect_response_body_to_files ResponseBodyToFile[]
---@field scripts Scripts
---@field url string
---@field method string
---@field http_version string
---@field processed boolean -- Whether the request has been processed, used by replay()

---@alias DocumentVariables table<string, string|number|boolean>

---@class ResponseBodyToFile
---@field file string -- The file path to write the response body to
---@field overwrite boolean -- Whether to overwrite the file if it already exists

---@class Scripts
---@field pre_request ScriptData
---@field post_request ScriptData

---@class ScriptData
---@field inline string[]
---@field files string[]

---@type DocumentRequest
local default_document_request = {
metadata = {},
variables = {},
method = "",
url = "",
http_version = "",
headers = {},
headers_raw = {},
cookie = "",
metadata = {},
body = "",
body_display = "",
start_line = 0, -- 1-based
end_line = 0, -- 1-based
show_icon_line_number = 1,
variables = {},
redirect_response_body_to_files = {},
scripts = {
pre_request = {
@@ -59,9 +63,7 @@ local default_document_request = {
files = {},
},
},
url = "",
http_version = "",
method = "",
processed = false,
}

local function split_content_by_blocks(lines, line_offset)
@@ -237,6 +239,8 @@ local function parse_url(line)
method, url = line:match("^([A-Z]+)%s+(.+)$")
end

method = method or "GET"

return method, url, http_version
end

@@ -298,9 +302,12 @@ M.get_document = function()
request.end_line = block.end_lnum

for relative_linenr, line in ipairs(block.lines) do
-- skip comments, silently skip URLs that are commented out
if line:match("^#%S") then
if parse_url(line:sub(2)) then skip_block = true end
if line:match("^# @") then
parse_metadata(request, line)
-- skip comments and silently skip URLs that are commented out
elseif line:match("^%s*#") or line:match("^%s*//") then
local _, url = parse_url(line:match("^%s*[#/]+%s*(.+)") or "")
if url then skip_block = true end
-- end of inline scripting
elseif is_request_line and line:match("^%%}$") then
is_prerequest_handler_script_inline = false
@@ -313,8 +320,6 @@ M.get_document = function()
-- inline scripting active: add the line to the prerequest handler scripts
elseif is_prerequest_handler_script_inline then
table.insert(request.scripts.pre_request.inline, line)
elseif line:sub(1, 1) == "#" then
parse_metadata(request, line)
-- we're still in(/before) the request line and we have a pre-request inline handler script
elseif is_request_line and line:match("^< %{%%$") then
is_prerequest_handler_script_inline = true
76 changes: 26 additions & 50 deletions lua/kulala/parser/request.lua
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ local STRING_UTILS = require("kulala.utils.string")
local CURL_FORMAT_FILE = FS.get_plugin_path({ "parser", "curl-format.json" })
local Logger = require("kulala.logger")
local StringVariablesParser = require("kulala.parser.string_variables_parser")
local utils = require("kulala.utils.table")

local M = {}

@@ -18,27 +19,28 @@ M.scripts.javascript = require("kulala.parser.scripts.javascript")

---@class Request
---@field metadata { name: string, value: string }[] -- Metadata of the request
---@field environment table<string, string|number> -- The environment- and document-variables
---@field method string -- The HTTP method of the request
---@field grpc GrpcCommand|nil -- The gRPC command
---@field url_raw string -- The raw URL as it appears in the document
---@field url string -- The URL with variables and dynamic variables replaced
---@field url_raw string -- The raw URL as it appears in the document
---@field http_version string -- The HTTP version of the request
---@field headers table<string, string> -- The headers with variables and dynamic variables replaced
---@field headers_display table<string, string> -- The headers with variables and dynamic variables replaced and sanitized
---@field headers_raw table<string, string> -- The headers as they appear in the document
---@field headers_display table<string, string> -- The headers with variables and dynamic variables replaced and sanitized
---@field ft string -- The filetype of the document
---@field cookie string -- The cookie as it appears in the document
---@field body string|nil -- The body with variables and dynamic variables replaced
---@field body_raw string|nil -- The raw body as it appears in the document
---@field body_computed string|nil -- The computed body as sent by curl; with variables and dynamic variables replaced
---@field body_display string|nil -- The body with variables and dynamic variables replaced and sanitized
---(e.g. with binary files replaced with a placeholder)
---@field body string|nil -- The body with variables and dynamic variables replaced
---@field environment table<string, string|number> -- The environment- and document-variables
---@field cmd string[] -- The command to execute the request
---@field ft string -- The filetype of the document
---@field http_version string -- The HTTP version of the request
---@field show_icon_line_number number|nil -- The line number to show the icon
---@field scripts Scripts -- The scripts to run before and after the request
---@field redirect_response_body_to_files ResponseBodyToFile[]
---@field scripts Scripts -- The scripts to run before and after the request
---@field cmd string[] -- The command to execute the request
---@field body_temp_file string -- The path to the temporary file containing the body
---@field grpc GrpcCommand|nil -- The gRPC command
---@field processed boolean -- Indicates if request has been already processed, used by replay()

---@class GrpcCommand
---@field address string|nil -- host:port, can be omitted if proto|proto-set is provided
@@ -53,36 +55,12 @@ local default_grpc_command = {
}

---@type Request
---@diagnostic disable-next-line: missing-fields
local default_request = {
metadata = {},
method = "GET",
grpc = nil,
http_version = "",
url = "",
url_raw = "",
headers = {},
environment = {},
headers_display = {},
headers_raw = {},
cookie = "",
body = nil,
body_raw = nil,
body_computed = nil,
body_display = nil,
cmd = {},
ft = "text",
environment = {},
redirect_response_body_to_files = {},
scripts = {
pre_request = {
inline = {},
files = {},
},
post_request = {
inline = {},
files = {},
},
},
show_icon_line_number = 1,
cmd = {},
body_temp_file = "",
}

@@ -169,6 +147,8 @@ end
---@param document_variables DocumentVariables -- The variables defined in the document
---@param silent boolean|nil -- Whether to suppress not found variable warnings
local process_variables = function(request, document_variables, silent)
if request.processed then return end

local env = ENV_PARSER.get_env() or {}
local params = { document_variables, env, silent }

@@ -505,19 +485,12 @@ function M.get_basic_request_data(requests, line_nr)

if not document_request then return end

request.scripts.pre_request = document_request.scripts.pre_request
request.scripts.post_request = document_request.scripts.post_request
request.show_icon_line_number = document_request.show_icon_line_number
request.headers = document_request.headers
request.cookie = document_request.cookie
request.headers_raw = document_request.headers_raw
request = vim.tbl_extend("keep", request, document_request)

request.url_raw = document_request.url
request.method = document_request.method
request.http_version = document_request.http_version
request.body_raw = document_request.body
request.body_display = document_request.body_display
request.metadata = document_request.metadata
request.redirect_response_body_to_files = document_request.redirect_response_body_to_files

utils.remove_keys(request, { "body", "variables", "start_line", "end_line" })

return request
end
@@ -544,9 +517,11 @@ M.parse = function(requests, document_variables, line_nr)
local request = M.get_basic_request_data(requests, line_nr)
if not request then return end

set_variables(request, document_variables)
set_headers(request)
process_graphql(request)
if not request.processed then
set_variables(request, document_variables)
set_headers(request)
process_graphql(request)
end

local json = vim.json.encode(request)
FS.write_file(GLOBALS.REQUEST_FILE, json, false)
@@ -565,6 +540,7 @@ M.parse = function(requests, document_variables, line_nr)
-- Save this to global, so .replay() can be triggered from any buffer or window
DB.global_update().replay = vim.deepcopy(request)
DB.global_update().replay.show_icon_line_number = nil
DB.global_update().replay.processed = true

return request
end
6 changes: 6 additions & 0 deletions lua/kulala/utils/table.lua
Original file line number Diff line number Diff line change
@@ -17,4 +17,10 @@ M.slice = function(tbl, first, last)
return sliced
end

M.remove_keys = function(tbl, keys)
vim.iter(keys):each(function(key)
tbl[key] = nil
end)
end

return M
2 changes: 1 addition & 1 deletion scripts/tests.sh
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ run() {
if [[ -n $1 ]]; then
nvim -l tests/minit.lua tests --filter "$1"
else
nvim -l tests/minit.lua tests
nvim -l tests/minit.lua tests -o utfTerminal
fi
}

55 changes: 54 additions & 1 deletion tests/functional/parser_spec.lua
Original file line number Diff line number Diff line change
@@ -81,7 +81,24 @@ describe("requests", function()
})
end)

it("skips reequests comented out with # ", function()
it("processes request only if it has not been processed yet", function()
h.create_buf(
([[
GET https://typicode.com/todos?date=2020-01-01 12:34:56
]]):to_table(true),
"test.http"
)

result = parser.parse() or {}
assert.is_same("https://typicode.com/todos?date=2020-01-01%2012%3A34%3A56", result.url)

result.processed = true

result = parser.parse({ result })
assert.is_same("https://typicode.com/todos?date=2020-01-01%2012%3A34%3A56", result.url)
end)

it("skips requests commented out with # ", function()
h.create_buf(
([[
# @name SIMPLE REQUEST
@@ -96,6 +113,42 @@ describe("requests", function()
assert.is.same({}, result)
end)

it("skips lines commented out with # ", function()
h.create_buf(
([[
# @name SIMPLE REQUEST
POST https://httpbingo.org/simple

{
# "skip": "true",
"test": "value"
}
]]):to_table(true),
"test.http"
)

result = parser.parse() or {}
assert.is_same(result.body:gsub("\n", ""), '{"test": "value"}')
end)

it("skips lines commented out with //", function()
h.create_buf(
([[
# @name SIMPLE REQUEST
POST https://httpbingo.org/simple

{
// "skip": "true",
"test": "value"
}
]]):to_table(true),
"test.http"
)

result = parser.parse() or {}
assert.is_same(result.body:gsub("\n", ""), '{"test": "value"}')
end)

it("processes headers", function()
h.create_buf(
([[
2 changes: 1 addition & 1 deletion tests/functional/ui_spec.lua
Original file line number Diff line number Diff line change
@@ -616,7 +616,7 @@ describe("UI", function()
GLOBALS.VERSION
)
result = vim.fn.getreg("+")
assert.has_string(result, expected)
assert.has_string(expected, result)
end)

it("it shows help hint and window", function()