Skip to content

Commit b86f3bb

Browse files
committed
Add a javascript runner for running javascript before and after requests
This implement a basic javascript runner. * support Response API * support set/get Request API variables * support set/get client global variables * support jsonPath query in javascript Dev notes: A intermediate file is created for each request in the script folder, `last_javascript.mjs` is the last javascript file that was run. This is useful for debugging. For instance you can run the file from the command line `node --inspect-brk last_javascript.mjs` and then open up `chrome://inspect` in chrome based browers and debug the javascript.
1 parent 62606c3 commit b86f3bb

File tree

5 files changed

+433
-0
lines changed

5 files changed

+433
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
doc/tags
22
.repro/
33
repro*.lua
4+
lua/rest-nvim/script/node_modules
5+
lua/rest-nvim/script/last_javascript.mjs
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
local script = {}
2+
local logger = require("rest-nvim.logger")
3+
4+
local has_js = vim.fn.has('node')
5+
6+
if has_js == 0 then
7+
print("Neovim was not built with JavaScript support")
8+
end
9+
10+
local function cwd()
11+
local current_script_path = debug.getinfo(1).source:match("@(.*)")
12+
return current_script_path:match("(.*[/\\])") or "."
13+
end
14+
15+
local function read_file(filename)
16+
local file_to_open = cwd() .. "/" .. filename
17+
18+
local file = io.open(file_to_open, "r")
19+
if file then
20+
local file_content = file:read("*all")
21+
file:close()
22+
return file_content
23+
else
24+
print("Error: Could not open file " .. file_to_open)
25+
end
26+
end
27+
28+
local function write_file(filename, content)
29+
local file_to_open = cwd() .. "/" .. filename
30+
local file = io.open(file_to_open, "w")
31+
if file then
32+
file:write(content)
33+
file:close()
34+
else
35+
print("Error: Could not open file " .. file_to_open)
36+
end
37+
return file_to_open
38+
end
39+
40+
local function local_vars (ctx)
41+
local ctx_vars = {}
42+
for k, v in pairs(ctx.vars) do
43+
ctx_vars[k] = v
44+
end
45+
for k, v in pairs(ctx.lv) do
46+
ctx_vars[k] = v
47+
end
48+
return ctx_vars
49+
end
50+
51+
local function create_prescript_env(ctx)
52+
return {
53+
_env = { cwd = cwd() },
54+
request = { variables = local_vars(ctx) }
55+
}
56+
end
57+
58+
local function create_handler_env(ctx, res)
59+
local env_vars = {}
60+
-- can't do pairs(vim.env) instead need to use environ
61+
for k, v in pairs(vim.fn.environ()) do
62+
-- ignore values which will make escaping complicated
63+
if not string.find(v, "[\\'\"`]") then
64+
env_vars[k] = v
65+
end
66+
end
67+
68+
local response = res
69+
-- TODO check mime type before parsing
70+
local ok, decoded_body = pcall(vim.fn.json_decode, res.body)
71+
if ok then
72+
response = vim.deepcopy(res)
73+
response.body = decoded_body
74+
end
75+
76+
return {
77+
_env = { cwd = cwd() },
78+
client = { global = { data = env_vars } },
79+
request = { variables = local_vars(ctx) },
80+
response = response,
81+
}
82+
end
83+
84+
local function execute_cmd(cmd)
85+
local handle = io.popen(cmd)
86+
if handle then
87+
local result = handle:read("*a")
88+
handle:close()
89+
return result
90+
end
91+
end
92+
93+
local js_str = read_file("javascript.mjs");
94+
95+
local function load_js(s, env)
96+
local env_json = vim.fn.json_encode(env)
97+
98+
-- uncomment to load each time when developing
99+
-- local js_str = read_file("javascript.mjs");
100+
101+
return function(...)
102+
local js_code = string.format(js_str, env_json, s)
103+
104+
-- save to file so no need to escape quotes
105+
local file_path = write_file('last_javascript.mjs', js_code)
106+
107+
local ok, result = pcall(function()
108+
return execute_cmd("node " .. file_path)
109+
end)
110+
if not ok then
111+
logger.error("JS execution error: " .. tostring(result))
112+
return nil
113+
end
114+
115+
return result
116+
end
117+
end
118+
119+
local function split_string_on_separator(multiline_str)
120+
local before_separator = {}
121+
local after_separator = {}
122+
local found_separator = false
123+
124+
for line in multiline_str:gmatch("[^\r\n]+") do
125+
if line == "-ENV-" then
126+
found_separator = true
127+
elseif found_separator then
128+
table.insert(after_separator, line)
129+
else
130+
table.insert(before_separator, line)
131+
end
132+
end
133+
134+
return table.concat(before_separator), table.concat(after_separator)
135+
end
136+
137+
local function update_local(ctx, env_json)
138+
for key, value in pairs(env_json.request.variables) do
139+
ctx:set_local(key, value)
140+
end
141+
end
142+
143+
local function update_global(env, env_json)
144+
for key, value in pairs(env_json.client.global.data) do
145+
env[key] = value
146+
end
147+
end
148+
149+
function script.load_pre_req_hook(s, ctx)
150+
return function ()
151+
local result = load_js(s, create_prescript_env(ctx))(s, ctx)
152+
local logs, json_str = split_string_on_separator(result)
153+
print(logs)
154+
local env_json = vim.fn.json_decode(json_str)
155+
update_local(ctx, env_json)
156+
end
157+
end
158+
159+
function script.load_post_req_hook(s, ctx)
160+
return function(res)
161+
local result = load_js(s, create_handler_env(ctx, res))(s, ctx)
162+
local logs, json_str = split_string_on_separator(result)
163+
print(logs)
164+
local env_json = vim.fn.json_decode(json_str)
165+
update_global(vim.env, env_json)
166+
update_local(ctx, env_json)
167+
end
168+
end
169+
170+
return script
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
try {
2+
const ctx = JSON.parse(`%s`);
3+
4+
let jsonPath;
5+
try {
6+
let jsonPathModule = await import("jsonpath");
7+
jsonPath = function (...args) {
8+
return jsonPathModule.default.value(...args);
9+
};
10+
} catch (e) {
11+
console.log(`jsonpath not found please install \`npm install --prefix ${ctx._env.cwd}\``);
12+
}
13+
14+
let client;
15+
if (ctx.client) {
16+
// empty table json encoded as array by lua
17+
ctx.client.global.data = Array.isArray(ctx.client.global.data) ? {} : ctx.client.global.data;
18+
19+
client = {
20+
global: {
21+
data: ctx.client.global.data,
22+
get: function (key) {
23+
return ctx.client.global.data[key];
24+
},
25+
set: function (key, value) {
26+
ctx.client.global.data[key] = value;
27+
},
28+
},
29+
};
30+
}
31+
32+
let response;
33+
if (ctx.response) {
34+
//https://www.jetbrains.com/help/idea/http-response-reference.html
35+
response = {
36+
body: ctx.response.body,
37+
headers: {
38+
valueOf: function(key) {
39+
return ctx.response.headers[key]?.[0]
40+
},
41+
valuesOf: function(key) {
42+
return ctx.response.headers[key]
43+
}
44+
},
45+
status: ctx.response.status.code,
46+
contentType: {
47+
mineType: ctx.response.headers["content-type"]?.[0]?.split(";")?.[0],
48+
charset: ctx.response.headers["content-type"]?.[0].split(";")?.[0]
49+
}
50+
}
51+
}
52+
53+
const request = {
54+
variables: {
55+
get: function (key) {
56+
return ctx.request.variables[key];
57+
},
58+
set: function (key, value) {
59+
ctx.request.variables[key] = value;
60+
},
61+
}
62+
}
63+
64+
;(function(){ %s })();
65+
66+
console.log("-ENV-");
67+
console.log(JSON.stringify(ctx));
68+
} catch (error) {
69+
console.log(error);
70+
console.log("-ENV-");
71+
console.log("{}");
72+
}

0 commit comments

Comments
 (0)