Merge pull request #83 from Azure/daschult/Substitutions

Add the ability to read, modify, and substitute URL queries
This commit is contained in:
Dan Schulte 2018-05-14 09:35:12 -07:00 коммит произвёл GitHub
Родитель 0182b97925 eb4f9a84b1
Коммит 11b713bd7c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 380 добавлений и 40 удалений

Просмотреть файл

@ -1,8 +1,133 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
export interface URLQuery {
[queryParameterName: string]: string;
/**
* A class that handles the query portion of a URLBuilder.
*/
export class URLQuery {
private readonly _rawQuery: { [queryParameterName: string]: string } = {};
/**
* Get whether or not there any query parameters in this URLQuery.
*/
public any(): boolean {
return Object.keys(this._rawQuery).length > 0;
}
/**
* Set a query parameter with the provided name and value. If the parameterValue is undefined or
* empty, then this will attempt to remove an existing query parameter with the provided
* parameterName.
*/
public set(parameterName: string, parameterValue: any): void {
if (parameterName) {
if (parameterValue != undefined) {
this._rawQuery[parameterName] = parameterValue.toString();
} else {
delete this._rawQuery[parameterName];
}
}
}
/**
* Get the value of the query parameter with the provided name. If no parameter exists with the
* provided parameter name, then undefined will be returned.
*/
public get(parameterName: string): string | undefined {
return parameterName ? this._rawQuery[parameterName] : undefined;
}
/**
* Get the string representation of this query. The return value will not start with a "?".
*/
public toString(): string {
let result = "";
for (const parameterName in this._rawQuery) {
if (result) {
result += "&";
}
result += `${parameterName}=${this._rawQuery[parameterName]}`;
}
return result;
}
/**
* Parse a URLQuery from the provided text.
*/
public static parse(text: string): URLQuery {
const result = new URLQuery();
if (text) {
if (text.startsWith("?")) {
text = text.substring(1);
}
const parameterNameState = "parameterName";
const parameterValueState = "parameterValue";
const invalidateParameterState = "invalidParameter";
let currentState = parameterNameState;
let parameterName = "";
let parameterValue = "";
for (let i = 0; i < text.length; ++i) {
const currentCharacter: string = text[i];
switch (currentState) {
case parameterNameState:
switch (currentCharacter) {
case "=":
currentState = parameterValueState;
break;
case "&":
parameterName = "";
parameterValue = "";
break;
default:
parameterName += currentCharacter;
break;
}
break;
case parameterValueState:
switch (currentCharacter) {
case "=":
parameterName = "";
parameterValue = "";
currentState = invalidateParameterState;
break;
case "&":
result.set(parameterName, parameterValue);
parameterName = "";
parameterValue = "";
currentState = parameterNameState;
break;
default:
parameterValue += currentCharacter;
break;
}
break;
case invalidateParameterState:
if (currentCharacter === "&") {
currentState = parameterNameState;
}
break;
default:
throw new Error("Unrecognized URLQuery parse state: " + currentState);
}
}
if (currentState === parameterValueState) {
result.set(parameterName, parameterValue);
}
}
return result;
}
}
/**
@ -13,7 +138,7 @@ export class URLBuilder {
private _host: string | undefined;
private _port: string | undefined;
private _path: string | undefined;
private _query: URLQuery = {};
private _query: URLQuery | undefined;
/**
* Set the scheme/protocol for this URL. If the provided scheme contains other parts of a URL
@ -96,25 +221,62 @@ export class URLBuilder {
}
/**
* Set the query parameter in this URL with the provided queryParameterName to be the provided
* queryParameterEncodedValue.
* If the provided searchValue is found in this URLBuilder's path, then replace it with the
* provided replaceValue.
*/
public setQueryParameter(queryParameterName: string, queryParameterEncodedValue: string): void {
if (queryParameterName) {
this._query[queryParameterName] = queryParameterEncodedValue;
public pathSubstitution(searchValue: string, replaceValue: string): void {
if (this._path && searchValue) {
this._path = replaceAll(this._path, searchValue, replaceValue || "");
}
}
/**
* Set the query in this URL.
*/
public setQuery(query: string | URLQuery | undefined): void {
public setQuery(query: string | undefined): void {
if (!query) {
this._query = {};
} else if (typeof query !== "string") {
this._query = query;
this._query = undefined;
} else {
this.set(query, URLTokenizerState.QUERY);
this._query = URLQuery.parse(query);
}
}
/**
* Set a query parameter with the provided name and value in this URL's query. If the provided
* query parameter value is undefined or empty, then the query parameter will be removed if it
* existed.
*/
public setQueryParameter(queryParameterName: string, queryParameterValue: any): void {
if (queryParameterName) {
if (!this._query) {
this._query = new URLQuery();
}
this._query.set(queryParameterName, queryParameterValue);
}
}
/**
* Get the value of the query parameter with the provided query parameter name. If no query
* parameter exists with the provided name, then undefined will be returned.
*/
public getQueryParameterValue(queryParameterName: string): string | undefined {
return this._query ? this._query.get(queryParameterName) : undefined;
}
/**
* Get the query in this URL.
*/
public getQuery(): string | undefined {
return this._query ? this._query.toString() : undefined;
}
/**
* If the provided searchValue is found in this URLBuilder's query, then replace it with the
* provided replaceValue.
*/
public querySubstitution(searchValue: string, replaceValue: string): void {
if (this._query && searchValue) {
this._query = URLQuery.parse(replaceAll(this._query.toString(), searchValue, replaceValue));
}
}
@ -148,21 +310,7 @@ export class URLBuilder {
break;
case URLTokenType.QUERY:
let queryString: string | undefined = token.text;
if (queryString) {
if (queryString.startsWith("?")) {
queryString = queryString.substring(1);
}
for (const queryParameterString of queryString.split("&")) {
const queryParameterParts: string[] = queryParameterString.split("=");
if (queryParameterParts.length === 2) {
this.setQueryParameter(queryParameterParts[0], queryParameterParts[1]);
} else {
throw new Error("Malformed query parameter: " + queryParameterString);
}
}
}
this._query = URLQuery.parse(token.text);
break;
default:
@ -194,17 +342,8 @@ export class URLBuilder {
result += this._path;
}
if (this._query) {
let queryString = "";
for (const queryParameterName in this._query) {
if (queryString) {
queryString += "&";
}
queryString += `${queryParameterName}=${this._query[queryParameterName]}`;
}
if (queryString) {
result += `?${queryString}`;
}
if (this._query && this._query.any()) {
result += `?${this._query.toString()}`;
}
return result;
@ -271,6 +410,13 @@ export function isAlphaNumericCharacter(character: string): boolean {
(97 /* 'a' */ <= characterCode && characterCode <= 122 /* 'z' */);
}
/**
* Replace all of the instances of searchValue in value with the provided replaceValue.
*/
export function replaceAll(value: string, searchValue: string, replaceValue: string): string {
return !value || !searchValue ? value : value.split(searchValue).join(replaceValue || "");
}
/**
* A class that tokenizes URL strings.
*/

Просмотреть файл

@ -2,7 +2,109 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as assert from "assert";
import { URLTokenizer, URLToken, URLBuilder } from "../../lib/url";
import { URLTokenizer, URLToken, URLBuilder, URLQuery } from "../../lib/url";
describe("URLQuery", () => {
it(`constructor()`, () => {
const urlQuery = new URLQuery();
assert.strictEqual(urlQuery.any(), false);
assert.strictEqual(urlQuery.toString(), "");
});
describe("set(string,string)", () => {
it(`with undefined parameter name`, () => {
const urlQuery = new URLQuery();
urlQuery.set(undefined as any, "tasty");
assert.strictEqual(urlQuery.get(undefined as any), undefined);
assert.strictEqual(urlQuery.any(), false);
assert.strictEqual(urlQuery.toString(), "");
});
it(`with empty parameter name`, () => {
const urlQuery = new URLQuery();
urlQuery.set("", "tasty");
assert.strictEqual(urlQuery.get(""), undefined);
assert.strictEqual(urlQuery.any(), false);
assert.strictEqual(urlQuery.toString(), "");
});
it(`with undefined parameter value`, () => {
const urlQuery = new URLQuery();
urlQuery.set("apples", undefined);
assert.strictEqual(urlQuery.get("apples"), undefined);
assert.strictEqual(urlQuery.any(), false);
assert.strictEqual(urlQuery.toString(), "");
});
it(`with empty parameter value`, () => {
const urlQuery = new URLQuery();
urlQuery.set("apples", "");
assert.strictEqual(urlQuery.get("apples"), "");
assert.strictEqual(urlQuery.any(), true);
assert.strictEqual(urlQuery.toString(), "apples=");
});
it(`with non-empty parameter value`, () => {
const urlQuery = new URLQuery();
urlQuery.set("apples", "grapes");
assert.strictEqual(urlQuery.get("apples"), "grapes");
assert.strictEqual(urlQuery.any(), true);
assert.strictEqual(urlQuery.toString(), "apples=grapes");
});
it(`with existing parameter value and undefined parameter value`, () => {
const urlQuery = new URLQuery();
urlQuery.set("apples", "grapes");
urlQuery.set("apples", undefined);
assert.strictEqual(urlQuery.get("apples"), undefined);
assert.strictEqual(urlQuery.any(), false);
assert.strictEqual(urlQuery.toString(), "");
});
it(`with existing parameter value and empty parameter value`, () => {
const urlQuery = new URLQuery();
urlQuery.set("apples", "grapes");
urlQuery.set("apples", "");
assert.strictEqual(urlQuery.get("apples"), "");
assert.strictEqual(urlQuery.any(), true);
assert.strictEqual(urlQuery.toString(), "apples=");
});
});
describe("parse(string)", () => {
it(`with undefined`, () => {
assert.strictEqual(URLQuery.parse(undefined as any).toString(), "");
});
it(`with ""`, () => {
assert.strictEqual(URLQuery.parse("").toString(), "");
});
it(`with "A"`, () => {
assert.strictEqual(URLQuery.parse("A").toString(), "");
});
it(`with "A="`, () => {
assert.strictEqual(URLQuery.parse("A=").toString(), "A=");
});
it(`with "A=B"`, () => {
assert.strictEqual(URLQuery.parse("A=B").toString(), "A=B");
});
it(`with "A=&"`, () => {
assert.strictEqual(URLQuery.parse("A=").toString(), "A=");
});
it(`with "A=="`, () => {
assert.strictEqual(URLQuery.parse("A==").toString(), "");
});
it(`with "A=&B=C"`, () => {
assert.strictEqual(URLQuery.parse("A=&B=C").toString(), "A=&B=C");
});
});
});
describe("URLBuilder", () => {
describe("setScheme()", () => {
@ -487,6 +589,22 @@ describe("URLBuilder", () => {
});
});
describe("setQueryParameter()", () => {
it(`with "a" and undefined`, () => {
const urlBuilder = new URLBuilder();
urlBuilder.setQueryParameter("a", undefined);
assert.strictEqual(urlBuilder.getQueryParameterValue("a"), undefined);
assert.strictEqual(urlBuilder.toString(), "");
});
it(`with "a" and ""`, () => {
const urlBuilder = new URLBuilder();
urlBuilder.setQueryParameter("a", "");
assert.strictEqual(urlBuilder.getQueryParameterValue("a"), "");
assert.strictEqual(urlBuilder.toString(), "?a=");
});
});
describe("parse()", () => {
it(`with ""`, () => {
assert.strictEqual(URLBuilder.parse("").toString(), "");
@ -588,6 +706,82 @@ describe("URLBuilder", () => {
assert.strictEqual(URLBuilder.parse("https://www.bing.com/my:/path").toString(), "https://www.bing.com/my:/path");
});
});
describe("pathSubstitution()", () => {
it(`with undefined path, "{arg}" searchValue, and "cats" replaceValue`, () => {
const urlBuilder = new URLBuilder();
urlBuilder.setPath(undefined);
urlBuilder.pathSubstitution("{arg}", "cats");
assert.strictEqual(urlBuilder.getPath(), undefined);
assert.strictEqual(urlBuilder.toString(), "");
});
it(`with "" path, "{arg}" searchValue, and "cats" replaceValue`, () => {
const urlBuilder = new URLBuilder();
urlBuilder.setPath("");
urlBuilder.pathSubstitution("{arg}", "cats");
assert.strictEqual(urlBuilder.getPath(), undefined);
assert.strictEqual(urlBuilder.toString(), "");
});
it(`with "my/really/cool/path" path, "" searchValue, and "cats" replaceValue`, () => {
const urlBuilder = new URLBuilder();
urlBuilder.setPath("my/really/cool/path");
urlBuilder.pathSubstitution("", "cats");
assert.strictEqual(urlBuilder.getPath(), "my/really/cool/path");
assert.strictEqual(urlBuilder.toString(), "/my/really/cool/path");
});
it(`with "my/really/cool/path" path, "y" searchValue, and "z" replaceValue`, () => {
const urlBuilder = new URLBuilder();
urlBuilder.setPath("my/really/cool/path");
urlBuilder.pathSubstitution("y", "z");
assert.strictEqual(urlBuilder.getPath(), "mz/reallz/cool/path");
assert.strictEqual(urlBuilder.toString(), "/mz/reallz/cool/path");
});
it(`with "my/really/cool/path" path, "y" searchValue, and "" replaceValue`, () => {
const urlBuilder = new URLBuilder();
urlBuilder.setPath("my/really/cool/path");
urlBuilder.pathSubstitution("y", "");
assert.strictEqual(urlBuilder.getPath(), "m/reall/cool/path");
assert.strictEqual(urlBuilder.toString(), "/m/reall/cool/path");
});
});
describe("querySubstitution()", () => {
it(`with undefined query, "A" searchValue, and "Z" replaceValue`, () => {
const urlBuilder = new URLBuilder();
urlBuilder.setQuery(undefined);
urlBuilder.querySubstitution("A", "Z");
assert.strictEqual(urlBuilder.getQuery(), undefined);
assert.strictEqual(urlBuilder.toString(), "");
});
it(`with "A=B&C=D&E=A" query, "" searchValue, and "Z" replaceValue`, () => {
const urlBuilder = new URLBuilder();
urlBuilder.setQuery("A=B&C=D&E=A");
urlBuilder.querySubstitution("", "Z");
assert.strictEqual(urlBuilder.getQuery(), "A=B&C=D&E=A");
assert.strictEqual(urlBuilder.toString(), "?A=B&C=D&E=A");
});
it(`with "A=B&C=D&E=A" query, "A" searchValue, and "" replaceValue`, () => {
const urlBuilder = new URLBuilder();
urlBuilder.setQuery("A=B&C=D&E=A");
urlBuilder.querySubstitution("A", "");
assert.strictEqual(urlBuilder.getQuery(), "C=D&E=");
assert.strictEqual(urlBuilder.toString(), "?C=D&E=");
});
it(`with "A=B&C=D&E=A" query, "A" searchValue, and "Z" replaceValue`, () => {
const urlBuilder = new URLBuilder();
urlBuilder.setQuery("A=B&C=D&E=A");
urlBuilder.querySubstitution("A", "Z");
assert.strictEqual(urlBuilder.getQuery(), "Z=B&C=D&E=Z");
assert.strictEqual(urlBuilder.toString(), "?Z=B&C=D&E=Z");
});
});
});
describe("URLTokenizer", () => {