Skip to content

Commit 4acc1da

Browse files
fix: change url builder, add @ prefix for files
1 parent 341d2b9 commit 4acc1da

File tree

10 files changed

+71
-54
lines changed

10 files changed

+71
-54
lines changed

Diff for: command-snapshot.json

+1-11
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,7 @@
1212
"command": "api:request:rest",
1313
"flagAliases": [],
1414
"flagChars": ["H", "S", "X", "b", "f", "i", "o"],
15-
"flags": [
16-
"api-version",
17-
"body",
18-
"file",
19-
"flags-dir",
20-
"header",
21-
"include",
22-
"method",
23-
"stream-to-file",
24-
"target-org"
25-
],
15+
"flags": ["body", "file", "flags-dir", "header", "include", "method", "stream-to-file", "target-org"],
2616
"plugin": "@salesforce/plugin-api"
2717
}
2818
]

Diff for: messages/rest.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,27 @@ For a full list of supported REST endpoints and resources, see https://developer
1212

1313
- List information about limits in the org with alias "my-org":
1414

15-
<%= config.bin %> <%= command.id %> 'limits' --target-org my-org
15+
<%= config.bin %> <%= command.id %> 'services/data/v56.0/limits' --target-org my-org
1616

1717
- List all endpoints in your default org; write the output to a file called "output.txt" and include the HTTP response status and headers:
1818

19-
<%= config.bin %> <%= command.id %> '/' --stream-to-file output.txt --include
19+
<%= config.bin %> <%= command.id %> '/services/data/v56.0/' --stream-to-file output.txt --include
2020

2121
- Get the response in XML format by specifying the "Accept" HTTP header:
2222

23-
<%= config.bin %> <%= command.id %> 'limits' --header 'Accept: application/xml'
23+
<%= config.bin %> <%= command.id %> '/services/data/v56.0/limits' --header 'Accept: application/xml'
2424

2525
- Create an account record using the POST method; specify the request details directly in the "--body" flag:
2626

27-
<%= config.bin %> <%= command.id %> sobjects/account --body "{\"Name\" : \"Account from REST API\",\"ShippingCity\" : \"Boise\"}" --method POST
27+
<%= config.bin %> <%= command.id %> /services/data/v56.0/sobjects/account --body "{\"Name\" : \"Account from REST API\",\"ShippingCity\" : \"Boise\"}" --method POST
2828

29-
- Create an account record using the information in a file called "info.json":
29+
- Create an account record using the information in a file called "info.json" (note the @ prefixing the file name):
3030

31-
<%= config.bin %> <%= command.id %> 'sobjects/account' --body info.json --method POST
31+
<%= config.bin %> <%= command.id %> '/services/data/v56.0/sobjects/account' --body @info.json --method POST
3232

3333
- Update an account record using the PATCH method:
3434

35-
<%= config.bin %> <%= command.id %> 'sobjects/account/<Account ID>' --body "{\"BillingCity\": \"San Francisco\"}" --method PATCH
35+
<%= config.bin %> <%= command.id %> '/services/data/v56.0/sobjects/account/<Account ID>' --body "{\"BillingCity\": \"San Francisco\"}" -X PATCH
3636

3737
- Store the values for the request header, body, and so on, in a file, which you then specify with the --file flag; see the description of --file for more information:
3838

@@ -81,4 +81,4 @@ HTTP header in "key:value" format.
8181

8282
# flags.body.summary
8383

84-
File or content for the body of the HTTP request. Specify "-" to read from standard input or "" for an empty body.
84+
File or content for the body of the HTTP request. Specify "-" to read from standard input or "" for an empty body. If passing a file, prefix it with '@' like CURL

Diff for: src/commands/api/request/rest.ts

+17-8
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ export class Rest extends SfCommand<void> {
5555
public static enableJsonFlag = false;
5656
public static readonly flags = {
5757
'target-org': Flags.requiredOrg(),
58-
'api-version': Flags.orgApiVersion(),
5958
include: includeFlag,
6059
method: Flags.option({
6160
options: methodOptions,
@@ -73,6 +72,7 @@ export class Rest extends SfCommand<void> {
7372
description: messages.getMessage('flags.file.description'),
7473
helpValue: 'file',
7574
char: 'f',
75+
exclusive: ['body'],
7676
}),
7777
'stream-to-file': streamToFileFlag,
7878
body: Flags.string({
@@ -106,12 +106,7 @@ export class Rest extends SfCommand<void> {
106106

107107
// the conditional above ensures we either have an arg or it's in the file - now we just have to find where the URL value is
108108
const specified = args.url ?? (fileOptions?.url as { raw: string }).raw ?? fileOptions?.url;
109-
const url = new URL(
110-
`${org.getField<string>(Org.Fields.INSTANCE_URL)}/services/data/v${
111-
flags['api-version'] ?? (await org.retrieveMaxApiVersion())
112-
// replace first '/' to create valid URL
113-
}/${specified.replace(/\//y, '')}`
114-
);
109+
const url = new URL(`${org.getField<string>(Org.Fields.INSTANCE_URL)}/${specified.replace(/\//y, '')}`);
115110

116111
// default the method to GET here to allow flags to override, but not hinder reading from files, rather than setting the default in the flag definition
117112
const method = flags.method ?? fileOptions?.method ?? 'GET';
@@ -120,8 +115,22 @@ export class Rest extends SfCommand<void> {
120115
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
121116
throw new SfError(`"${method}" must be one of ${methodOptions.join(', ')}`);
122117
}
118+
// body can be undefined;
119+
// if we have a --body @myfile.json, read the file
120+
// if we have a --body '{"key":"value"}' use that
121+
// else read from --file's body
122+
let body;
123+
if (method !== 'GET') {
124+
if (flags.body && flags.body.startsWith('@')) {
125+
// remove the '@' and read it
126+
body = readFileSync(flags.body.substring(1));
127+
} else if (flags.body) {
128+
body = flags.body;
129+
} else if (!flags.body) {
130+
body = getBodyContents(fileOptions?.body);
131+
}
132+
}
123133

124-
const body = method !== 'GET' ? flags.body ?? getBodyContents(fileOptions?.body) : undefined;
125134
let headers = getHeaders(flags.header ?? fileOptions?.header);
126135

127136
if (body instanceof FormData) {

Diff for: test/commands/api/request/rest/rest.nut.ts

+22-6
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ skipIfWindows('api:request:rest NUT', () => {
4848

4949
describe('std out', () => {
5050
it('get result in json format', () => {
51-
const result = execCmd("api request rest 'limits'").shellOutput.stdout;
51+
const result = execCmd("api request rest '/services/data/v56.0/limits'").shellOutput.stdout;
5252

5353
// make sure we got a JSON object back
5454
expect(Object.keys(JSON.parse(result) as Record<string, unknown>)).to.have.length;
@@ -71,7 +71,8 @@ skipIfWindows('api:request:rest NUT', () => {
7171
});
7272

7373
it('should pass headers', () => {
74-
const result = execCmd("api request rest 'limits' -H 'Accept: application/xml'").shellOutput.stdout;
74+
const result = execCmd("api request rest '/services/data/v56.0/limits' -H 'Accept: application/xml'").shellOutput
75+
.stdout;
7576

7677
// the headers will change this to xml
7778
expect(result.startsWith('<?xml version="1.0" encoding="UTF-8"?><LimitsSnapshot>')).to.be.true;
@@ -94,9 +95,22 @@ skipIfWindows('api:request:rest NUT', () => {
9495
expect(res).to.include('"standardEmailPhotoUrl"');
9596
});
9697

98+
it('can send --body as a file', () => {
99+
const res = execCmd(
100+
`api request rest /services/data/v60.0/jobs/ingest -X POST --body @${join(
101+
testSession.project.dir,
102+
'bulkOpen.json'
103+
)}`
104+
).shellOutput.stdout;
105+
// this prints as json to stdout, verify a few key/values
106+
expect(res).to.include('"id":');
107+
expect(res).to.include('"operation": "insert"');
108+
expect(res).to.include('"object": "Account"');
109+
});
110+
97111
it('can send raw data, with disabled headers', () => {
98112
const res = execCmd(`api request rest --file ${join(testSession.project.dir, 'raw.json')}`).shellOutput.stdout;
99-
// this prints as json to stdout, verify a few key/valuess
113+
// this prints as json to stdout, verify a few key/values
100114
expect(res).to.include('"AnalyticsExternalDataSizeMB":');
101115
expect(res).to.include('"SingleEmail"');
102116
expect(res).to.include('"PermissionSets"');
@@ -105,7 +119,8 @@ skipIfWindows('api:request:rest NUT', () => {
105119

106120
describe('stream-to-file', () => {
107121
it('get result in json format', () => {
108-
const result = execCmd("api request rest 'limits' --stream-to-file out.txt").shellOutput.stdout;
122+
const result = execCmd("api request rest '/services/data/v56.0/limits' --stream-to-file out.txt").shellOutput
123+
.stdout;
109124

110125
expect(result.trim()).to.equal('File saved to out.txt');
111126

@@ -115,8 +130,9 @@ skipIfWindows('api:request:rest NUT', () => {
115130
});
116131

117132
it('should pass headers', () => {
118-
const result = execCmd("api request rest 'limits' -H 'Accept: application/xml' --stream-to-file out.txt")
119-
.shellOutput.stdout;
133+
const result = execCmd(
134+
"api request rest '/services/data/v56.0/limits' -H 'Accept: application/xml' --stream-to-file out.txt"
135+
).shellOutput.stdout;
120136

121137
expect(result.trim()).to.equal('File saved to out.txt');
122138

Diff for: test/commands/api/request/rest/rest.test.ts

+13-17
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,22 @@ describe('rest', () => {
5252
it('should request org limits and default to "GET" HTTP method', async () => {
5353
nock(testOrg.instanceUrl).get('/services/data/v56.0/limits').reply(200, orgLimitsResponse);
5454

55-
await Rest.run(['--api-version', '56.0', 'limits', '--target-org', 'test@hub.com']);
55+
await Rest.run(['services/data/v56.0/limits', '--target-org', 'test@hub.com']);
5656

5757
expect(uxStub.styledJSON.args[0][0]).to.deep.equal(orgLimitsResponse);
5858
});
5959

6060
it("should strip leading '/'", async () => {
6161
nock(testOrg.instanceUrl).get('/services/data/v56.0/limits').reply(200, orgLimitsResponse);
6262

63-
await Rest.run(['--api-version', '56.0', '/limits', '--target-org', 'test@hub.com']);
63+
await Rest.run(['/services/data/v56.0/limits', '--target-org', 'test@hub.com']);
6464

6565
expect(uxStub.styledJSON.args[0][0]).to.deep.equal(orgLimitsResponse);
6666
});
6767

6868
it('should throw error for invalid header args', async () => {
6969
try {
70-
await Rest.run(['limits', '--target-org', 'test@hub.com', '-H', 'myInvalidHeader']);
70+
await Rest.run(['/services/data/v56.0/limits', '--target-org', 'test@hub.com', '-H', 'myInvalidHeader']);
7171
assert.fail('the above should throw');
7272
} catch (e) {
7373
expect((e as SfError).name).to.equal('Failed To Parse HTTP Header');
@@ -81,15 +81,7 @@ describe('rest', () => {
8181
it('should redirect to file', async () => {
8282
nock(testOrg.instanceUrl).get('/services/data/v56.0/limits').reply(200, orgLimitsResponse);
8383
const writeSpy = $$.SANDBOX.stub(process.stdout, 'write');
84-
await Rest.run([
85-
'--api-version',
86-
'56.0',
87-
'limits',
88-
'--target-org',
89-
'test@hub.com',
90-
'--stream-to-file',
91-
'myOutput.txt',
92-
]);
84+
await Rest.run(['/services/data/v56.0/limits', '--target-org', 'test@hub.com', '--stream-to-file', 'myOutput.txt']);
9385

9486
// gives it a second to resolve promises and close streams before we start asserting
9587
await sleep(1000);
@@ -119,9 +111,7 @@ describe('rest', () => {
119111
.reply(200, xmlRes);
120112

121113
await Rest.run([
122-
'/',
123-
'--api-version',
124-
'42.0',
114+
'/services/data/v42.0/',
125115
'--method',
126116
'GET',
127117
'--header',
@@ -136,7 +126,13 @@ describe('rest', () => {
136126

137127
it('should validate HTTP headers are in a "key:value" format', async () => {
138128
try {
139-
await Rest.run(['services/data', '--header', 'Accept application/xml', '--target-org', 'test@hub.com']);
129+
await Rest.run([
130+
'/services/data/v56.0/limits',
131+
'--header',
132+
'Accept application/xml',
133+
'--target-org',
134+
'test@hub.com',
135+
]);
140136
} catch (e) {
141137
const err = e as SfError;
142138
expect(err.message).to.equal('Failed to parse HTTP header: "Accept application/xml".');
@@ -208,7 +204,7 @@ describe('rest', () => {
208204
location: `${testOrg.instanceUrl}/services/data/v56.0/limits`,
209205
});
210206

211-
await Rest.run(['limites', '--api-version', '56.0', '--target-org', 'test@hub.com']);
207+
await Rest.run(['/services/data/v56.0/limites', '--target-org', 'test@hub.com']);
212208

213209
expect(uxStub.styledJSON.args[0][0]).to.deep.equal(orgLimitsResponse);
214210
});

Diff for: test/test-files/data-project/bulkOpen.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"object": "Account",
3+
"contentType": "CSV",
4+
"operation": "insert",
5+
"lineEnding": "LF"
6+
}

Diff for: test/test-files/data-project/fileUpload.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@
2020
}
2121
]
2222
},
23-
"url": "connect/files/users/me"
23+
"url": "/services/data/v56.0/connect/files/users/me"
2424
}

Diff for: test/test-files/data-project/profilePicUpload.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@
2525
}
2626
]
2727
},
28-
"url": "connect/user-profiles/me/photo"
28+
"url": "/services/data/v56.0/connect/user-profiles/me/photo"
2929
}

Diff for: test/test-files/data-project/raw.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@
1414
"mode": "raw"
1515
},
1616
"url": {
17-
"raw": "/limits"
17+
"raw": "/services/data/v56.0/limits"
1818
}
1919
}

Diff for: test/test-files/data-project/rest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"headers": ["Accept:application/json"],
3-
"url": "/limits"
3+
"url": "/services/data/v56.0/limits"
44
}

0 commit comments

Comments
 (0)