@port-labs/jq-node-bindings 0.0.11 → 0.0.13

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,6 +1,12 @@
1
1
  declare module '@port-labs/jq-node-bindings' {
2
2
  type ExecOptions = { enableEnv?: boolean, throwOnError?: boolean };
3
3
 
4
+ export class JqExecError extends Error {
5
+ }
6
+
7
+ export class JqExecCompileError extends Error {
8
+ }
9
+
4
10
  export function exec(json: object, input: string, options?: ExecOptions): object | Array<any> | string | number | boolean | null;
5
11
 
6
12
  export function renderRecursively(json: object, input: object | Array<any> | string | number | boolean | null, execOptions?: ExecOptions): object | Array<any> | string | number | boolean | null;
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
@@ -6,6 +6,14 @@ const formatFilter = (filter, {enableEnv = false} = {}) => {
6
6
  // Conditionally enable access to env
7
7
  return enableEnv ? formattedFilter : `def env: {}; {} as $ENV | ${formattedFilter}`;
8
8
  }
9
+
10
+
11
+ class JqExecError extends Error {
12
+ }
13
+
14
+ class JqExecCompileError extends JqExecError {
15
+ }
16
+
9
17
  const exec = (object, filter, {enableEnv = false, throwOnError = false} = {}) => {
10
18
  try {
11
19
  const data = nativeJq.exec(JSON.stringify(object), formatFilter(filter, {enableEnv}))
@@ -13,12 +21,14 @@ const exec = (object, filter, {enableEnv = false, throwOnError = false} = {}) =>
13
21
  return data?.value;
14
22
  } catch (err) {
15
23
  if (throwOnError) {
16
- throw err;
24
+ throw new (err?.message?.startsWith('jq: compile error') ? JqExecCompileError : JqExecError)(err.message);
17
25
  }
18
26
  return null
19
27
  }
20
28
  }
21
29
 
22
30
  module.exports = {
23
- exec
31
+ exec,
32
+ JqExecError,
33
+ JqExecCompileError
24
34
  };
package/lib/template.js CHANGED
@@ -93,6 +93,19 @@ const renderRecursively = (inputJson, template, execOptions = {}) => {
93
93
  if (typeof template === 'object' && template !== null) {
94
94
  return Object.fromEntries(
95
95
  Object.entries(template).flatMap(([key, value]) => {
96
+ const SPREAD_KEYWORD = "spreadValue";
97
+ const keywordMatcher = `^\\{\\{\\s*${SPREAD_KEYWORD}\\(\\s*\\)\\s*\\}\\}$`; // matches {{ <Keyword>() }} with white spaces where you'd expect them
98
+
99
+ if (key.trim().match(keywordMatcher)) {
100
+ const evaluatedValue = renderRecursively(inputJson, value, execOptions);
101
+ if (typeof evaluatedValue !== "object") {
102
+ throw new Error(
103
+ `Evaluated value should be an object if the key is ${key}. Original value: ${value}, evaluated to: ${JSON.stringify(evaluatedValue)}`
104
+ );
105
+ }
106
+ return Object.entries(evaluatedValue);
107
+ }
108
+
96
109
  const evaluatedKey = renderRecursively(inputJson, key, execOptions);
97
110
  if (!['undefined', 'string'].includes(typeof evaluatedKey) && evaluatedKey !== null) {
98
111
  throw new Error(
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@port-labs/jq-node-bindings",
3
- "version": "v0.0.11",
3
+ "version": "v0.0.13",
4
4
  "description": "Node.js bindings for JQ",
5
- "jq-node-bindings": "0.0.11",
5
+ "jq-node-bindings": "0.0.13",
6
6
  "main": "lib/index.js",
7
7
  "scripts": {
8
8
  "configure": "node-gyp configure",
package/src/binding.cc CHANGED
@@ -63,7 +63,7 @@ void jv_object_to_v8(std::string key, jv actual, v8::Local<v8::Object> ret) {
63
63
  snprintf(err, sizeof(err), "jq: error: %s", jv_string_value(msg));
64
64
  jv_free(msg);
65
65
  jv_free(actual);
66
- Nan::ThrowTypeError(err);
66
+ Nan::ThrowError(err);
67
67
  return;
68
68
  }
69
69
  jv_free(msg);
@@ -127,7 +127,7 @@ void throw_err_cb(void *data, jv msg) {
127
127
  if (jv_get_kind(msg) != JV_KIND_STRING)
128
128
  msg = jv_dump_string(msg, JV_PRINT_INVALID);
129
129
  if (!strncmp(jv_string_value(msg), "jq: error", sizeof("jq: error") - 1))
130
- snprintf(err_data->buf, sizeof(err_data->buf), "%s", jv_string_value(msg));
130
+ snprintf(err_data->buf, sizeof(err_data->buf), "jq: compile error%s", jv_string_value(msg) + strlen("jq: error"));
131
131
  if (strchr(err_data->buf, '\n'))
132
132
  *(strchr(err_data->buf, '\n')) = '\0';
133
133
  jv_free(msg);
@@ -143,7 +143,7 @@ void jq_exec(std::string json, std::string filter,const Nan::FunctionCallbackInf
143
143
  jq = jq_init();
144
144
  jq_set_error_cb(jq, throw_err_cb, &err_msg);
145
145
  if (!jq_compile(jq, filter.c_str())) {
146
- Nan::ThrowTypeError(err_msg.buf);
146
+ Nan::ThrowError(err_msg.buf);
147
147
  return;
148
148
  }
149
149
  cache.put(filter, jq);
@@ -159,11 +159,11 @@ describe('jq', () => {
159
159
  })
160
160
 
161
161
  it('test throw on error', () => {
162
- expect(() => { jq.exec({}, 'foo', {throwOnError: true}) }).toThrow("jq: error: foo/0 is not defined at <top-level>, line 1:");
163
- expect(() => { jq.exec({}, '1/0', {throwOnError: true}) }).toThrow("jq: error: Division by zero? at <top-level>, line 1:");
164
- expect(() => { jq.exec({}, '{', {throwOnError: true}) }).toThrow("jq: error: syntax error, unexpected $end (Unix shell quoting issues?) at <top-level>, line 1:");
165
- expect(() => { jq.exec({}, '{(0):1}', {throwOnError: true}) }).toThrow("jq: 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: error: Possibly unterminated 'if' statement at <top-level>, line 1:");
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
167
  expect(() => { jq.exec({}, 'null | map(.+1)', {throwOnError: true}) }).toThrow("jq: error: Cannot iterate over null (null)");
168
168
  expect(() => { jq.exec({foo: "bar"}, '.foo + 1', {throwOnError: true}) }).toThrow("jq: error: string (\"bar\") and number (1) cannot be added");
169
169
  })
@@ -125,6 +125,14 @@ describe('template', () => {
125
125
  expect(render({'{{""}}': 'bar'})).toEqual({});
126
126
  expect(render({'{{\'\'}}': 'bar'})).toEqual({});
127
127
  });
128
+ it('testing spread key', () => {
129
+ const json = { foo: "bar" };
130
+ const render = (input) => jq.renderRecursively(json, input);
131
+
132
+ expect(render({ "{{spreadValue()}}": { foo: "bar" } })).toEqual({foo: "bar"});
133
+ expect(render({ " {{ spreadValue( ) }} ": { foo: "bar" } })).toEqual({foo: "bar"});
134
+ expect(render({ "{{spreadValue()}}": "{{ . }}" })).toEqual({ foo: "bar" });
135
+ });
128
136
  it('recursive templates should work', () => {
129
137
  const json = { foo: 'bar', bar: 'foo' };
130
138
  const render = (input) => jq.renderRecursively(json, input);
@@ -156,15 +164,19 @@ describe('template', () => {
156
164
  expect(jq.renderRecursively({}, '{{env}}')).toEqual({});
157
165
  })
158
166
  it('test throw on error', () => {
159
- expect(() => { jq.renderRecursively({}, '{{foo}}', {throwOnError: true}) }).toThrow("jq: error: foo/0 is not defined at <top-level>, line 1:");
160
- expect(() => { jq.renderRecursively({}, '{{1/0}}', {throwOnError: true}) }).toThrow("jq: error: Division by zero? at <top-level>, line 1:");
161
- expect(() => { jq.renderRecursively({}, '{{{}}', {throwOnError: true}) }).toThrow("jq: error: syntax error, unexpected $end (Unix shell quoting issues?) at <top-level>, line 1:");
162
- expect(() => { jq.renderRecursively({}, '{{ {(0):1} }}', {throwOnError: true}) }).toThrow("jq: 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: error: Possibly unterminated 'if' statement 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
+ expect(() => { jq.renderRecursively({}, '{{1/0}}', {throwOnError: true}) }).toThrow("jq: compile error: Division by zero? at <top-level>, line 1:");
169
+ expect(() => { jq.renderRecursively({}, '{{{}}', {throwOnError: true}) }).toThrow("jq: compile error: syntax error, unexpected $end (Unix shell quoting issues?) at <top-level>, line 1:");
170
+ expect(() => { jq.renderRecursively({}, '{{ {(0):1} }}', {throwOnError: true}) }).toThrow("jq: compile error: Cannot use number (0) as object key at <top-level>, line 1:");
171
+ 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
172
  expect(() => { jq.renderRecursively({}, '{{null | map(.+1)}}', {throwOnError: true}) }).toThrow("jq: error: Cannot iterate over null (null)");
165
173
  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: error: foo/0 is not defined at <top-level>, line 1:");
167
- expect(() => { jq.renderRecursively({}, '/{{foo}}/', {throwOnError: true}) }).toThrow("jq: error: foo/0 is not defined at <top-level>, line 1:");
174
+ expect(() => { jq.renderRecursively({}, '{{foo}}/{{bar}}', {throwOnError: true}) }).toThrow("jq: compile error: foo/0 is not defined at <top-level>, line 1:");
175
+ expect(() => { jq.renderRecursively({}, '/{{foo}}/', {throwOnError: true}) }).toThrow("jq: compile error: foo/0 is not defined at <top-level>, line 1:");
176
+ expect(() => { jq.renderRecursively({}, { "{{ spreadValue() }}": "str" }, { throwOnError: true }) })
177
+ .toThrow('Evaluated value should be an object if the key is {{ spreadValue() }}. Original value: str, evaluated to: "str"');
178
+ expect(() => { jq.renderRecursively({}, { "{{ spreadValue() }}": "{{ \"str\" }}" }, { throwOnError: true }) })
179
+ .toThrow('Evaluated value should be an object if the key is {{ spreadValue() }}. Original value: {{ \"str\" }}, evaluated to: "str"');
168
180
  })
169
181
  })
170
182