Skip to content

Commit 335f997

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 335f997

File tree

5 files changed

+422
-0
lines changed

5 files changed

+422
-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: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
local script = {}
2+
local logger = require("rest-nvim.logger")
3+
4+
local has_js = vim.fn.executable('node')
5+
6+
if has_js == 0 then
7+
print("node is not available")
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 response = res
60+
-- TODO check mime type before parsing
61+
local ok, decoded_body = pcall(vim.fn.json_decode, res.body)
62+
if ok then
63+
response = vim.deepcopy(res)
64+
response.body = decoded_body
65+
end
66+
67+
return {
68+
_env = { cwd = cwd() },
69+
client = { global = { data = {} } },
70+
request = { variables = local_vars(ctx) },
71+
response = response,
72+
}
73+
end
74+
75+
local function execute_cmd(cmd)
76+
local handle = io.popen(cmd)
77+
if handle then
78+
local result = handle:read("*a")
79+
handle:close()
80+
return result
81+
end
82+
end
83+
84+
local js_str = read_file("javascript.mjs");
85+
86+
local function load_js(s, env)
87+
local env_json = vim.fn.json_encode(env):gsub("\\", "\\\\") -- Escape backslashes
88+
89+
-- uncomment to load each time when developing
90+
-- local js_str = read_file("javascript.mjs");
91+
92+
local js_code = string.format(js_str, env_json, s)
93+
94+
-- save to file so no need to escape quotes
95+
local file_path = write_file('last_javascript.mjs', js_code)
96+
97+
local ok, result = pcall(function()
98+
return execute_cmd("node " .. file_path)
99+
end)
100+
if not ok then
101+
logger.error("JS execution error: " .. tostring(result))
102+
return nil
103+
end
104+
105+
return result
106+
end
107+
108+
local function split_string_on_separator(multiline_str)
109+
local before_separator = {}
110+
local after_separator = {}
111+
local found_separator = false
112+
113+
for line in multiline_str:gmatch("[^\r\n]+") do
114+
if line == "-ENV-" then
115+
found_separator = true
116+
elseif found_separator then
117+
table.insert(after_separator, line)
118+
else
119+
table.insert(before_separator, line)
120+
end
121+
end
122+
123+
return table.concat(before_separator), table.concat(after_separator)
124+
end
125+
126+
local function update_local(ctx, env_json)
127+
for key, value in pairs(env_json.request.variables) do
128+
ctx:set_local(key, value)
129+
end
130+
end
131+
132+
local function update_global(env, env_json)
133+
for key, value in pairs(env_json.client.global.data) do
134+
env[key] = value
135+
end
136+
end
137+
138+
function script.load_pre_req_hook(s, ctx)
139+
return function ()
140+
local result = load_js(s, create_prescript_env(ctx))
141+
local logs, json_str = split_string_on_separator(result)
142+
print(logs)
143+
local env_json = vim.fn.json_decode(json_str)
144+
update_local(ctx, env_json)
145+
end
146+
end
147+
148+
function script.load_post_req_hook(s, ctx)
149+
return function(res)
150+
local result = load_js(s, create_handler_env(ctx, res))
151+
local logs, json_str = split_string_on_separator(result)
152+
print(logs)
153+
local env_json = vim.fn.json_decode(json_str)
154+
update_global(vim.env, env_json)
155+
update_local(ctx, env_json)
156+
end
157+
end
158+
159+
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)