Skip to content

Commit cff1a71

Browse files
authored
Merge branch 'dev' into JS-Issue-293-298-Emptybody-Content-type
2 parents 45727eb + ed902ec commit cff1a71

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
@@ -50,6 +50,7 @@ export interface URLComponents {
5050
path?: string;
5151
oDataQueryParams: KeyValuePairObjectStringNumber;
5252
otherURLQueryParams: KeyValuePairObjectStringNumber;
53+
otherURLQueryOptions: any[];
5354
}
5455

5556
/**
@@ -119,6 +120,7 @@ export class GraphRequest {
119120
version: this.config.defaultVersion,
120121
oDataQueryParams: {},
121122
otherURLQueryParams: {},
123+
otherURLQueryOptions: [],
122124
};
123125
this._headers = {};
124126
this._options = {};
@@ -171,14 +173,7 @@ export class GraphRequest {
171173
// Capture query string into oDataQueryParams and otherURLQueryParams
172174
const queryParams = path.substring(queryStrPos + 1, path.length).split("&");
173175
for (const queryParam of queryParams) {
174-
const qParams = queryParam.split("=");
175-
const key = qParams[0];
176-
const value = qParams[1];
177-
if (oDataQueryNames.indexOf(key) !== -1) {
178-
this.urlComponents.oDataQueryParams[key] = value;
179-
} else {
180-
this.urlComponents.otherURLQueryParams[key] = value;
181-
}
176+
this.parseQueryParameter(queryParam);
182177
}
183178
}
184179
};
@@ -245,9 +240,105 @@ export class GraphRequest {
245240
}
246241
}
247242
}
243+
244+
if (urlComponents.otherURLQueryOptions.length !== 0) {
245+
for (const str of urlComponents.otherURLQueryOptions) {
246+
query.push(str);
247+
}
248+
}
248249
return query.length > 0 ? "?" + query.join("&") : "";
249250
}
250251

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

453544
/**
454545
* @public
455-
* To add query string for filter OData Query param
546+
* 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.
456547
* @param {string} filterStr - The filter query string
457-
* @returns The same GraphRequest instance that is being called with
548+
* @returns The same GraphRequest instance that is being called with, after adding the $filter query
458549
*/
459550
public filter(filterStr: string): GraphRequest {
460551
this.urlComponents.oDataQueryParams.$filter = filterStr;
@@ -463,9 +554,9 @@ export class GraphRequest {
463554

464555
/**
465556
* @public
466-
* To add criterion for search OData Query param
557+
* 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.
467558
* @param {string} searchStr - The search criterion string
468-
* @returns The same GraphRequest instance that is being called with
559+
* @returns The same GraphRequest instance that is being called with, after adding the $search query criteria
469560
*/
470561
public search(searchStr: string): GraphRequest {
471562
this.urlComponents.oDataQueryParams.$search = searchStr;
@@ -474,9 +565,9 @@ export class GraphRequest {
474565

475566
/**
476567
* @public
477-
* To add number for top OData Query param
568+
* 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.
478569
* @param {number} n - The number value
479-
* @returns The same GraphRequest instance that is being called with
570+
* @returns The same GraphRequest instance that is being called with, after adding the number for $top query
480571
*/
481572
public top(n: number): GraphRequest {
482573
this.urlComponents.oDataQueryParams.$top = n;
@@ -485,9 +576,9 @@ export class GraphRequest {
485576

486577
/**
487578
* @public
488-
* To add number for skip OData Query param
579+
* 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.
489580
* @param {number} n - The number value
490-
* @returns The same GraphRequest instance that is being called with
581+
* @returns The same GraphRequest instance that is being called with, after adding the number for the $skip query
491582
*/
492583
public skip(n: number): GraphRequest {
493584
this.urlComponents.oDataQueryParams.$skip = n;
@@ -496,9 +587,9 @@ export class GraphRequest {
496587

497588
/**
498589
* @public
499-
* To add token string for skipToken OData Query param
590+
* 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.
500591
* @param {string} token - The token value
501-
* @returns The same GraphRequest instance that is being called with
592+
* @returns The same GraphRequest instance that is being called with, after adding the token string for $skipToken query option
502593
*/
503594
public skipToken(token: string): GraphRequest {
504595
this.urlComponents.oDataQueryParams.$skipToken = token;
@@ -507,9 +598,9 @@ export class GraphRequest {
507598

508599
/**
509600
* @public
510-
* To add boolean for count OData Query param
601+
* 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.
511602
* @param {boolean} isCount - The count boolean
512-
* @returns The same GraphRequest instance that is being called with
603+
* @returns The same GraphRequest instance that is being called with, after adding the boolean value for the $count query option
513604
*/
514605
public count(isCount: boolean = false): GraphRequest {
515606
this.urlComponents.oDataQueryParams.$count = isCount.toString();
@@ -520,23 +611,14 @@ export class GraphRequest {
520611
* @public
521612
* Appends query string to the urlComponent
522613
* @param {string|KeyValuePairObjectStringNumber} queryDictionaryOrString - The query value
523-
* @returns The same GraphRequest instance that is being called with
614+
* @returns The same GraphRequest instance that is being called with, after appending the query string to the url component
615+
*/
616+
/*
617+
* Accepts .query("displayName=xyz")
618+
* and .select({ name: "value" })
524619
*/
525620
public query(queryDictionaryOrString: string | KeyValuePairObjectStringNumber): GraphRequest {
526-
const otherURLQueryParams = this.urlComponents.otherURLQueryParams;
527-
if (typeof queryDictionaryOrString === "string") {
528-
const querySplit = queryDictionaryOrString.split("=");
529-
const queryKey = querySplit[0];
530-
const queryValue = querySplit[1];
531-
otherURLQueryParams[queryKey] = queryValue;
532-
} else {
533-
for (const key in queryDictionaryOrString) {
534-
if (queryDictionaryOrString.hasOwnProperty(key)) {
535-
otherURLQueryParams[key] = queryDictionaryOrString[key];
536-
}
537-
}
538-
}
539-
return this;
621+
return this.parseQueryParameter(queryDictionaryOrString);
540622
}
541623

542624
/**

0 commit comments

Comments
 (0)