diff --git a/lib/serialization/base64UrlSpec.ts b/lib/serialization/base64UrlSpec.ts new file mode 100644 index 0000000..dbdf91e --- /dev/null +++ b/lib/serialization/base64UrlSpec.ts @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +import { TypeSpec, createValidationErrorMessage } from "./typeSpec"; + +/** + * A type specification that describes how to validate and serialize a Base64Url encoded ByteArray. + */ +const byteArraySpec: TypeSpec = { + typeName: "Base64Url", + + serialize(propertyPath: string[], value: any): string { + if (!value || typeof value.constructor.isBuffer !== "function" || !value.constructor.isBuffer(value)) { + throw new Error(createValidationErrorMessage(propertyPath, value, "a Buffer")); + } + + const result: string = value.toString("base64"); + + let trimmedResultLength = result.length; + while ((trimmedResultLength - 1) >= 0 && result[trimmedResultLength - 1] === "=") { + --trimmedResultLength; + } + + return result.substr(0, trimmedResultLength).replace(/\+/g, "-").replace(/\//g, "_"); + } +}; + +export default byteArraySpec; \ No newline at end of file diff --git a/lib/serialization/byteArraySpec.ts b/lib/serialization/byteArraySpec.ts index 47a13e8..40c078c 100644 --- a/lib/serialization/byteArraySpec.ts +++ b/lib/serialization/byteArraySpec.ts @@ -6,14 +6,14 @@ import { TypeSpec, createValidationErrorMessage } from "./typeSpec"; * A type specification that describes how to validate and serialize a ByteArray. */ const byteArraySpec: TypeSpec = { - typeName: "ByteArray(Buffer)", + typeName: "ByteArray", - serialize(propertyPath: string[], value: any): string { - if (!value || typeof value.constructor.isBuffer !== "function" || !value.constructor.isBuffer(value)) { - throw new Error(createValidationErrorMessage(propertyPath, value, "a ByteArray(Buffer)")); - } - return value.toString("base64"); + serialize(propertyPath: string[], value: any): string { + if (!value || typeof value.constructor.isBuffer !== "function" || !value.constructor.isBuffer(value)) { + throw new Error(createValidationErrorMessage(propertyPath, value, "a Buffer")); } + return value.toString("base64"); + } }; export default byteArraySpec; \ No newline at end of file diff --git a/lib/serialization/dateSpec.ts b/lib/serialization/dateSpec.ts index 6241fbf..447982a 100644 --- a/lib/serialization/dateSpec.ts +++ b/lib/serialization/dateSpec.ts @@ -6,14 +6,14 @@ import { TypeSpec, createValidationErrorMessage } from "./typeSpec"; * A type specification that describes how to validate and serialize a Date. */ const dateSpec: TypeSpec = { - typeName: "Date", + typeName: "Date", - serialize(propertyPath: string[], value: any): string { - if (!value || (!(value instanceof Date) && (typeof value !== "string" || isNaN(Date.parse(value))))) { - throw new Error(createValidationErrorMessage(propertyPath, value, `an instanceof Date or a string in ISO8601 format`)); - } - return (value instanceof Date ? value : new Date(value)).toISOString().substring(0, 10); + serialize(propertyPath: string[], value: any): string { + if (!value || (!(value instanceof Date) && (typeof value !== "string" || isNaN(Date.parse(value))))) { + throw new Error(createValidationErrorMessage(propertyPath, value, `an instanceof Date or a string in ISO8601 format`)); } + return (value instanceof Date ? value : new Date(value)).toISOString().substring(0, 10); + } }; export default dateSpec; \ No newline at end of file diff --git a/lib/serialization/dateTimeRfc1123Spec.ts b/lib/serialization/dateTimeRfc1123Spec.ts index 3a34bdf..a968d63 100644 --- a/lib/serialization/dateTimeRfc1123Spec.ts +++ b/lib/serialization/dateTimeRfc1123Spec.ts @@ -6,14 +6,14 @@ import { TypeSpec, createValidationErrorMessage } from "./typeSpec"; * A type specification that describes how to validate and serialize a Date. */ const dateTimeRfc1123Spec: TypeSpec = { - typeName: "DateTimeRFC1123", + typeName: "DateTimeRFC1123", - serialize(propertyPath: string[], value: any): string { - if (!value || (!(value instanceof Date) && (typeof value !== "string" || isNaN(Date.parse(value))))) { - throw new Error(createValidationErrorMessage(propertyPath, value, `an instanceof Date or a string in ISO8601 format`)); - } - return (value instanceof Date ? value : new Date(value)).toUTCString(); + serialize(propertyPath: string[], value: any): string { + if (!value || (!(value instanceof Date) && (typeof value !== "string" || isNaN(Date.parse(value))))) { + throw new Error(createValidationErrorMessage(propertyPath, value, `an instanceof Date or a string in ISO8601 format`)); } + return (value instanceof Date ? value : new Date(value)).toUTCString(); + } }; export default dateTimeRfc1123Spec; \ No newline at end of file diff --git a/lib/serialization/dateTimeSpec.ts b/lib/serialization/dateTimeSpec.ts index d1671cc..74f40e5 100644 --- a/lib/serialization/dateTimeSpec.ts +++ b/lib/serialization/dateTimeSpec.ts @@ -6,14 +6,14 @@ import { TypeSpec, createValidationErrorMessage } from "./typeSpec"; * A type specification that describes how to validate and serialize a Date. */ const dateTimeSpec: TypeSpec = { - typeName: "DateTime", + typeName: "DateTime", - serialize(propertyPath: string[], value: any): string { - if (!value || (!(value instanceof Date) && (typeof value !== "string" || isNaN(Date.parse(value))))) { - throw new Error(createValidationErrorMessage(propertyPath, value, `an instanceof Date or a string in ISO8601 format`)); - } - return (value instanceof Date ? value : new Date(value)).toISOString(); + serialize(propertyPath: string[], value: any): string { + if (!value || (!(value instanceof Date) && (typeof value !== "string" || isNaN(Date.parse(value))))) { + throw new Error(createValidationErrorMessage(propertyPath, value, `an instanceof Date or a string in ISO8601 format`)); } + return (value instanceof Date ? value : new Date(value)).toISOString(); + } }; export default dateTimeSpec; \ No newline at end of file diff --git a/lib/serialization/numberSpec.ts b/lib/serialization/numberSpec.ts index e7e05ea..8c4e3dd 100644 --- a/lib/serialization/numberSpec.ts +++ b/lib/serialization/numberSpec.ts @@ -6,14 +6,14 @@ import { TypeSpec, createValidationErrorMessage } from "./typeSpec"; * A type specification that describes how to validate and serialize a number. */ const numberSpec: TypeSpec = { - typeName: "number", + typeName: "number", - serialize(propertyPath: string[], value: any): number { - if (typeof value !== "number") { - throw new Error(createValidationErrorMessage(propertyPath, value, "a number")); - } - return value; + serialize(propertyPath: string[], value: any): number { + if (typeof value !== "number") { + throw new Error(createValidationErrorMessage(propertyPath, value, "a number")); } + return value; + } }; export default numberSpec; \ No newline at end of file diff --git a/lib/serialization/objectSpec.ts b/lib/serialization/objectSpec.ts index 94b1c58..ce8a28e 100644 --- a/lib/serialization/objectSpec.ts +++ b/lib/serialization/objectSpec.ts @@ -6,14 +6,14 @@ import { TypeSpec, createValidationErrorMessage } from "./typeSpec"; * A type specification that describes how to validate and serialize an object. */ const objectSpec: TypeSpec<{}> = { - typeName: "object", + typeName: "object", - serialize(propertyPath: string[], value: any): {} { - if (typeof value !== "object" || Array.isArray(value)) { - throw new Error(createValidationErrorMessage(propertyPath, value, "an object")); - } - return value; + serialize(propertyPath: string[], value: any): {} { + if (typeof value !== "object" || Array.isArray(value)) { + throw new Error(createValidationErrorMessage(propertyPath, value, "an object")); } + return value; + } }; export default objectSpec; \ No newline at end of file diff --git a/lib/serialization/streamSpec.ts b/lib/serialization/streamSpec.ts index 64d8426..1fd5613 100644 --- a/lib/serialization/streamSpec.ts +++ b/lib/serialization/streamSpec.ts @@ -7,14 +7,14 @@ import { TypeSpec, createValidationErrorMessage } from "./typeSpec"; * A type specification that describes how to validate and serialize a Stream. */ const streamSpec: TypeSpec = { - typeName: "Stream", + typeName: "Stream", - serialize(propertyPath: string[], value: any): any { - if (!isStream(value)) { - throw new Error(createValidationErrorMessage(propertyPath, value, "a Stream")); - } - return value; + serialize(propertyPath: string[], value: any): any { + if (!isStream(value)) { + throw new Error(createValidationErrorMessage(propertyPath, value, "a Stream")); } + return value; + } }; export default streamSpec; \ No newline at end of file diff --git a/lib/serialization/stringSpec.ts b/lib/serialization/stringSpec.ts index 9da44f3..9ee3bbe 100644 --- a/lib/serialization/stringSpec.ts +++ b/lib/serialization/stringSpec.ts @@ -6,14 +6,14 @@ import { TypeSpec, createValidationErrorMessage } from "./typeSpec"; * A type specification that describes how to validate and serialize a string. */ const stringSpec: TypeSpec = { - typeName: "string", + typeName: "string", - serialize(propertyPath: string[], value: any): string { - if (typeof value !== "string") { - throw new Error(createValidationErrorMessage(propertyPath, value, "a string")); - } - return value; + serialize(propertyPath: string[], value: any): string { + if (typeof value !== "string") { + throw new Error(createValidationErrorMessage(propertyPath, value, "a string")); } + return value; + } }; export default stringSpec; \ No newline at end of file diff --git a/lib/serialization/timeSpanSpec.ts b/lib/serialization/timeSpanSpec.ts index 0a0f7a9..ceed075 100644 --- a/lib/serialization/timeSpanSpec.ts +++ b/lib/serialization/timeSpanSpec.ts @@ -7,14 +7,14 @@ import { isDuration } from "moment"; * A type specification that describes how to validate and serialize a Date. */ const timeSpanSpec: TypeSpec = { - typeName: "TimeSpan", + typeName: "TimeSpan", - serialize(propertyPath: string[], value: any): string { - if (!value || (!isDuration(value) && !(value.constructor && value.constructor.name === "Duration" && typeof value.isValid === "function" && value.isValid()))) { - throw new Error(createValidationErrorMessage(propertyPath, value, `a TimeSpan/Duration`)); - } - return value.toISOString(); + serialize(propertyPath: string[], value: any): string { + if (!value || (!isDuration(value) && !(value.constructor && value.constructor.name === "Duration" && typeof value.isValid === "function" && value.isValid()))) { + throw new Error(createValidationErrorMessage(propertyPath, value, `a TimeSpan/Duration`)); } + return value.toISOString(); + } }; export default timeSpanSpec; \ No newline at end of file diff --git a/lib/serialization/typeSpec.ts b/lib/serialization/typeSpec.ts index 7fcf532..df6bd90 100644 --- a/lib/serialization/typeSpec.ts +++ b/lib/serialization/typeSpec.ts @@ -5,24 +5,24 @@ * A type specification that describes how to validate and serialize an object of a given type. */ export interface TypeSpec { - /** - * The name of the type that this TypeSpec validates. - */ - typeName: string; + /** + * The name of the type that this TypeSpec validates. + */ + typeName: string; - /** - * The values that are allowed for this TypeSpec. If this is undefined, then all values of the - * correct type are valid. - */ - allowedValues?: TSerialized[]; + /** + * The values that are allowed for this TypeSpec. If this is undefined, then all values of the + * correct type are valid. + */ + allowedValues?: TSerialized[]; - /** - * Validate and serialize the provided value into the return type T. - * @param propertyPath The path from the root of the type being serialized down to this - * property. - * @param value The value to validate and serialize. - */ - serialize(propertyPath: string[], value: any): TSerialized; + /** + * Validate and serialize the provided value into the return type T. + * @param propertyPath The path from the root of the type being serialized down to this + * property. + * @param value The value to validate and serialize. + */ + serialize(propertyPath: string[], value: any): TSerialized; } /** @@ -32,5 +32,5 @@ export interface TypeSpec { * @param expectedConditionDescription A brief description of what type was expected. */ export function createValidationErrorMessage(propertyPath: string[], value: any, expectedConditionDescription: string): string { - return `Property ${propertyPath.join(".")} with value ${JSON.stringify(value)} must be ${expectedConditionDescription}.`; + return `Property ${propertyPath.join(".")} with value ${JSON.stringify(value)} must be ${expectedConditionDescription}.`; } \ No newline at end of file diff --git a/lib/serialization/unixTimeSpec.ts b/lib/serialization/unixTimeSpec.ts index 070c25e..4dd550d 100644 --- a/lib/serialization/unixTimeSpec.ts +++ b/lib/serialization/unixTimeSpec.ts @@ -6,15 +6,15 @@ import { TypeSpec, createValidationErrorMessage } from "./typeSpec"; * A type specification that describes how to validate and serialize a Date. */ const unixTimeSpec: TypeSpec = { - typeName: "UnixTime", + typeName: "UnixTime", - serialize(propertyPath: string[], value: any): number { - if (!value || (!(value instanceof Date) && (typeof value !== "string" || isNaN(Date.parse(value))))) { - throw new Error(createValidationErrorMessage(propertyPath, value, `an instanceof Date or a string in ISO8601 format`)); - } - const valueDate: Date = (value instanceof Date ? value : new Date(value)); - return Math.floor(valueDate.getTime() / 1000); + serialize(propertyPath: string[], value: any): number { + if (!value || (!(value instanceof Date) && (typeof value !== "string" || isNaN(Date.parse(value))))) { + throw new Error(createValidationErrorMessage(propertyPath, value, `an instanceof Date or a string in ISO8601 format`)); } + const valueDate: Date = (value instanceof Date ? value : new Date(value)); + return Math.floor(valueDate.getTime() / 1000); + } }; export default unixTimeSpec; \ No newline at end of file diff --git a/lib/serialization/uuidSpec.ts b/lib/serialization/uuidSpec.ts index ecebc2f..0960222 100644 --- a/lib/serialization/uuidSpec.ts +++ b/lib/serialization/uuidSpec.ts @@ -7,14 +7,14 @@ import * as utils from "../util/utils"; * A type specification that describes how to validate and serialize a UUID. */ const uuidSpec: TypeSpec = { - typeName: "UUID", + typeName: "UUID", - serialize(propertyPath: string[], value: any): string { - if (typeof value !== "string" || !utils.isValidUuid(value)) { - throw new Error(createValidationErrorMessage(propertyPath, value, "a UUID")); - } - return value; + serialize(propertyPath: string[], value: any): string { + if (typeof value !== "string" || !utils.isValidUuid(value)) { + throw new Error(createValidationErrorMessage(propertyPath, value, "a UUID")); } + return value; + } }; export default uuidSpec; \ No newline at end of file diff --git a/test/serialization/base64UrlSpecTests.ts b/test/serialization/base64UrlSpecTests.ts new file mode 100644 index 0000000..330d93b --- /dev/null +++ b/test/serialization/base64UrlSpecTests.ts @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +import * as assert from "assert"; +import base64UrlSpec from "../../lib/serialization/base64UrlSpec"; + +describe("base64UrlSpec", () => { + it("should have \"Base64Url\" for its typeName property", () => { + assert.strictEqual("Base64Url", base64UrlSpec.typeName); + }); + + describe("serialize()", () => { + it("should throw an error when given undefined", () => { + try { + base64UrlSpec.serialize(["a", "property", "path"], undefined); + assert.fail("Expected an error to be thrown."); + } catch (error) { + assert.strictEqual(error.message, "Property a.property.path with value undefined must be a Buffer."); + } + }); + + it("should throw an error when given 5", () => { + try { + base64UrlSpec.serialize(["another", "property", "path"], 5); + assert.fail("Expected an error to be thrown."); + } catch (error) { + assert.strictEqual(error.message, "Property another.property.path with value 5 must be a Buffer."); + } + }); + + it("should throw an error when given {}", () => { + try { + base64UrlSpec.serialize(["another", "property", "path"], {}); + assert.fail("Expected an error to be thrown."); + } catch (error) { + assert.strictEqual(error.message, "Property another.property.path with value {} must be a Buffer."); + } + }); + + it("should return a base64 encoded string with no error when given a Buffer", () => { + assert.strictEqual(base64UrlSpec.serialize(["this", "one", "works"], new Buffer([0, 1, 2, 3, 4])), "AAECAwQ"); + }); + }); +}); \ No newline at end of file diff --git a/test/serialization/byteArraySpecTests.ts b/test/serialization/byteArraySpecTests.ts index 0a27d25..5ee5917 100644 --- a/test/serialization/byteArraySpecTests.ts +++ b/test/serialization/byteArraySpecTests.ts @@ -4,8 +4,8 @@ import * as assert from "assert"; import byteArraySpec from "../../lib/serialization/byteArraySpec"; describe("byteArraySpec", () => { - it("should have \"ByteArray(Buffer)\" for its typeName property", () => { - assert.strictEqual("ByteArray(Buffer)", byteArraySpec.typeName); + it("should have \"ByteArray\" for its typeName property", () => { + assert.strictEqual("ByteArray", byteArraySpec.typeName); }); describe("serialize()", () => { @@ -14,7 +14,7 @@ describe("byteArraySpec", () => { byteArraySpec.serialize(["a", "property", "path"], undefined); assert.fail("Expected an error to be thrown."); } catch (error) { - assert.strictEqual(error.message, "Property a.property.path with value undefined must be a ByteArray(Buffer)."); + assert.strictEqual(error.message, "Property a.property.path with value undefined must be a Buffer."); } }); @@ -23,7 +23,7 @@ describe("byteArraySpec", () => { byteArraySpec.serialize(["another", "property", "path"], 5); assert.fail("Expected an error to be thrown."); } catch (error) { - assert.strictEqual(error.message, "Property another.property.path with value 5 must be a ByteArray(Buffer)."); + assert.strictEqual(error.message, "Property another.property.path with value 5 must be a Buffer."); } }); @@ -32,7 +32,7 @@ describe("byteArraySpec", () => { byteArraySpec.serialize(["another", "property", "path"], {}); assert.fail("Expected an error to be thrown."); } catch (error) { - assert.strictEqual(error.message, "Property another.property.path with value {} must be a ByteArray(Buffer)."); + assert.strictEqual(error.message, "Property another.property.path with value {} must be a Buffer."); } });