@port-labs/jq-node-bindings 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -1,4 +1,13 @@
1
1
  declare module '@port-labs/jq-node-bindings' {
2
- export function exec(json: object, input: string, options?: {enableEnv?: boolean}): object | Array<any> | string | number | boolean | null;
3
- export function renderRecursively(json: object, input: object | Array<any> | string | number | boolean | null): object | Array<any> | string | number | boolean | null;
2
+ type ExecOptions = { enableEnv?: boolean, throwOnError?: boolean };
3
+
4
+ export class JqExecError extends Error {
5
+ }
6
+
7
+ export class JqExecCompileError extends Error {
8
+ }
9
+
10
+ export function exec(json: object, input: string, options?: ExecOptions): object | Array<any> | string | number | boolean | null;
11
+
12
+ export function renderRecursively(json: object, input: object | Array<any> | string | number | boolean | null, execOptions?: ExecOptions): object | Array<any> | string | number | boolean | null;
4
13
  }
package/lib/index.js CHANGED
@@ -4,5 +4,7 @@ const template = require('./template');
4
4
 
5
5
  module.exports = {
6
6
  exec: jq.exec,
7
- renderRecursively: template.renderRecursively
7
+ renderRecursively: template.renderRecursively,
8
+ JqExecError: jq.JqExecError,
9
+ JqExecCompileError: jq.JqExecCompileError,
8
10
  };
package/lib/jq.js CHANGED
@@ -1,21 +1,34 @@
1
1
  const nativeJq = require('bindings')('jq-node-bindings')
2
2
 
3
- const formatFilter = (filter, options) => {
3
+ const formatFilter = (filter, {enableEnv = false} = {}) => {
4
4
  // Escape single quotes only if they are opening or closing a string
5
5
  let formattedFilter = filter.replace(/(^|\s)'(?!\s|")|(?<!\s|")'(\s|$)/g, '$1"$2');
6
6
  // Conditionally enable access to env
7
- return options.enableEnv ? formattedFilter: `def env: {}; {} as $ENV | ${formattedFilter}`;
7
+ return enableEnv ? formattedFilter : `def env: {}; {} as $ENV | ${formattedFilter}`;
8
8
  }
9
- const exec = (object, filter, options = { enableEnv: false }) => {
9
+
10
+
11
+ class JqExecError extends Error {
12
+ }
13
+
14
+ class JqExecCompileError extends JqExecError {
15
+ }
16
+
17
+ const exec = (object, filter, {enableEnv = false, throwOnError = false} = {}) => {
10
18
  try {
11
- const data = nativeJq.exec(JSON.stringify(object), formatFilter(filter, options))
19
+ const data = nativeJq.exec(JSON.stringify(object), formatFilter(filter, {enableEnv}))
12
20
 
13
21
  return data?.value;
14
22
  } catch (err) {
23
+ if (throwOnError) {
24
+ throw new (err?.message?.startsWith('jq: compile error') ? JqExecCompileError : JqExecError)(err.message);
25
+ }
15
26
  return null
16
27
  }
17
28
  }
18
29
 
19
30
  module.exports = {
20
- exec
31
+ exec,
32
+ JqExecError,
33
+ JqExecCompileError
21
34
  };
package/lib/template.js CHANGED
@@ -51,7 +51,7 @@ const findInsideDoubleBracesIndices = (input) => {
51
51
  return indices;
52
52
  }
53
53
 
54
- const render = (inputJson, template) => {
54
+ const render = (inputJson, template, execOptions = {}) => {
55
55
  if (typeof template !== 'string') {
56
56
  return null;
57
57
  }
@@ -64,12 +64,12 @@ const render = (inputJson, template) => {
64
64
  const firstIndex = indices[0];
65
65
  if (indices.length === 1 && template.trim().startsWith('{{') && template.trim().endsWith('}}')) {
66
66
  // If entire string is a template, evaluate and return the result with the original type
67
- return jq.exec(inputJson, template.slice(firstIndex.start, firstIndex.end));
67
+ return jq.exec(inputJson, template.slice(firstIndex.start, firstIndex.end), execOptions);
68
68
  }
69
69
 
70
70
  let result = template.slice(0, firstIndex.start - '{{'.length); // Initiate result with string until first template start index
71
71
  indices.forEach((index, i) => {
72
- const jqResult = jq.exec(inputJson, template.slice(index.start, index.end));
72
+ const jqResult = jq.exec(inputJson, template.slice(index.start, index.end), execOptions);
73
73
  result +=
74
74
  // Add to the result the stringified evaluated jq of the current template
75
75
  (typeof jqResult === 'string' ? jqResult : JSON.stringify(jqResult)) +
@@ -83,24 +83,24 @@ const render = (inputJson, template) => {
83
83
  return result;
84
84
  }
85
85
 
86
- const renderRecursively = (inputJson, template) => {
86
+ const renderRecursively = (inputJson, template, execOptions = {}) => {
87
87
  if (typeof template === 'string') {
88
- return render(inputJson, template);
88
+ return render(inputJson, template, execOptions);
89
89
  }
90
90
  if (Array.isArray(template)) {
91
- return template.map((value) => renderRecursively(inputJson, value));
91
+ return template.map((value) => renderRecursively(inputJson, value, execOptions));
92
92
  }
93
93
  if (typeof template === 'object' && template !== null) {
94
94
  return Object.fromEntries(
95
- Object.entries(template).flatMap(([key, value]) => {
96
- const evaluatedKey = renderRecursively(inputJson, key);
97
- if (!['undefined', 'string'].includes(typeof evaluatedKey) && evaluatedKey !== null) {
98
- throw new Error(
99
- `Evaluated object key should be undefined, null or string. Original key: ${key}, evaluated to: ${JSON.stringify(evaluatedKey)}`,
100
- );
101
- }
102
- return evaluatedKey ? [[evaluatedKey, renderRecursively(inputJson, value)]] : [];
103
- }),
95
+ Object.entries(template).flatMap(([key, value]) => {
96
+ const evaluatedKey = renderRecursively(inputJson, key, execOptions);
97
+ if (!['undefined', 'string'].includes(typeof evaluatedKey) && evaluatedKey !== null) {
98
+ throw new Error(
99
+ `Evaluated object key should be undefined, null or string. Original key: ${key}, evaluated to: ${JSON.stringify(evaluatedKey)}`,
100
+ );
101
+ }
102
+ return evaluatedKey ? [[evaluatedKey, renderRecursively(inputJson, value, execOptions)]] : [];
103
+ }),
104
104
  );
105
105
  }
106
106
 
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@port-labs/jq-node-bindings",
3
- "version": "v0.0.10",
3
+ "version": "v0.0.12",
4
4
  "description": "Node.js bindings for JQ",
5
- "jq-node-bindings": "0.0.10",
5
+ "jq-node-bindings": "0.0.12",
6
6
  "main": "lib/index.js",
7
7
  "scripts": {
8
8
  "configure": "node-gyp configure",
package/src/binding.cc CHANGED
@@ -56,6 +56,20 @@ void jv_object_to_v8(std::string key, jv actual, v8::Local<v8::Object> ret) {
56
56
  v8::Local<v8::Value> v8_val;
57
57
 
58
58
  switch (k) {
59
+ case JV_KIND_INVALID: {
60
+ jv msg = jv_invalid_get_msg(jv_copy(actual));
61
+ char err[4096];
62
+ if (jv_get_kind(msg) == JV_KIND_STRING) {
63
+ snprintf(err, sizeof(err), "jq: error: %s", jv_string_value(msg));
64
+ jv_free(msg);
65
+ jv_free(actual);
66
+ Nan::ThrowError(err);
67
+ return;
68
+ }
69
+ jv_free(msg);
70
+ jv_free(actual);
71
+ break;
72
+ }
59
73
  case JV_KIND_NULL: {
60
74
  v8_val = Nan::Null();
61
75
  break;
@@ -104,40 +118,55 @@ void jv_object_to_v8(std::string key, jv actual, v8::Local<v8::Object> ret) {
104
118
  Nan::Set(ret, v8_key, v8_val);
105
119
  }
106
120
 
121
+ struct err_data {
122
+ char buf[4096];
123
+ };
124
+
125
+ void throw_err_cb(void *data, jv msg) {
126
+ struct err_data *err_data = (struct err_data *)data;
127
+ if (jv_get_kind(msg) != JV_KIND_STRING)
128
+ msg = jv_dump_string(msg, JV_PRINT_INVALID);
129
+ if (!strncmp(jv_string_value(msg), "jq: error", sizeof("jq: error") - 1))
130
+ snprintf(err_data->buf, sizeof(err_data->buf), "jq: compile error%s", jv_string_value(msg) + strlen("jq: error"));
131
+ if (strchr(err_data->buf, '\n'))
132
+ *(strchr(err_data->buf, '\n')) = '\0';
133
+ jv_free(msg);
134
+ }
135
+
107
136
  void jq_exec(std::string json, std::string filter,const Nan::FunctionCallbackInfo<v8::Value>& info) {
108
137
  jq_state *jq = NULL;
138
+ struct err_data err_msg;
109
139
 
110
140
  if (cache.exist(filter)) {
111
141
  jq = cache.get(filter);
112
142
  } else {
113
143
  jq = jq_init();
144
+ jq_set_error_cb(jq, throw_err_cb, &err_msg);
114
145
  if (!jq_compile(jq, filter.c_str())) {
115
- info.GetReturnValue().Set(Nan::Null());
146
+ Nan::ThrowError(err_msg.buf);
116
147
  return;
117
148
  }
118
149
  cache.put(filter, jq);
119
150
  }
120
151
 
121
- jv input = jv_parse(json.c_str());
122
-
123
- if (!jv_is_valid(input)) {
152
+ if (jq == NULL) {
124
153
  info.GetReturnValue().Set(Nan::Null());
125
154
  return;
126
155
  }
127
156
 
128
- if (jq == NULL) {
157
+ jv input = jv_parse(json.c_str());
158
+
159
+ if (!jv_is_valid(input)) {
129
160
  info.GetReturnValue().Set(Nan::Null());
161
+ jv_free(input);
130
162
  return;
131
163
  }
132
164
 
133
165
  jq_start(jq, input, 0);
134
-
135
- jv actual = jq_next(jq);
136
- jv_kind k = jv_get_kind(actual);
166
+ jv result = jq_next(jq);
137
167
 
138
168
  v8::Local<v8::Object> ret = Nan::New<v8::Object>();
139
-
140
- jv_object_to_v8("value", actual, ret);
169
+ jv_object_to_v8("value", result, ret);
141
170
 
142
171
  info.GetReturnValue().Set(ret);
143
172
  }
@@ -149,14 +178,14 @@ std::string FromV8String(v8::Local<v8::String> val) {
149
178
  }
150
179
 
151
180
  void Exec(const Nan::FunctionCallbackInfo<v8::Value>& info) {
152
- v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
153
-
154
181
  if (info.Length() < 2) {
155
182
  Nan::ThrowTypeError("Wrong number of arguments");
183
+ return;
156
184
  }
157
185
 
158
186
  if (!info[0]->IsString() || !info[1]->IsString()) {
159
187
  Nan::ThrowTypeError("Wrong arguments");
188
+ return;
160
189
  }
161
190
 
162
191
  std::string json = FromV8String(Nan::To<v8::String>(info[0]).ToLocalChecked());
@@ -166,12 +195,10 @@ void Exec(const Nan::FunctionCallbackInfo<v8::Value>& info) {
166
195
  }
167
196
 
168
197
  void Init(v8::Local<v8::Object> exports) {
169
- v8::Local<v8::Context> context = exports->CreationContext();
170
- exports->Set(context,
171
- Nan::New("exec").ToLocalChecked(),
172
- Nan::New<v8::FunctionTemplate>(Exec)
173
- ->GetFunction(context)
174
- .ToLocalChecked());
198
+ v8::Local<v8::Context> context = exports->GetCreationContext().ToLocalChecked();
199
+ (void)exports->Set(context,
200
+ Nan::New("exec").ToLocalChecked(),
201
+ Nan::New<v8::FunctionTemplate>(Exec)->GetFunction(context).ToLocalChecked());
175
202
  }
176
203
 
177
204
  NODE_MODULE(exec, Init)
@@ -3,7 +3,7 @@ const jq = require('../lib');
3
3
  describe('jq', () => {
4
4
  it('should break', () => {
5
5
  const json = { foo2: 'bar' };
6
- const input = '.foo';
6
+ const input = 'foo';
7
7
  const result = jq.exec(json, input);
8
8
 
9
9
  expect(result).toBe(null);
@@ -116,7 +116,7 @@ describe('jq', () => {
116
116
  const input = '.foo';
117
117
  const result = jq.exec(json, input);
118
118
 
119
- expect(result).toBe(undefined);
119
+ expect(result).toBe(null);
120
120
  })
121
121
 
122
122
  it('should excape \'\' to ""', () => {
@@ -157,5 +157,15 @@ describe('jq', () => {
157
157
  expect(jq.exec({}, 'env', {})).toEqual({});
158
158
  expect(jq.exec({}, 'env')).toEqual({});
159
159
  })
160
+
161
+ it('test throw on error', () => {
162
+ expect(() => { jq.exec({}, 'foo', {throwOnError: true}) }).toThrow("jq: compile error: foo/0 is not defined at <top-level>, line 1:");
163
+ expect(() => { jq.exec({}, '1/0', {throwOnError: true}) }).toThrow("jq: compile error: Division by zero? at <top-level>, line 1:");
164
+ expect(() => { jq.exec({}, '{', {throwOnError: true}) }).toThrow("jq: compile error: syntax error, unexpected $end (Unix shell quoting issues?) at <top-level>, line 1:");
165
+ expect(() => { jq.exec({}, '{(0):1}', {throwOnError: true}) }).toThrow("jq: compile error: Cannot use number (0) as object key at <top-level>, line 1:");
166
+ expect(() => { jq.exec({}, 'if true then 1 else 0', {throwOnError: true}) }).toThrow("jq: compile error: Possibly unterminated 'if' statement at <top-level>, line 1:");
167
+ expect(() => { jq.exec({}, 'null | map(.+1)', {throwOnError: true}) }).toThrow("jq: error: Cannot iterate over null (null)");
168
+ expect(() => { jq.exec({foo: "bar"}, '.foo + 1', {throwOnError: true}) }).toThrow("jq: error: string (\"bar\") and number (1) cannot be added");
169
+ })
160
170
  })
161
171
 
@@ -45,7 +45,7 @@ describe('template', () => {
45
45
  const input = '{{.foo}}';
46
46
  const result = jq.renderRecursively(json, input);
47
47
 
48
- expect(result).toBe(undefined);
48
+ expect(result).toBe(null);
49
49
  });
50
50
  it('should excape \'\' to ""', () => {
51
51
  const json = { foo: 'com' };
@@ -149,5 +149,22 @@ describe('template', () => {
149
149
 
150
150
  expect(render('{{"\\"foo\\""}}')).toEqual('"foo"');
151
151
  });
152
+ it('test disable env', () => {
153
+ expect(jq.renderRecursively({}, '{{env}}', {enableEnv: false})).toEqual({});
154
+ expect(jq.renderRecursively({}, '{{env}}', {enableEnv: true})).not.toEqual({});
155
+ expect(jq.renderRecursively({}, '{{env}}', {})).toEqual({});
156
+ expect(jq.renderRecursively({}, '{{env}}')).toEqual({});
157
+ })
158
+ it('test throw on error', () => {
159
+ expect(() => { jq.renderRecursively({}, '{{foo}}', {throwOnError: true}) }).toThrow("jq: compile error: foo/0 is not defined at <top-level>, line 1:");
160
+ expect(() => { jq.renderRecursively({}, '{{1/0}}', {throwOnError: true}) }).toThrow("jq: compile error: Division by zero? at <top-level>, line 1:");
161
+ expect(() => { jq.renderRecursively({}, '{{{}}', {throwOnError: true}) }).toThrow("jq: compile error: syntax error, unexpected $end (Unix shell quoting issues?) at <top-level>, line 1:");
162
+ expect(() => { jq.renderRecursively({}, '{{ {(0):1} }}', {throwOnError: true}) }).toThrow("jq: compile error: Cannot use number (0) as object key at <top-level>, line 1:");
163
+ expect(() => { jq.renderRecursively({}, '{{if true then 1 else 0}}', {throwOnError: true}) }).toThrow("jq: compile error: Possibly unterminated 'if' statement at <top-level>, line 1:");
164
+ expect(() => { jq.renderRecursively({}, '{{null | map(.+1)}}', {throwOnError: true}) }).toThrow("jq: error: Cannot iterate over null (null)");
165
+ expect(() => { jq.renderRecursively({foo: "bar"}, '{{.foo + 1}}', {throwOnError: true}) }).toThrow("jq: error: string (\"bar\") and number (1) cannot be added");
166
+ expect(() => { jq.renderRecursively({}, '{{foo}}/{{bar}}', {throwOnError: true}) }).toThrow("jq: compile error: foo/0 is not defined at <top-level>, line 1:");
167
+ expect(() => { jq.renderRecursively({}, '/{{foo}}/', {throwOnError: true}) }).toThrow("jq: compile error: foo/0 is not defined at <top-level>, line 1:");
168
+ })
152
169
  })
153
170