insomnia-plugin-jsonc 0.1.0 → 0.2.0
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/README.md +61 -7
- package/cover.svg +28 -0
- package/icon.svg +13 -0
- package/main.js +14 -13
- package/package.json +12 -7
- package/strip.js +40 -0
- package/test.js +78 -30
package/README.md
CHANGED
|
@@ -1,16 +1,70 @@
|
|
|
1
1
|
# insomnia-plugin-jsonc
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Write comments and trailing commas in your JSON request bodies. They are stripped before the request is sent.
|
|
4
|
+
|
|
5
|
+
## Example
|
|
6
|
+
|
|
7
|
+
What you write in Insomnia:
|
|
8
|
+
|
|
9
|
+
```jsonc
|
|
10
|
+
{
|
|
11
|
+
"page": 1, // current page
|
|
12
|
+
/* "debug": true, */
|
|
13
|
+
"url": "https://example.com", // `//` inside strings is kept
|
|
14
|
+
"tags": ["a", "b",],
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
What the server receives:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"page": 1,
|
|
23
|
+
|
|
24
|
+
"url": "https://example.com",
|
|
25
|
+
"tags": ["a", "b"]
|
|
26
|
+
}
|
|
27
|
+
```
|
|
4
28
|
|
|
5
29
|
## Installation
|
|
6
30
|
|
|
7
|
-
|
|
8
|
-
|
|
31
|
+
### Plugin Hub
|
|
32
|
+
|
|
33
|
+
Install from the [Insomnia Plugin Hub](https://insomnia.rest/plugins/insomnia-plugin-jsonc).
|
|
34
|
+
|
|
35
|
+
### From Insomnia
|
|
36
|
+
|
|
37
|
+
1. Open Preferences (Ctrl + , | Cmd + ,)
|
|
38
|
+
2. Go to the Plugins tab
|
|
39
|
+
3. Enter `insomnia-plugin-jsonc` and click Install Plugin
|
|
40
|
+
|
|
41
|
+
### Manually
|
|
42
|
+
|
|
43
|
+
Copy this folder into the Insomnia plugins directory:
|
|
44
|
+
|
|
45
|
+
- Windows: `%APPDATA%\Insomnia\plugins\`
|
|
46
|
+
- macOS: `~/Library/Application Support/Insomnia/plugins/`
|
|
47
|
+
- Linux: `~/.config/Insomnia/plugins/`
|
|
48
|
+
|
|
49
|
+
## What is stripped
|
|
50
|
+
|
|
51
|
+
- Line comments: `// ...` up to the end of the line
|
|
52
|
+
- Block comments: `/* ... */`, which can span several lines
|
|
53
|
+
- Trailing commas before `]` or `}`, even with a comment in between
|
|
54
|
+
|
|
55
|
+
Content inside strings is never touched, so `"https://example.com"` or `"a /* b */"` stay as they are.
|
|
56
|
+
|
|
57
|
+
The plugin only applies to JSON bodies: `application/json`, with or without parameters (`application/json; charset=utf-8`), and `+json` types (`application/vnd.api+json`). Other bodies are sent unchanged.
|
|
58
|
+
|
|
59
|
+
## Limitations
|
|
60
|
+
|
|
61
|
+
- Whitespace around a removed comment is kept, so the sent body may contain extra spaces or blank lines.
|
|
62
|
+
- The result is not validated: invalid JSON is sent as is, and the server reports the error.
|
|
9
63
|
|
|
10
|
-
##
|
|
64
|
+
## How it works
|
|
11
65
|
|
|
12
|
-
|
|
66
|
+
A request hook strips comments with a regular expression adapted from [`json-easy-strip`](https://www.npmjs.com/package/json-easy-strip), then removes trailing commas. The body is not parsed and serialized again, so its formatting is preserved.
|
|
13
67
|
|
|
14
|
-
##
|
|
68
|
+
## License
|
|
15
69
|
|
|
16
|
-
|
|
70
|
+
MIT
|
package/cover.svg
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 400" width="1200" height="400">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#4000BF"/>
|
|
5
|
+
<stop offset="1" stop-color="#2A0080"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="1200" height="400" fill="url(#bg)"/>
|
|
9
|
+
<!-- { } logo -->
|
|
10
|
+
<g transform="translate(80 72) scale(0.5)">
|
|
11
|
+
<g fill="none" stroke="#FFFFFF" stroke-width="32" stroke-linecap="round" stroke-linejoin="round">
|
|
12
|
+
<path d="M176 112h-16c-24 0-40 16-40 40v56c0 24-16 48-40 48 24 0 40 24 40 48v56c0 24 16 40 40 40h16"/>
|
|
13
|
+
<path d="M336 112h16c24 0 40 16 40 40v56c0 24 16 48 40 48-24 0-40 24-40 48v56c0 24-16 40-40 40h-16"/>
|
|
14
|
+
</g>
|
|
15
|
+
<g stroke="#B794FF" stroke-width="28" stroke-linecap="round">
|
|
16
|
+
<path d="M196 312l40-112"/>
|
|
17
|
+
<path d="M276 312l40-112"/>
|
|
18
|
+
</g>
|
|
19
|
+
</g>
|
|
20
|
+
<g font-family="Menlo, Consolas, 'DejaVu Sans Mono', monospace">
|
|
21
|
+
<text x="400" y="190" font-size="64" font-weight="bold" fill="#FFFFFF">JSON Comments</text>
|
|
22
|
+
<text x="400" y="250" font-size="30" fill="#D9C7FF">Strips comments from JSON request bodies.</text>
|
|
23
|
+
<!-- before → after -->
|
|
24
|
+
<text x="400" y="318" font-size="24" fill="#FFFFFF">{"a": 1<tspan fill="#B794FF" text-decoration="line-through">, /* note */</tspan>}</text>
|
|
25
|
+
<text x="720" y="318" font-size="24" fill="#B794FF">→</text>
|
|
26
|
+
<text x="770" y="318" font-size="24" fill="#FFFFFF">{"a": 1}</text>
|
|
27
|
+
</g>
|
|
28
|
+
</svg>
|
package/icon.svg
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
|
2
|
+
<rect width="512" height="512" rx="96" fill="#4000BF"/>
|
|
3
|
+
<!-- { } -->
|
|
4
|
+
<g fill="none" stroke="#FFFFFF" stroke-width="32" stroke-linecap="round" stroke-linejoin="round">
|
|
5
|
+
<path d="M176 112h-16c-24 0-40 16-40 40v56c0 24-16 48-40 48 24 0 40 24 40 48v56c0 24 16 40 40 40h16"/>
|
|
6
|
+
<path d="M336 112h16c24 0 40 16 40 40v56c0 24 16 48 40 48-24 0-40 24-40 48v56c0 24-16 40-40 40h-16"/>
|
|
7
|
+
</g>
|
|
8
|
+
<!-- // -->
|
|
9
|
+
<g stroke="#B794FF" stroke-width="28" stroke-linecap="round">
|
|
10
|
+
<path d="M196 312l40-112"/>
|
|
11
|
+
<path d="M276 312l40-112"/>
|
|
12
|
+
</g>
|
|
13
|
+
</svg>
|
package/main.js
CHANGED
|
@@ -1,34 +1,35 @@
|
|
|
1
|
+
const { stripJSONC } = require("./strip");
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* JSON Content-Type
|
|
3
5
|
*/
|
|
4
6
|
const CONTENT_TYPE_JSON = "application/json";
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Taken from [`json-easy-strip`](https://www.npmjs.com/package/json-easy-strip).
|
|
9
|
+
* Check if a MIME type is JSON, ignoring its parameters
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* Matches `application/json` and structured syntax suffixes like `application/vnd.api+json`.
|
|
12
12
|
*
|
|
13
|
-
* @param
|
|
14
|
-
* @returns the
|
|
13
|
+
* @param mimeType the MIME type, possibly with parameters (`application/json; charset=utf-8`)
|
|
14
|
+
* @returns whether the MIME type is JSON
|
|
15
15
|
*/
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const isJSONMimeType = (mimeType) => {
|
|
17
|
+
const type = String(mimeType ?? "").split(";")[0].trim().toLowerCase();
|
|
18
|
+
return type === CONTENT_TYPE_JSON || type.endsWith("+json");
|
|
19
|
+
};
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Hook to update request body and remove comments
|
|
22
|
+
* Hook to update request body and remove comments and trailing commas
|
|
23
23
|
* @param context insomnia request context
|
|
24
24
|
* @link https://docs.insomnia.rest/insomnia/context-object-reference
|
|
25
25
|
*/
|
|
26
26
|
const updateRequestBodyHook = (context) => {
|
|
27
27
|
const body = context.request.getBody();
|
|
28
|
-
|
|
28
|
+
// `text` is undefined on an empty body
|
|
29
|
+
if (isJSONMimeType(body.mimeType) && typeof body.text === "string") {
|
|
29
30
|
context.request.setBody({
|
|
30
31
|
...body,
|
|
31
|
-
text:
|
|
32
|
+
text: stripJSONC(body.text),
|
|
32
33
|
});
|
|
33
34
|
}
|
|
34
35
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "insomnia-plugin-jsonc",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Insomnia plugin to
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Insomnia plugin to strip JSON body comments",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"insomnia": {
|
|
7
7
|
"name": "jsonc",
|
|
8
8
|
"displayName": "JSON Comments Plugin",
|
|
9
|
-
"description": "
|
|
9
|
+
"description": "Strips comments from JSON request bodies.",
|
|
10
|
+
"images": {
|
|
11
|
+
"icon": "icon.svg",
|
|
12
|
+
"cover": "cover.svg"
|
|
13
|
+
},
|
|
10
14
|
"publisher": {
|
|
11
15
|
"name": "Guillaume Ongenae",
|
|
12
16
|
"icon": "https://avatars.githubusercontent.com/u/9453090"
|
|
@@ -14,11 +18,11 @@
|
|
|
14
18
|
"unlisted": false
|
|
15
19
|
},
|
|
16
20
|
"scripts": {
|
|
17
|
-
"test": "node test.js"
|
|
21
|
+
"test": "node --test test.js"
|
|
18
22
|
},
|
|
19
23
|
"repository": {
|
|
20
24
|
"type": "git",
|
|
21
|
-
"url": "git+ssh://git@github.com/
|
|
25
|
+
"url": "git+ssh://git@github.com/g-ongenae/insomnia-plugin-jsonc.git"
|
|
22
26
|
},
|
|
23
27
|
"keywords": [
|
|
24
28
|
"insomnia",
|
|
@@ -33,6 +37,7 @@
|
|
|
33
37
|
"url": "https://github.com/g-ongenae/insomnia-plugin-jsonc/issues"
|
|
34
38
|
},
|
|
35
39
|
"homepage": "https://github.com/g-ongenae/insomnia-plugin-jsonc#readme",
|
|
36
|
-
"
|
|
37
|
-
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"fast-check": "^4.10.2"
|
|
42
|
+
}
|
|
38
43
|
}
|
package/strip.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip comments from a JSON string
|
|
3
|
+
*
|
|
4
|
+
* Adapted from [`json-easy-strip`](https://www.npmjs.com/package/json-easy-strip),
|
|
5
|
+
* with strings matched as a whole (any escaped character included) so that
|
|
6
|
+
* an escaped backslash before a closing quote is handled.
|
|
7
|
+
*
|
|
8
|
+
* Extracted to not parse and stringify again the JSON string
|
|
9
|
+
*
|
|
10
|
+
* @param data the JSON string with comments
|
|
11
|
+
* @returns the JSON string without comments
|
|
12
|
+
*/
|
|
13
|
+
const stripJSONComments = (data) =>
|
|
14
|
+
data.replace(/"(?:\\.|[^"\\])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) =>
|
|
15
|
+
g ? "" : m
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Strip trailing commas before a closing `]` or `}` from a JSON string
|
|
20
|
+
*
|
|
21
|
+
* Must run after `stripJSONComments`, so that a comment between the comma
|
|
22
|
+
* and the closing bracket does not hide it.
|
|
23
|
+
*
|
|
24
|
+
* @param data the JSON string without comments
|
|
25
|
+
* @returns the JSON string without trailing commas
|
|
26
|
+
*/
|
|
27
|
+
const stripTrailingCommas = (data) =>
|
|
28
|
+
data.replace(/"(?:\\.|[^"\\])*"|,(\s*[\]}])/g, (m, g) =>
|
|
29
|
+
g === undefined ? m : g
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Strip comments and trailing commas from a JSONC string
|
|
34
|
+
*
|
|
35
|
+
* @param data the JSONC string
|
|
36
|
+
* @returns the JSON string
|
|
37
|
+
*/
|
|
38
|
+
const stripJSONC = (data) => stripTrailingCommas(stripJSONComments(data));
|
|
39
|
+
|
|
40
|
+
module.exports = { stripJSONComments, stripTrailingCommas, stripJSONC };
|
package/test.js
CHANGED
|
@@ -1,44 +1,92 @@
|
|
|
1
|
+
const { test } = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const fc = require('fast-check');
|
|
1
4
|
const { requestHooks } = require('./main');
|
|
5
|
+
const { stripJSONC } = require('./strip');
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
|
-
*
|
|
5
|
-
* @param {object} givenBody
|
|
6
|
-
* @
|
|
7
|
-
* @returns a fake insomnia context with a validation
|
|
8
|
+
* Run the hook on a fake insomnia context
|
|
9
|
+
* @param {object} givenBody the body returned by getBody
|
|
10
|
+
* @returns the list of bodies passed to setBody
|
|
8
11
|
*/
|
|
9
|
-
const
|
|
10
|
-
|
|
12
|
+
const runHook = (givenBody) => {
|
|
13
|
+
const calls = [];
|
|
14
|
+
requestHooks[0]({
|
|
11
15
|
request: {
|
|
12
16
|
getBody: () => givenBody,
|
|
13
|
-
setBody: (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
console.log(result);
|
|
19
|
-
},
|
|
20
|
-
}
|
|
21
|
-
};
|
|
17
|
+
setBody: (body) => calls.push(body),
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
return calls;
|
|
22
21
|
};
|
|
23
22
|
|
|
24
23
|
/**
|
|
25
|
-
* List of test cases
|
|
24
|
+
* List of test cases: [name, given body, expected body]
|
|
26
25
|
*/
|
|
27
26
|
const cases = [
|
|
28
|
-
|
|
29
|
-
[{ mimeType: '
|
|
30
|
-
//
|
|
31
|
-
[{ mimeType: 'application/json', text: 'foo' }, { mimeType: 'application/json', text: 'foo' }],
|
|
32
|
-
[{ mimeType: 'application/json', text: '{"foo": "bar"}' }, { mimeType: 'application/json', text: '{"foo": "bar"}' }],
|
|
33
|
-
//
|
|
34
|
-
[{ mimeType: 'application/json', text: '{"foo":
|
|
35
|
-
[{ mimeType: 'application/json', text: '
|
|
36
|
-
[{ mimeType: 'application/json', text: '
|
|
27
|
+
['should not alter invalid JSON body without comments', { mimeType: 'application/json', text: 'foo' }, { mimeType: 'application/json', text: 'foo' }],
|
|
28
|
+
['should not alter JSON body without comments', { mimeType: 'application/json', text: '{"foo": "bar"}' }, { mimeType: 'application/json', text: '{"foo": "bar"}' }],
|
|
29
|
+
// Spaces around comments are preserved
|
|
30
|
+
['should remove inline block comment', { mimeType: 'application/json', text: '{"foo": /* hello */ "bar"}' }, { mimeType: 'application/json', text: '{"foo": "bar"}' }],
|
|
31
|
+
['should remove leading block comment', { mimeType: 'application/json', text: '/* hello */ {"foo": "bar"}' }, { mimeType: 'application/json', text: ' {"foo": "bar"}' }],
|
|
32
|
+
['should remove line comment after the last item', { mimeType: 'application/json', text: '{"a": 1 // last\n}' }, { mimeType: 'application/json', text: '{"a": 1 \n}' }],
|
|
33
|
+
['should remove line comment', { mimeType: 'application/json', text: '{"foo": "bar" // hello\n }' }, { mimeType: 'application/json', text: '{"foo": "bar" \n }' }],
|
|
34
|
+
['should remove trailing comma in object', { mimeType: 'application/json', text: '{"a": 1, }' }, { mimeType: 'application/json', text: '{"a": 1 }' }],
|
|
35
|
+
['should remove trailing comma in array', { mimeType: 'application/json', text: '[1,\n]' }, { mimeType: 'application/json', text: '[1\n]' }],
|
|
36
|
+
['should remove trailing comma followed by a comment', { mimeType: 'application/json', text: '{"a": 1, // last\n}' }, { mimeType: 'application/json', text: '{"a": 1 \n}' }],
|
|
37
|
+
['should remove trailing comma followed by a block comment', { mimeType: 'application/json', text: '[1, /* last */]' }, { mimeType: 'application/json', text: '[1 ]' }],
|
|
38
|
+
['should not remove comma-like content in strings', { mimeType: 'application/json', text: '{"a": ",}", "b": ",]"}' }, { mimeType: 'application/json', text: '{"a": ",}", "b": ",]"}' }],
|
|
39
|
+
['should handle JSON MIME type with parameters', { mimeType: 'application/json; charset=utf-8', text: '{"a": 1 /* b */}' }, { mimeType: 'application/json; charset=utf-8', text: '{"a": 1 }' }],
|
|
40
|
+
['should handle JSON MIME type case-insensitively', { mimeType: 'Application/JSON', text: '{"a": 1 /* b */}' }, { mimeType: 'Application/JSON', text: '{"a": 1 }' }],
|
|
41
|
+
['should handle +json MIME types', { mimeType: 'application/vnd.api+json', text: '{"a": 1 /* b */}' }, { mimeType: 'application/vnd.api+json', text: '{"a": 1 }' }],
|
|
37
42
|
];
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
test('should not call setBody on non JSON body', () => {
|
|
45
|
+
assert.deepEqual(runHook({ mimeType: 'non-json', text: 'foo // bar' }), []);
|
|
46
|
+
assert.deepEqual(runHook({ mimeType: 'application/jsonp', text: 'foo // bar' }), []);
|
|
47
|
+
assert.deepEqual(runHook({ text: 'foo // bar' }), []);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('should not throw nor call setBody on empty JSON body', () => {
|
|
51
|
+
assert.deepEqual(runHook({ mimeType: 'application/json' }), []);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
for (const [name, givenBody, expectedBody] of cases) {
|
|
55
|
+
test(name, () => {
|
|
56
|
+
const calls = runHook(givenBody);
|
|
57
|
+
assert.equal(calls.length, 1);
|
|
58
|
+
assert.equal(calls[0].text, expectedBody.text);
|
|
59
|
+
});
|
|
44
60
|
}
|
|
61
|
+
|
|
62
|
+
test('should keep comment-like content in a string after an escaped backslash', () => {
|
|
63
|
+
const text = String.raw`{"a": "x\\", "b": "// y"}`;
|
|
64
|
+
const [body] = runHook({ mimeType: 'application/json', text });
|
|
65
|
+
assert.equal(body.text, text);
|
|
66
|
+
assert.deepEqual(JSON.parse(body.text), { a: 'x\\', b: '// y' });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('should strip comments and trailing commas inserted in any JSON document', () => {
|
|
70
|
+
const comment = fc.string({ unit: 'grapheme-ascii' }).filter((c) => !c.includes('*/'));
|
|
71
|
+
const lineComments = fc.array(fc.record({ block: fc.option(comment), line: fc.option(comment), trailingComma: fc.boolean() }));
|
|
72
|
+
|
|
73
|
+
fc.assert(
|
|
74
|
+
fc.property(fc.jsonValue(), lineComments, (value, comments) => {
|
|
75
|
+
// JSON.stringify escapes newlines in strings, so each line starts and ends on a token boundary
|
|
76
|
+
const json = JSON.stringify(value, null, 2);
|
|
77
|
+
const text = json
|
|
78
|
+
.split('\n')
|
|
79
|
+
.map((line, i, lines) => {
|
|
80
|
+
const { block = null, line: lineComment = null, trailingComma = false } = comments[i] ?? {};
|
|
81
|
+
// A trailing comma is allowed after the last value of a non-empty array or object
|
|
82
|
+
const isLastValue = /^\s*[\]}]/.test(lines[i + 1] ?? '') && !/[[{]$/.test(line);
|
|
83
|
+
const comma = trailingComma && isLastValue ? ',' : '';
|
|
84
|
+
return `${block === null ? '' : `/*${block}*/`}${line}${comma}${lineComment === null ? '' : `//${lineComment}`}`;
|
|
85
|
+
})
|
|
86
|
+
.join('\n');
|
|
87
|
+
|
|
88
|
+
// Compare with the parsed original rather than `value`, since -0 does not survive JSON
|
|
89
|
+
assert.deepEqual(JSON.parse(stripJSONC(text)), JSON.parse(json));
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
});
|