Skip to content

Commit f84323f

Browse files
authored
feat(582): Request URL as Object (#594)
* #582 - make request URL an object * #582 - fix broken monaco version * #582 - persist URL as object * #582 - test query parsing in import service * #582 - rename to TrufosQueryParam * #582 - remove comment * #582 - fix setting draft flag to false after save * #582 - fix draft flag when working on headers or queries * #592 - support query params without value * #592 - support writing trailing "?"
1 parent cd37b99 commit f84323f

File tree

26 files changed

+594
-349
lines changed

26 files changed

+594
-349
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
"immer": "^10.1.3",
9191
"lucide-react": "^0.546.0",
9292
"mime-types": "^3.0.1",
93-
"monaco-editor": "^0.54.0",
93+
"monaco-editor": "^0.53.0",
9494
"next-themes": "^0.4.6",
9595
"openid-client": "^6.8.1",
9696
"postman-collection": "^5.2.0",

src/main/import/service/import-service.test.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import path from 'node:path';
88
import { vi, describe, it, expect } from 'vitest';
99

1010
const POSTMAN_COLLECTION =
11-
'{"info":{"name":"HTTP Status Messages","id":"my-collection-id","_postman_schema":"https://schema.getpostman.com/#2.0.0","version":{"major":"2","minor":"0","patch":"0","prerelease":"draft.1"}},"variable":[{"id":"var-1","type":"string","value":"hello-world"},{"id":"var-2","type":"number","value":"123"},{"id":"var-3","type":"boolean","value":"true"},{"id":"var-4","type":"any","value":"hello-world-any"},{"id":"var-5","value":null}],"event":[{"listen":"test","id":"my-global-script-1","script":{"type":"text/javascript","exec":["const package1 = pm.require(\\"package1\\");","console.log(\\"hello\\", package1);"],"packages":{"package1":{"id":"script-package-1"}}}},{"listen":"prerequest","script":{"type":"text/javascript","exec":["const package1 = pm.require(\\"package1\\");","console.log(\\"hello\\", package1);"],"packages":{"package1":{"id":"script-package-1"}}}}],"item":[{"id":"request-200","description":{"content":"<h1>This is H1</h1> <i>italic</i> <script>this will be dropped in toString()</script>","version":"2.0.1-abc+efg"},"name":"200 ok","request":"http://echo.getpostman.com/status/200","response":[{"name":"a sample response","originalRequest":"http://echo.getpostman.com/status/200","status":"200 OK","code":200,"header":"Content-Type: application/json\\r\\nAuthorization: Hawk id=\\"dh37fgj492je\\", ts=\\"1448549987\\", nonce=\\"eOJZCd\\", mac=\\"O2TFlvAlMvKVSKOzc6XkfU6+5285k5p3m5dAjxumo2k=\\"\\r\\n","cookie":[{"domain":".httpbin.org","expires":1502442248,"hostOnly":false,"httpOnly":false,"key":"_ga","path":"/","secure":false,"session":false,"_postman_storeId":"0","value":"GA1.2.113558537.1435817423"}],"body":"response body"}],"event":[{"listen":"test","script":"my-global-script-1"},{"listen":"test","script":{"type":"text/javascript","exec":"console.log(\\"hello\\");"}}],"proxy":{"match":"https://*.getpostman.com/*","server":"https://proxy.com"},"protocolProfileBehavior":{"disableBodyPruning":true}},{"id":"request-200-post","description":{"content":"<h1>This is H1</h1> <i>italic</i> <script>this will be dropped in toString()</script>","version":"2.0.1-abc+efg"},"name":"200 ok","request":{"description":{"content":"my description","type":"text/markdown"},"method":"POST","url":"http://echo.getpostman.com/post","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"urlencoded","urlencoded":[{"key":"yo","value":"mate"}]}},"response":[{"name":"a sample response","originalRequest":"http://echo.getpostman.com/post","status":"200 OK","code":200,"header":"Content-Type: application/json\\r\\nAuthorization: Hawk id=\\"dh37fgj492je\\", ts=\\"1448549987\\", nonce=\\"eOJZCd\\", mac=\\"O2TFlvAlMvKVSKOzc6XkfU6+5285k5p3m5dAjxumo2k=\\"\\r\\n","cookie":[{"domain":".httpbin.org","expires":1502442248,"hostOnly":false,"httpOnly":false,"key":"_ga","path":"/","secure":false,"session":false,"_postman_storeId":"0","value":"GA1.2.113558537.1435817423"}],"body":"response body"}],"event":[{"listen":"test","script":"my-global-script-1"},{"listen":"test","script":{"type":"text/javascript","exec":"console.log(\\"hello\\");"}}]},{"name":"This is a folder","id":"my-folder-1","_my_meta":"hello","item":[{"id":"request-200","name":"201","request":{"url":"http://echo.getpostman.com/status/201","method":"PUT","body":{"mode":"urlencoded","urlencoded":[{"key":"yo","value":"mate"}]},"header":"Content-Type: application/json\\nAuthorization: Hawk id=\\"dh37fgj492je\\", ts=\\"1448549987\\", nonce=\\"eOJZCd\\", mac=\\"O2TFlvAlMvKVSKOzc6XkfU6+5285k5p3m5dAjxumo2k=\\"\\n"}},{"id":"request-post","name":"201","request":{"url":"http://echo.getpostman.com/post","body":{"mode":"raw","raw":"blahblah"},"auth":{"type":"basic","basic":{"username":"yosam","password":"asdhjajsd"}}}},{"id":"request-400","name":"400 bad request","request":"http://shamasis:[email protected]:9443/status/400/?query=string&a=b&abcd#{{search}}"},{"id":"This is a sub folder","name":"my-folder-2","item":[{"id":"blank-folder","name":"This is a blank","item":[{"id":"request-gg","name":"gg not found","request":{"description":{"content":"Some stuff I want to say about this request. It\'s in *markdown* too.","type":"text/markdown","version":"1.2.3+hi"},"url":{"description":"This is a nice URL.","protocol":"https","port":"8443","path":"path/to/document","host":"sub.example.com."},"header":[{"key":"Host","value":"sub.example.com"},{"key":"Content-Type","value":"application/json"}]},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["postman.setEnvironmentVariable(\\"username\\", \\"a85\\");","postman.setEnvironmentVariable(\\"repository\\", \\"Newman\\")"]}},{"listen":"prerequest","script":{"type":"text/javascript","exec":"console.log(\\"hello\\");\\r\\nconsole.log(\'hi\')"}}]}]},{"id":"solo-folder","name":"Solo Folder","item":[{"id":"request-404","name":"404 not found","request":{"description":{"content":"Some stuff I want to say about this request. It\'s in *markdown* too.","type":"text/markdown","version":"1.2.3+hi"},"url":{"description":"This is a nice URL.","protocol":"https","port":"8443","path":"path/to/document","host":"sub.example.com."},"auth":{"type":"hawk","basic":{"username":"yosam","password":"asdhjajsd"},"hawk":{"authKey":"asjehcgfdjyrggucgn"}},"header":[{"key":"Access-Control-Allow-Credentials","value":"true","description":"Setting this header to \'true\' means that the server allows cookies (or other user credentials) to be included on cross-origin requests."},{"key":"Server","value":"nginx","description":"Server Name"}]},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["postman.setEnvironmentVariable(\\"username\\", \\"a85\\");","postman.setEnvironmentVariable(\\"repository\\", \\"Newman\\")"]}},{"listen":"prerequest","script":{"type":"text/javascript","exec":"console.log(\\"hello\\");\\r\\nconsole.log(\'hi\')"}}]}]}]}]}],"protocolProfileBehavior":{"disableBodyPruning":false}}';
11+
'{"info":{"name":"HTTP Status Messages","id":"my-collection-id","_postman_schema":"https://schema.getpostman.com/#2.0.0","version":{"major":"2","minor":"0","patch":"0","prerelease":"draft.1"}},"variable":[{"id":"var-1","type":"string","value":"hello-world"},{"id":"var-2","type":"number","value":"123"},{"id":"var-3","type":"boolean","value":"true"},{"id":"var-4","type":"any","value":"hello-world-any"},{"id":"var-5","value":null}],"event":[{"listen":"test","id":"my-global-script-1","script":{"type":"text/javascript","exec":["const package1 = pm.require(\\"package1\\");","console.log(\\"hello\\", package1);"],"packages":{"package1":{"id":"script-package-1"}}}},{"listen":"prerequest","script":{"type":"text/javascript","exec":["const package1 = pm.require(\\"package1\\");","console.log(\\"hello\\", package1);"],"packages":{"package1":{"id":"script-package-1"}}}}],"item":[{"id":"request-200","description":{"content":"<h1>This is H1</h1> <i>italic</i> <script>this will be dropped in toString()</script>","version":"2.0.1-abc+efg"},"name":"200 ok","request":"http://echo.getpostman.com/status/200?key=value","response":[{"name":"a sample response","originalRequest":"http://echo.getpostman.com/status/200","status":"200 OK","code":200,"header":"Content-Type: application/json\\r\\nAuthorization: Hawk id=\\"dh37fgj492je\\", ts=\\"1448549987\\", nonce=\\"eOJZCd\\", mac=\\"O2TFlvAlMvKVSKOzc6XkfU6+5285k5p3m5dAjxumo2k=\\"\\r\\n","cookie":[{"domain":".httpbin.org","expires":1502442248,"hostOnly":false,"httpOnly":false,"key":"_ga","path":"/","secure":false,"session":false,"_postman_storeId":"0","value":"GA1.2.113558537.1435817423"}],"body":"response body"}],"event":[{"listen":"test","script":"my-global-script-1"},{"listen":"test","script":{"type":"text/javascript","exec":"console.log(\\"hello\\");"}}],"proxy":{"match":"https://*.getpostman.com/*","server":"https://proxy.com"},"protocolProfileBehavior":{"disableBodyPruning":true}},{"id":"request-200-post","description":{"content":"<h1>This is H1</h1> <i>italic</i> <script>this will be dropped in toString()</script>","version":"2.0.1-abc+efg"},"name":"200 ok","request":{"description":{"content":"my description","type":"text/markdown"},"method":"POST","url":"http://echo.getpostman.com/post","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"urlencoded","urlencoded":[{"key":"yo","value":"mate"}]}},"response":[{"name":"a sample response","originalRequest":"http://echo.getpostman.com/post","status":"200 OK","code":200,"header":"Content-Type: application/json\\r\\nAuthorization: Hawk id=\\"dh37fgj492je\\", ts=\\"1448549987\\", nonce=\\"eOJZCd\\", mac=\\"O2TFlvAlMvKVSKOzc6XkfU6+5285k5p3m5dAjxumo2k=\\"\\r\\n","cookie":[{"domain":".httpbin.org","expires":1502442248,"hostOnly":false,"httpOnly":false,"key":"_ga","path":"/","secure":false,"session":false,"_postman_storeId":"0","value":"GA1.2.113558537.1435817423"}],"body":"response body"}],"event":[{"listen":"test","script":"my-global-script-1"},{"listen":"test","script":{"type":"text/javascript","exec":"console.log(\\"hello\\");"}}]},{"name":"This is a folder","id":"my-folder-1","_my_meta":"hello","item":[{"id":"request-200","name":"201","request":{"url":"http://echo.getpostman.com/status/201","method":"PUT","body":{"mode":"urlencoded","urlencoded":[{"key":"yo","value":"mate"}]},"header":"Content-Type: application/json\\nAuthorization: Hawk id=\\"dh37fgj492je\\", ts=\\"1448549987\\", nonce=\\"eOJZCd\\", mac=\\"O2TFlvAlMvKVSKOzc6XkfU6+5285k5p3m5dAjxumo2k=\\"\\n"}},{"id":"request-post","name":"201","request":{"url":"http://echo.getpostman.com/post","body":{"mode":"raw","raw":"blahblah"},"auth":{"type":"basic","basic":{"username":"yosam","password":"asdhjajsd"}}}},{"id":"request-400","name":"400 bad request","request":"http://shamasis:[email protected]:9443/status/400/?query=string&a=b&abcd#{{search}}"},{"id":"This is a sub folder","name":"my-folder-2","item":[{"id":"blank-folder","name":"This is a blank","item":[{"id":"request-gg","name":"gg not found","request":{"description":{"content":"Some stuff I want to say about this request. It\'s in *markdown* too.","type":"text/markdown","version":"1.2.3+hi"},"url":{"description":"This is a nice URL.","protocol":"https","port":"8443","path":"path/to/document","host":"sub.example.com."},"header":[{"key":"Host","value":"sub.example.com"},{"key":"Content-Type","value":"application/json"}]},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["postman.setEnvironmentVariable(\\"username\\", \\"a85\\");","postman.setEnvironmentVariable(\\"repository\\", \\"Newman\\")"]}},{"listen":"prerequest","script":{"type":"text/javascript","exec":"console.log(\\"hello\\");\\r\\nconsole.log(\'hi\')"}}]}]},{"id":"solo-folder","name":"Solo Folder","item":[{"id":"request-404","name":"404 not found","request":{"description":{"content":"Some stuff I want to say about this request. It\'s in *markdown* too.","type":"text/markdown","version":"1.2.3+hi"},"url":{"description":"This is a nice URL.","protocol":"https","port":"8443","path":"path/to/document","host":"sub.example.com."},"auth":{"type":"hawk","basic":{"username":"yosam","password":"asdhjajsd"},"hawk":{"authKey":"asjehcgfdjyrggucgn"}},"header":[{"key":"Access-Control-Allow-Credentials","value":"true","description":"Setting this header to \'true\' means that the server allows cookies (or other user credentials) to be included on cross-origin requests."},{"key":"Server","value":"nginx","description":"Server Name"}]},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["postman.setEnvironmentVariable(\\"username\\", \\"a85\\");","postman.setEnvironmentVariable(\\"repository\\", \\"Newman\\")"]}},{"listen":"prerequest","script":{"type":"text/javascript","exec":"console.log(\\"hello\\");\\r\\nconsole.log(\'hi\')"}}]}]}]}]}],"protocolProfileBehavior":{"disableBodyPruning":false}}';
1212
const POSTMAN_COLLECTION_FILE_PATH = path.join(tmpdir(), 'postman-collection.json');
1313

1414
vi.mock('main/persistence/service/persistence-service');
@@ -39,7 +39,10 @@ describe('ImportService', () => {
3939
const firstChild = childrenLevel1[0] as TrufosRequest;
4040
expect(firstChild.type).toBe('request');
4141
expect(firstChild.title).toBe('200 ok');
42-
expect(firstChild.url).toBe('http://echo.getpostman.com/status/200');
42+
expect(firstChild.url).toEqual({
43+
base: 'http://echo.getpostman.com/status/200',
44+
query: [{ key: 'key', value: 'value', isActive: true }],
45+
});
4346
expect(firstChild.method).toBe('GET');
4447
expect(firstChild.headers).toEqual([]);
4548
expect(firstChild.body).toEqual({
@@ -50,7 +53,7 @@ describe('ImportService', () => {
5053
const secondChild = childrenLevel1[1] as TrufosRequest;
5154
expect(secondChild.type).toBe('request');
5255
expect(secondChild.title).toBe('200 ok');
53-
expect(secondChild.url).toBe('http://echo.getpostman.com/post');
56+
expect(secondChild.url).toEqual({ base: 'http://echo.getpostman.com/post', query: [] });
5457
expect(secondChild.method).toBe('POST');
5558
expect(secondChild.headers).toEqual([
5659
{
@@ -74,7 +77,10 @@ describe('ImportService', () => {
7477
const firstChildLevel2 = childrenLevel2[0] as TrufosRequest;
7578
expect(firstChildLevel2.type).toBe('request');
7679
expect(firstChildLevel2.title).toBe('201');
77-
expect(firstChildLevel2.url).toBe('http://echo.getpostman.com/status/201');
80+
expect(firstChildLevel2.url).toEqual({
81+
base: 'http://echo.getpostman.com/status/201',
82+
query: [],
83+
});
7884
expect(firstChildLevel2.method).toBe('PUT');
7985
expect(firstChildLevel2.headers).toEqual([
8086
{
@@ -97,7 +103,7 @@ describe('ImportService', () => {
97103
const secondChildLevel2 = childrenLevel2[1] as TrufosRequest;
98104
expect(secondChildLevel2.type).toBe('request');
99105
expect(secondChildLevel2.title).toBe('201');
100-
expect(secondChildLevel2.url).toBe('http://echo.getpostman.com/post');
106+
expect(secondChildLevel2.url).toEqual({ base: 'http://echo.getpostman.com/post', query: [] });
101107
expect(secondChildLevel2.method).toBe('GET');
102108
expect(secondChildLevel2.headers).toEqual([]);
103109
expect(secondChildLevel2.body).toEqual({

src/main/import/service/postman-importer.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { Collection as TrufosCollection } from 'shim/objects/collection';
99
import { Folder as TrufosFolder } from 'shim/objects/folder';
1010
import { RequestBody, RequestBodyType, TrufosRequest } from 'shim/objects/request';
11+
import { parseUrl } from 'shim/objects/url';
1112
import { RequestMethod } from 'shim/objects/request-method';
1213
import fs from 'node:fs/promises';
1314
import { VARIABLE_NAME_REGEX, VariableObject } from 'shim/objects/variables';
@@ -32,8 +33,8 @@ export class PostmanImporter implements CollectionImporter {
3233
variable.id,
3334
{
3435
value: variable.toString(),
35-
},
36-
] as [string, VariableObject]
36+
} as VariableObject,
37+
] as const
3738
);
3839
logger.info('Loaded', variablesArray.length, 'collection variables');
3940

@@ -111,18 +112,17 @@ export class PostmanImporter implements CollectionImporter {
111112
parentId: parent.id,
112113
type: 'request',
113114
title: item.name,
114-
url: request.url.toString(),
115-
method: request.method as RequestMethod,
115+
url: parseUrl(request.url.toString()),
116116
headers: request.headers.all().map((header) => ({
117117
key: header.key,
118118
value: header.value,
119119
isActive: !header.disabled,
120120
})),
121+
method: request.method as RequestMethod,
121122
body: bodyInfo ?? {
122123
type: RequestBodyType.TEXT,
123124
mimeType: DEFAULT_MIME_TYPE,
124125
},
125-
queryParams: [],
126126
};
127127

128128
parent.children.push(trufosRequest);

src/main/network/service/http-service.test.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { HttpService } from './http-service';
22
import { MockAgent } from 'undici';
33
import fs from 'node:fs';
44
import { TrufosRequest } from 'shim/objects/request';
5+
import { parseUrl, TrufosURL } from 'shim/objects/url';
56
import { randomUUID } from 'node:crypto';
67
import { RequestMethod } from 'shim/objects/request-method';
78
import { IncomingHttpHeaders } from 'undici/types/header';
@@ -31,11 +32,10 @@ describe('HttpService', () => {
3132
parentId: randomUUID(),
3233
type: 'request',
3334
title: 'Test Request',
34-
url: url.toString(),
35+
url: parseUrl(url.toString()),
3536
method: RequestMethod.GET,
3637
headers: [],
3738
body: null,
38-
queryParams: [],
3939
};
4040

4141
// Act
@@ -70,11 +70,10 @@ describe('HttpService', () => {
7070
parentId: randomUUID(),
7171
type: 'request',
7272
title: 'Test Request',
73-
url: url.toString(),
73+
url: parseUrl(url.toString()),
7474
method: RequestMethod.GET,
7575
headers: [],
7676
body: null,
77-
queryParams: [],
7877
};
7978

8079
// Act
@@ -98,11 +97,10 @@ describe('HttpService', () => {
9897
parentId: randomUUID(),
9998
type: 'request',
10099
title: 'Test Authenticated Request',
101-
url: url.toString(),
100+
url: parseUrl(url.toString()),
102101
method: RequestMethod.GET,
103102
headers: [],
104103
body: null,
105-
queryParams: [],
106104
auth: { type: AuthorizationType.BASIC, username, password },
107105
};
108106
const httpService = setupMockHttpService(url, null);
@@ -128,11 +126,10 @@ describe('HttpService', () => {
128126
parentId: randomUUID(),
129127
type: 'request',
130128
title: 'Test Authenticated Request',
131-
url: url.toString(),
129+
url: parseUrl(url.toString()),
132130
method: RequestMethod.GET,
133131
headers: [{ key: 'Authorization', value: authorizationValue, isActive: true }],
134132
body: null,
135-
queryParams: [],
136133
auth: { type: AuthorizationType.BASIC, username, password },
137134
};
138135
const httpService = setupMockHttpService(url, null);
@@ -170,11 +167,10 @@ describe('HttpService', () => {
170167
parentId: randomUUID(),
171168
type: 'request',
172169
title: 'Variable URL Request',
173-
url: rawUrl,
170+
url: parseUrl(rawUrl),
174171
method: RequestMethod.GET,
175172
headers: [],
176173
body: null,
177-
queryParams: [],
178174
};
179175

180176
// Act
@@ -208,14 +204,13 @@ describe('HttpService', () => {
208204
parentId: randomUUID(),
209205
type: 'request',
210206
title: 'Header Variable Request',
211-
url: 'https://example.com/test',
207+
url: parseUrl(finalUrl.toString()),
212208
method: RequestMethod.GET,
213209
headers: [
214210
{ key: 'X-Api-Version', value: '{{ apiVersion }}', isActive: true },
215211
{ key: 'Authorization', value: 'Bearer {{ token }}', isActive: true },
216212
],
217213
body: null,
218-
queryParams: [],
219214
};
220215

221216
// Act
@@ -250,14 +245,13 @@ describe('HttpService', () => {
250245
parentId: randomUUID(),
251246
type: 'request',
252247
title: 'Multi Header Variable Request',
253-
url: 'https://example.com/multi',
248+
url: parseUrl('https://example.com/multi'),
254249
method: RequestMethod.GET,
255250
headers: [
256251
{ key: 'X-Trace-Id', value: '{{ trace1 }}', isActive: true },
257252
{ key: 'X-Trace-Id', value: '{{ trace2 }}', isActive: true },
258253
],
259254
body: null,
260-
queryParams: [],
261255
};
262256

263257
// Act

src/main/network/service/http-service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import fs from 'node:fs';
66
import { Readable } from 'stream';
77
import { EnvironmentService } from 'main/environment/service/environment-service';
88
import { RequestBodyType, TrufosRequest } from 'shim/objects/request';
9+
import { buildUrl } from 'shim/objects/url';
910
import { TrufosResponse } from 'shim/objects/response';
1011
import { PersistenceService } from 'main/persistence/service/persistence-service';
1112
import { TrufosHeader } from 'shim/objects/headers';
@@ -54,7 +55,7 @@ export class HttpService {
5455
}
5556

5657
// resolve variables (except in body, which is resolved stream-based during send)
57-
const url = await environmentService.setVariablesInString(request.url);
58+
const url = await environmentService.setVariablesInString(buildUrl(request.url));
5859
const headers = await this.resolveVariablesInHeaders(
5960
this.trufosHeadersToUndiciHeaders(request.headers)
6061
);

0 commit comments

Comments
 (0)