Skip to content

Commit 6bec8c7

Browse files
author
lex
committed
feat(http): auto-execute dependent requests for cross-file run directives
When request2 depends on request1's response via {{request1.response.body.*}}, and request1 hasn't been executed yet, running request2 now automatically executes request1 first, caches its response, then substitutes the value. Changes: - request_vars.lua: expose find_request_variable_refs publicly - request_vars.lua: add is_response_cached() for cache status check - request_vars.lua: add collect_requests_from_content() to parse named requests from arbitrary content (not buffer-dependent) - request_vars.lua: add resolve_content_dependencies() - the core function that resolves {{Name.response.body.*}} refs in arbitrary content by executing uncached dependencies found in the same file (topological DFS) - import.lua: execute_run_directive() now resolves dependencies in the target block before processing pre-scripts - import.lua: execute_all_requests() now resolves dependencies per-request before execution - Both paths cache responses via cache_response() so subsequent requests find their dependencies already resolved Closes: cross-file request dependency resolution
1 parent 3012d3e commit 6bec8c7

2 files changed

Lines changed: 282 additions & 133 deletions

File tree

lua/poste/http/import.lua

Lines changed: 148 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
--- - Aliased requests only accessible via #alias.Name, not bare #Name
1414
--- - Bare import name collisions: later overrides earlier (warning)
1515
local state = require("poste.state")
16+
local request_vars = require("poste.http.request_vars")
1617

1718
local M = {}
1819

@@ -475,81 +476,88 @@ function M.execute_run_directive(opts, callback)
475476
local block_end = find_block_end(content, opts.line)
476477
local orig_block_end = block_end -- original bounds for post-script on original content
477478

478-
-- Process pre-script from target block
479-
local modified_content, injected_count = process_target_pre_script(content, opts.line, block_end)
480-
block_end = block_end + injected_count
479+
-- Resolve request variable dependencies in target block first.
480+
-- This executes any uncached {{Name.response.body.*}} refs pointing to
481+
-- requests in the same file, then substitutes their values before proceeding.
482+
request_vars.resolve_content_dependencies(binary, opts.path, state.current_env, content, opts.line, function(dep_resolved_content)
483+
-- Process pre-script from target block (on dependency-resolved content)
484+
local modified_content, injected_count = process_target_pre_script(dep_resolved_content, opts.line, block_end)
485+
block_end = block_end + injected_count
486+
487+
-- Apply variable overrides if specified
488+
if opts.vars and next(opts.vars) then
489+
opts.line = opts.line + injected_count
490+
modified_content = M.apply_variable_overrides(modified_content, opts.line, opts.vars)
491+
end
481492

482-
-- Apply variable overrides if specified
483-
if opts.vars and next(opts.vars) then
484-
opts.line = opts.line + injected_count
485-
modified_content = M.apply_variable_overrides(modified_content, opts.line, opts.vars)
486-
end
493+
local cmd = string.format("%s run %s --line %d --env %s --json --stdin",
494+
vim.fn.shellescape(binary),
495+
vim.fn.shellescape(opts.path),
496+
opts.line,
497+
vim.fn.shellescape(state.current_env)
498+
)
499+
500+
state.log("INFO", string.format("import run: %s", cmd))
501+
502+
local stdout_buf = {}
503+
local stderr_buf = {}
504+
505+
local job_id = vim.fn.jobstart(cmd, {
506+
stdin = "pipe",
507+
stdout_buffered = true,
508+
stderr_buffered = true,
509+
on_stdout = function(_, data)
510+
if not data then return end
511+
for _, line in ipairs(data) do
512+
if line ~= "" then table.insert(stdout_buf, line) end
513+
end
514+
end,
515+
on_stderr = function(_, data)
516+
if not data then return end
517+
for _, line in ipairs(data) do
518+
if line ~= "" then table.insert(stderr_buf, line) end
519+
end
520+
end,
521+
on_exit = function(_, code)
522+
if code ~= 0 then
523+
vim.schedule(function()
524+
vim.notify(string.format("run directive failed (exit %d): %s", code,
525+
table.concat(stderr_buf, "\n")), vim.log.levels.ERROR, { title = "Poste" })
526+
if callback then callback(false, {
527+
status = 0,
528+
status_text = "Failed (exit " .. code .. ")",
529+
body = table.concat(stderr_buf, "\n"),
530+
}) end
531+
end)
532+
return
533+
end
487534

488-
local cmd = string.format("%s run %s --line %d --env %s --json --stdin",
489-
vim.fn.shellescape(binary),
490-
vim.fn.shellescape(opts.path),
491-
opts.line,
492-
vim.fn.shellescape(state.current_env)
493-
)
494-
495-
state.log("INFO", string.format("import run: %s", cmd))
496-
497-
local stdout_buf = {}
498-
local stderr_buf = {}
499-
500-
local job_id = vim.fn.jobstart(cmd, {
501-
stdin = "pipe",
502-
stdout_buffered = true,
503-
stderr_buffered = true,
504-
on_stdout = function(_, data)
505-
if not data then return end
506-
for _, line in ipairs(data) do
507-
if line ~= "" then table.insert(stdout_buf, line) end
508-
end
509-
end,
510-
on_stderr = function(_, data)
511-
if not data then return end
512-
for _, line in ipairs(data) do
513-
if line ~= "" then table.insert(stderr_buf, line) end
514-
end
515-
end,
516-
on_exit = function(_, code)
517-
if code ~= 0 then
518-
vim.schedule(function()
519-
vim.notify(string.format("run directive failed (exit %d): %s", code,
520-
table.concat(stderr_buf, "\n")), vim.log.levels.ERROR, { title = "Poste" })
535+
local stdout_text = table.concat(stdout_buf, "\n")
536+
local ok, parsed = pcall(vim.json.decode, stdout_text)
537+
if ok and type(parsed) == "table" then
538+
-- Run target block's post-script (so client.global.set() persists)
539+
process_target_post_script(parsed, content, opts.line, orig_block_end)
540+
-- Cache the response for cross-request variable resolution
541+
request_vars.cache_response(opts.request_name, parsed)
542+
if callback then callback(true, parsed) end
543+
else
521544
if callback then callback(false, {
522545
status = 0,
523-
status_text = "Failed (exit " .. code .. ")",
524-
body = table.concat(stderr_buf, "\n"),
546+
status_text = "JSON parse failed",
547+
body = stdout_text,
525548
}) end
526-
end)
527-
return
528-
end
529-
530-
local stdout_text = table.concat(stdout_buf, "\n")
531-
local ok, parsed = pcall(vim.json.decode, stdout_text)
532-
if ok and type(parsed) == "table" then
533-
-- Run target block's post-script (so client.global.set() persists)
534-
process_target_post_script(parsed, content, opts.line, orig_block_end)
535-
if callback then callback(true, parsed) end
536-
else
537-
if callback then callback(false, {
538-
status = 0,
539-
status_text = "JSON parse failed",
540-
body = stdout_text,
541-
}) end
542-
end
543-
end,
544-
})
549+
end
550+
end,
551+
})
545552

546-
if job_id > 0 then
547-
vim.fn.chansend(job_id, modified_content)
548-
vim.fn.chanclose(job_id, "stdin")
549-
else
550-
vim.notify("Failed to start poste job for run directive", vim.log.levels.ERROR, { title = "Poste" })
551-
if callback then callback(false, nil) end
552-
end
553+
if job_id > 0 then
554+
vim.fn.chansend(job_id, modified_content)
555+
vim.fn.chanclose(job_id, "stdin")
556+
else
557+
vim.notify("Failed to start poste job for run directive", vim.log.levels.ERROR, { title = "Poste" })
558+
if callback then callback(false, nil) end
559+
end
560+
end)
553561

554562
elseif opts.action == "execute_all" then
555563
-- Execute all requests in the target file
@@ -589,73 +597,80 @@ function M.execute_all_requests(file_path, content, vars, callback)
589597
local req = requests[idx]
590598
idx = idx + 1
591599

592-
-- Find block bounds and process pre-script from target block
593-
local block_end = find_block_end(content, req.line)
594-
local modified_content, _ = process_target_pre_script(content, req.line, block_end)
600+
-- Resolve request variable dependencies in this block first.
601+
-- This executes any uncached {{Name.response.body.*}} refs pointing to
602+
-- requests in the same file, then substitutes their values.
603+
request_vars.resolve_content_dependencies(binary, file_path, state.current_env, content, req.line, function(dep_resolved_content)
604+
-- Find block bounds and process pre-script from target block (on dependency-resolved content)
605+
local block_end = find_block_end(dep_resolved_content, req.line)
606+
local modified_content, _ = process_target_pre_script(dep_resolved_content, req.line, block_end)
595607

596-
if vars and next(vars) then
597-
modified_content = M.apply_variable_overrides(modified_content, req.line, vars)
598-
end
608+
if vars and next(vars) then
609+
modified_content = M.apply_variable_overrides(modified_content, req.line, vars)
610+
end
599611

600-
local cmd = string.format("%s run %s --line %d --env %s --json --stdin",
601-
vim.fn.shellescape(binary),
602-
vim.fn.shellescape(file_path),
603-
req.line,
604-
vim.fn.shellescape(state.current_env)
605-
)
606-
607-
local stdout_buf = {}
608-
local stderr_buf = {}
609-
610-
local job_id = vim.fn.jobstart(cmd, {
611-
stdin = "pipe",
612-
stdout_buffered = true,
613-
stderr_buffered = true,
614-
on_stdout = function(_, data)
615-
if not data then return end
616-
for _, line in ipairs(data) do
617-
if line ~= "" then table.insert(stdout_buf, line) end
618-
end
619-
end,
620-
on_stderr = function(_, data)
621-
if not data then return end
622-
for _, line in ipairs(data) do
623-
if line ~= "" then table.insert(stderr_buf, line) end
624-
end
625-
end,
626-
on_exit = function(_, code)
627-
local stdout_text = table.concat(stdout_buf, "\n")
628-
local ok, parsed = pcall(vim.json.decode, stdout_text)
629-
if ok and type(parsed) == "table" then
630-
-- Run target block's post-script so client.global.set() persists
631-
process_target_post_script(parsed, content, req.line, block_end)
632-
table.insert(results, { name = req.name, response = parsed })
633-
else
634-
table.insert(results, { name = req.name, response = {
635-
status = code,
636-
status_text = "Failed",
637-
body = stdout_text,
638-
stderr = table.concat(stderr_buf, "\n"),
639-
}})
640-
end
641-
vim.schedule(function()
642-
vim.notify(string.format("[%d/%d] %s — %s", idx - 1, #requests, req.name,
643-
parsed and (parsed.status .. " " .. (parsed.status_text or "")) or "failed"),
644-
vim.log.levels.INFO, { title = "Poste" })
645-
end)
646-
execute_next()
647-
end,
648-
})
612+
local cmd = string.format("%s run %s --line %d --env %s --json --stdin",
613+
vim.fn.shellescape(binary),
614+
vim.fn.shellescape(file_path),
615+
req.line,
616+
vim.fn.shellescape(state.current_env)
617+
)
618+
619+
local stdout_buf = {}
620+
local stderr_buf = {}
621+
622+
local job_id = vim.fn.jobstart(cmd, {
623+
stdin = "pipe",
624+
stdout_buffered = true,
625+
stderr_buffered = true,
626+
on_stdout = function(_, data)
627+
if not data then return end
628+
for _, line in ipairs(data) do
629+
if line ~= "" then table.insert(stdout_buf, line) end
630+
end
631+
end,
632+
on_stderr = function(_, data)
633+
if not data then return end
634+
for _, line in ipairs(data) do
635+
if line ~= "" then table.insert(stderr_buf, line) end
636+
end
637+
end,
638+
on_exit = function(_, code)
639+
local stdout_text = table.concat(stdout_buf, "\n")
640+
local ok, parsed = pcall(vim.json.decode, stdout_text)
641+
if ok and type(parsed) == "table" then
642+
-- Run target block's post-script so client.global.set() persists
643+
process_target_post_script(parsed, content, req.line, block_end)
644+
-- Cache the response for cross-request variable resolution
645+
request_vars.cache_response(req.name, parsed)
646+
table.insert(results, { name = req.name, response = parsed })
647+
else
648+
table.insert(results, { name = req.name, response = {
649+
status = code,
650+
status_text = "Failed",
651+
body = stdout_text,
652+
stderr = table.concat(stderr_buf, "\n"),
653+
}})
654+
end
655+
vim.schedule(function()
656+
vim.notify(string.format("[%d/%d] %s — %s", idx - 1, #requests, req.name,
657+
parsed and (parsed.status .. " " .. (parsed.status_text or "")) or "failed"),
658+
vim.log.levels.INFO, { title = "Poste" })
659+
end)
660+
execute_next()
661+
end,
662+
})
649663

650-
if job_id > 0 then
651-
vim.fn.chansend(job_id, modified_content)
652-
vim.fn.chanclose(job_id, "stdin")
653-
else
654-
table.insert(results, { name = req.name, response = {
655-
status = 0, status_text = "Job start failed", body = "",
656-
}})
657-
execute_next()
658-
end
664+
if job_id > 0 then
665+
vim.fn.chansend(job_id, modified_content)
666+
vim.fn.chanclose(job_id, "stdin")
667+
else
668+
table.insert(results, { name = req.name, response = {
669+
status = 0, status_text = "Job start failed", body = "",
670+
}})
671+
execute_next()
672+
end
673+
end)
659674
end
660675

661676
execute_next()

0 commit comments

Comments
 (0)