Skip to content

Commit 354b215

Browse files
committed
Merge branch 'JS-Issue-293-298-Emptybody-Content-type' of https://github.com/microsoftgraph/msgraph-sdk-javascript into JS-Issue-293-298-Emptybody-Content-type
2 parents 3f9412d + cff1a71 commit 354b215

File tree

3 files changed

+185
-39
lines changed

3 files changed

+185
-39
lines changed

spec/core/urlGeneration.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,59 @@ cases.push({
9191
.query("$search=senior"),
9292
});
9393

94+
cases.push({
95+
url: "https://graph.microsoft.com/beta/me/people?$select=displayName,title,id&$count=false&$expand=a($expand=a,b)",
96+
request: client
97+
.api("/me/people")
98+
.version("beta")
99+
.select(["displayName", "title"])
100+
.count(true)
101+
.expand("a($expand=a,b)")
102+
.query("$select=id")
103+
.query("$count=false"),
104+
});
105+
106+
cases.push({
107+
url: "https://graph.microsoft.com/v1.0/me/people?$select=displayName,title,id&select=value",
108+
request: client
109+
.api("/me/people")
110+
.version("v1.0")
111+
.select(["displayName", "title"])
112+
.query({ select: "value" })
113+
.query({ $select: "id" }),
114+
});
115+
116+
// handling an invalid input
117+
cases.push({
118+
url: "https://graph.microsoft.com/v1.0/me/people?$select=displayName,title&select=value&test",
119+
request: client
120+
.api("/me/people")
121+
.version("v1.0")
122+
.select(["displayName", "title"])
123+
.query({ select: "value" })
124+
.query("test"),
125+
});
126+
127+
// handling an invalid input
128+
cases.push({
129+
url: "https://graph.microsoft.com/v1.0/me/people?$expand=address($select=home,$expand=city)&$select=home,displayName,title&select=value&test",
130+
request: client
131+
.api("/me/people?$expand=address($select=home,$expand=city)&$select=home")
132+
.version("v1.0")
133+
.select(["displayName", "title"])
134+
.query({ select: "value" })
135+
.query("test"),
136+
});
137+
138+
cases.push({
139+
url: "https://graph.microsoft.com/v1.0/me/people?$expand=home($select=home)&name=test",
140+
request: client.api("/me/people").query("?name=test&$expand=home($select=home)"),
141+
});
142+
cases.push({
143+
url: "https://graph.microsoft.com/v1.0/me/people?$expand=home($select=home)&name=test",
144+
request: client.api("/me/people?name=test&$expand=home($select=home)"),
145+
});
146+
94147
cases.push({
95148
url: "https://graph.microsoft.com/v1.0/me/drive/root?$expand=children($select=name),permissions",
96149
request: client

spec/core/urlParsing.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ const testCases = {
2828
"me?$select=displayName": "https://graph.microsoft.com/v1.0/me?$select=displayName",
2929
"me?select=displayName": "https://graph.microsoft.com/v1.0/me?select=displayName",
3030
"https://graph.microsoft.com/beta/me?select=displayName": "https://graph.microsoft.com/beta/me?select=displayName",
31+
32+
// test for nested query parameters
33+
"https://graph.microsoft.com/beta/identityGovernance/entitlementManagement/accessPackages/?$expand=accessPackageAssignmentPolicies,accessPackageResourceRoleScopes($expand=accessPackageResourceRole,accessPackageResourceScope)": "https://graph.microsoft.com/beta/identityGovernance/entitlementManagement/accessPackages/?$expand=accessPackageAssignmentPolicies,accessPackageResourceRoleScopes($expand=accessPackageResourceRole,accessPackageResourceScope)",
34+
"me?$select=displayName&$select=id": "https://graph.microsoft.com/v1.0/me?$select=displayName,id",
35+
"/me?$filter=b&$filter=a": "https://graph.microsoft.com/v1.0/me?$filter=a",
36+
"https://graph.microsoft.com/v1.0/me?$top=4&$expand=4&$iscount=true&$top=2": "https://graph.microsoft.com/v1.0/me?$top=2&$expand=4&$iscount=true",
37+
"/items?$expand=fields($select=Title)&$expand=name($select=firstName)": "https://graph.microsoft.com/v1.0/items?$expand=fields($select=Title),name($select=firstName)",
38+
39+
// Passing invalid parameters
40+
"/me?&test&123": "https://graph.microsoft.com/v1.0/me?&test&123",
41+
"/me?$select($select=name)": "https://graph.microsoft.com/v1.0/me?$select($select=name)",
3142
};
3243

3344
describe("urlParsing.ts", () => {
@@ -42,5 +53,5 @@ describe("urlParsing.ts", () => {
4253
}
4354
}
4455
});
45-
/* tslint:enable: no-string-literal */
4656
});
57+
/* tslint:enable: no-string-literal */

src/GraphRequest.ts

Lines changed: 120 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export interface URLComponents {
4848
path?: string;
4949
oDataQueryParams: KeyValuePairObjectStringNumber;
5050
otherURLQueryParams: KeyValuePairObjectStringNumber;
51+
otherURLQueryOptions: any[];
5152
}
5253

5354
/**
@@ -117,6 +118,7 @@ export class GraphRequest {
117118
version: this.config.defaultVersion,
118119
oDataQueryParams: {},
119120
otherURLQueryParams: {},
121+
otherURLQueryOptions: [],
120122
};
121123
this._headers = {};
122124
this._options = {};
@@ -169,14 +171,7 @@ export class GraphRequest {
169171
// Capture query string into oDataQueryParams and otherURLQueryParams
170172
const queryParams = path.substring(queryStrPos + 1, path.length).split("&");
171173
for (const queryParam of queryParams) {
172-
const qParams = queryParam.split("=");
173-
const key = qParams[0];
174-
const value = qParams[1];
175-
if (oDataQueryNames.indexOf(key) !== -1) {
176-
this.urlComponents.oDataQueryParams[key] = value;
177-
} else {
178-
this.urlComponents.otherURLQueryParams[key] = value;
179-
}
174+
this.parseQueryParameter(queryParam);
180175
}
181176
}
182177
};
@@ -243,9 +238,105 @@ export class GraphRequest {
243238
}
244239
}
245240
}
241+
242+
if (urlComponents.otherURLQueryOptions.length !== 0) {
243+
for (const str of urlComponents.otherURLQueryOptions) {
244+
query.push(str);
245+
}
246+
}
246247
return query.length > 0 ? "?" + query.join("&") : "";
247248
}
248249

250+
/**
251+
* @private
252+
* Parses the query parameters to set the urlComponents property of the GraphRequest object
253+
* @param {string|KeyValuePairObjectStringNumber} queryDictionaryOrString - The query parameter
254+
* @returns The same GraphRequest instance that is being called with
255+
*/
256+
private parseQueryParameter(queryDictionaryOrString: string | KeyValuePairObjectStringNumber): GraphRequest {
257+
if (typeof queryDictionaryOrString === "string") {
258+
if (queryDictionaryOrString.charAt(0) === "?") {
259+
queryDictionaryOrString = queryDictionaryOrString.substring(1, queryDictionaryOrString.length);
260+
}
261+
262+
if (queryDictionaryOrString.indexOf("&") !== -1) {
263+
const queryParams = queryDictionaryOrString.split("&");
264+
for (const str of queryParams) {
265+
this.parseQueryParamenterString(str);
266+
}
267+
} else {
268+
this.parseQueryParamenterString(queryDictionaryOrString);
269+
}
270+
} else if (queryDictionaryOrString.constructor === Object) {
271+
for (const key in queryDictionaryOrString) {
272+
if (queryDictionaryOrString.hasOwnProperty(key)) {
273+
this.setURLComponentsQueryParamater(key, queryDictionaryOrString[key]);
274+
}
275+
}
276+
} else {
277+
/*Push values which are not of key-value structure.
278+
Example-> Handle an invalid input->.query(123) and let the Graph API respond with the error in the URL*/ this.urlComponents.otherURLQueryOptions.push(queryDictionaryOrString);
279+
}
280+
281+
return this;
282+
}
283+
284+
/**
285+
* @private
286+
* Parses the query parameter of string type to set the urlComponents property of the GraphRequest object
287+
* @param {string} queryParameter - the query parameters
288+
* returns nothing
289+
*/
290+
private parseQueryParamenterString(queryParameter: string): void {
291+
/* The query key-value pair must be split on the first equals sign to avoid errors in parsing nested query parameters.
292+
Example-> "/me?$expand=home($select=city)" */
293+
if (this.isValidQueryKeyValuePair(queryParameter)) {
294+
const indexOfFirstEquals = queryParameter.indexOf("=");
295+
const paramKey = queryParameter.substring(0, indexOfFirstEquals);
296+
const paramValue = queryParameter.substring(indexOfFirstEquals + 1, queryParameter.length);
297+
this.setURLComponentsQueryParamater(paramKey, paramValue);
298+
} else {
299+
/* Push values which are not of key-value structure.
300+
Example-> Handle an invalid input->.query(test), .query($select($select=name)) and let the Graph API respond with the error in the URL*/
301+
this.urlComponents.otherURLQueryOptions.push(queryParameter);
302+
}
303+
}
304+
305+
/**
306+
* @private
307+
* Sets values into the urlComponents property of GraphRequest object.
308+
* @param {string} paramKey - the query parameter key
309+
* @param {string} paramValue - the query paramter value
310+
* @returns nothing
311+
*/
312+
private setURLComponentsQueryParamater(paramKey: string, paramValue: string | number): void {
313+
if (oDataQueryNames.indexOf(paramKey) !== -1) {
314+
const currentValue = this.urlComponents.oDataQueryParams[paramKey];
315+
const isValueAppendable = currentValue && (paramKey === "$expand" || paramKey === "$select" || paramKey === "$orderby");
316+
this.urlComponents.oDataQueryParams[paramKey] = isValueAppendable ? currentValue + "," + paramValue : paramValue;
317+
} else {
318+
this.urlComponents.otherURLQueryParams[paramKey] = paramValue;
319+
}
320+
}
321+
/**
322+
* @private
323+
* Check if the query parameter string has a valid key-value structure
324+
* @param {string} queryString - the query parameter string. Example -> "name=value"
325+
* #returns true if the query string has a valid key-value structure else false
326+
*/
327+
private isValidQueryKeyValuePair(queryString: string): boolean {
328+
const indexofFirstEquals = queryString.indexOf("=");
329+
if (indexofFirstEquals === -1) {
330+
return false;
331+
}
332+
const indexofOpeningParanthesis = queryString.indexOf("(");
333+
if (indexofOpeningParanthesis !== -1 && queryString.indexOf("(") < indexofFirstEquals) {
334+
// Example -> .query($select($expand=true));
335+
return false;
336+
}
337+
return true;
338+
}
339+
249340
/**
250341
* @private
251342
* Updates the custom headers and options for a request
@@ -413,7 +504,7 @@ export class GraphRequest {
413504
* @public
414505
* To add properties for select OData Query param
415506
* @param {string|string[]} properties - The Properties value
416-
* @returns The same GraphRequest instance that is being called with
507+
* @returns The same GraphRequest instance that is being called with, after adding the properties for $select query
417508
*/
418509
/*
419510
* Accepts .select("displayName,birthday")
@@ -430,7 +521,7 @@ export class GraphRequest {
430521
* @public
431522
* To add properties for expand OData Query param
432523
* @param {string|string[]} properties - The Properties value
433-
* @returns The same GraphRequest instance that is being called with
524+
* @returns The same GraphRequest instance that is being called with, after adding the properties for $expand query
434525
*/
435526
public expand(properties: string | string[]): GraphRequest {
436527
this.addCsvQueryParameter("$expand", properties, arguments);
@@ -441,7 +532,7 @@ export class GraphRequest {
441532
* @public
442533
* To add properties for orderby OData Query param
443534
* @param {string|string[]} properties - The Properties value
444-
* @returns The same GraphRequest instance that is being called with
535+
* @returns The same GraphRequest instance that is being called with, after adding the properties for $orderby query
445536
*/
446537
public orderby(properties: string | string[]): GraphRequest {
447538
this.addCsvQueryParameter("$orderby", properties, arguments);
@@ -450,9 +541,9 @@ export class GraphRequest {
450541

451542
/**
452543
* @public
453-
* To add query string for filter OData Query param
544+
* To add query string for filter OData Query param. The request URL accepts only one $filter Odata Query option and its value is set to the most recently passed filter query string.
454545
* @param {string} filterStr - The filter query string
455-
* @returns The same GraphRequest instance that is being called with
546+
* @returns The same GraphRequest instance that is being called with, after adding the $filter query
456547
*/
457548
public filter(filterStr: string): GraphRequest {
458549
this.urlComponents.oDataQueryParams.$filter = filterStr;
@@ -461,9 +552,9 @@ export class GraphRequest {
461552

462553
/**
463554
* @public
464-
* To add criterion for search OData Query param
555+
* To add criterion for search OData Query param. The request URL accepts only one $search Odata Query option and its value is set to the most recently passed search criterion string.
465556
* @param {string} searchStr - The search criterion string
466-
* @returns The same GraphRequest instance that is being called with
557+
* @returns The same GraphRequest instance that is being called with, after adding the $search query criteria
467558
*/
468559
public search(searchStr: string): GraphRequest {
469560
this.urlComponents.oDataQueryParams.$search = searchStr;
@@ -472,9 +563,9 @@ export class GraphRequest {
472563

473564
/**
474565
* @public
475-
* To add number for top OData Query param
566+
* To add number for top OData Query param. The request URL accepts only one $top Odata Query option and its value is set to the most recently passed number value.
476567
* @param {number} n - The number value
477-
* @returns The same GraphRequest instance that is being called with
568+
* @returns The same GraphRequest instance that is being called with, after adding the number for $top query
478569
*/
479570
public top(n: number): GraphRequest {
480571
this.urlComponents.oDataQueryParams.$top = n;
@@ -483,9 +574,9 @@ export class GraphRequest {
483574

484575
/**
485576
* @public
486-
* To add number for skip OData Query param
577+
* To add number for skip OData Query param. The request URL accepts only one $skip Odata Query option and its value is set to the most recently passed number value.
487578
* @param {number} n - The number value
488-
* @returns The same GraphRequest instance that is being called with
579+
* @returns The same GraphRequest instance that is being called with, after adding the number for the $skip query
489580
*/
490581
public skip(n: number): GraphRequest {
491582
this.urlComponents.oDataQueryParams.$skip = n;
@@ -494,9 +585,9 @@ export class GraphRequest {
494585

495586
/**
496587
* @public
497-
* To add token string for skipToken OData Query param
588+
* To add token string for skipToken OData Query param. The request URL accepts only one $skipToken Odata Query option and its value is set to the most recently passed token value.
498589
* @param {string} token - The token value
499-
* @returns The same GraphRequest instance that is being called with
590+
* @returns The same GraphRequest instance that is being called with, after adding the token string for $skipToken query option
500591
*/
501592
public skipToken(token: string): GraphRequest {
502593
this.urlComponents.oDataQueryParams.$skipToken = token;
@@ -505,9 +596,9 @@ export class GraphRequest {
505596

506597
/**
507598
* @public
508-
* To add boolean for count OData Query param
599+
* To add boolean for count OData Query param. The URL accepts only one $count Odata Query option and its value is set to the most recently passed boolean value.
509600
* @param {boolean} isCount - The count boolean
510-
* @returns The same GraphRequest instance that is being called with
601+
* @returns The same GraphRequest instance that is being called with, after adding the boolean value for the $count query option
511602
*/
512603
public count(isCount: boolean = false): GraphRequest {
513604
this.urlComponents.oDataQueryParams.$count = isCount.toString();
@@ -518,23 +609,14 @@ export class GraphRequest {
518609
* @public
519610
* Appends query string to the urlComponent
520611
* @param {string|KeyValuePairObjectStringNumber} queryDictionaryOrString - The query value
521-
* @returns The same GraphRequest instance that is being called with
612+
* @returns The same GraphRequest instance that is being called with, after appending the query string to the url component
613+
*/
614+
/*
615+
* Accepts .query("displayName=xyz")
616+
* and .select({ name: "value" })
522617
*/
523618
public query(queryDictionaryOrString: string | KeyValuePairObjectStringNumber): GraphRequest {
524-
const otherURLQueryParams = this.urlComponents.otherURLQueryParams;
525-
if (typeof queryDictionaryOrString === "string") {
526-
const querySplit = queryDictionaryOrString.split("=");
527-
const queryKey = querySplit[0];
528-
const queryValue = querySplit[1];
529-
otherURLQueryParams[queryKey] = queryValue;
530-
} else {
531-
for (const key in queryDictionaryOrString) {
532-
if (queryDictionaryOrString.hasOwnProperty(key)) {
533-
otherURLQueryParams[key] = queryDictionaryOrString[key];
534-
}
535-
}
536-
}
537-
return this;
619+
return this.parseQueryParameter(queryDictionaryOrString);
538620
}
539621

540622
/**

0 commit comments

Comments
 (0)