зеркало из https://github.com/Azure/ms-rest-js.git
Merge pull request #83 from Azure/daschult/Substitutions
Add the ability to read, modify, and substitute URL queries
This commit is contained in:
Коммит
11b713bd7c
224
lib/url.ts
224
lib/url.ts
|
@ -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", () => {
|
||||
|
|
Загрузка…
Ссылка в новой задаче