ms-rest-js/lib/serializer.ts

895 строки
33 KiB
TypeScript
Исходник Обычный вид История

2017-09-13 19:42:16 +03:00
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as base64 from "./util/base64";
import * as utils from "./util/utils";
2017-09-13 19:42:16 +03:00
export class Serializer {
constructor(public readonly modelMappers: { [key: string]: any } = {}, public readonly isXML?: boolean) { }
2017-09-13 19:42:16 +03:00
validateConstraints(mapper: Mapper, value: any, objectName: string): void {
2018-06-19 23:09:51 +03:00
const failValidation = (constraintName: keyof MapperConstraints, constraintValue: any) => {
throw new Error(`"${objectName}" with value "${value}" should satisfy the constraint "${constraintName}": ${constraintValue}.`);
};
if (mapper.constraints && (value != undefined)) {
const {
ExclusiveMaximum,
ExclusiveMinimum,
InclusiveMaximum,
InclusiveMinimum,
MaxItems,
MaxLength,
MinItems,
MinLength,
MultipleOf,
Pattern,
UniqueItems
} = mapper.constraints;
if (ExclusiveMaximum != undefined && value >= ExclusiveMaximum) {
failValidation("ExclusiveMaximum", ExclusiveMaximum);
2018-06-19 23:09:51 +03:00
}
if (ExclusiveMinimum != undefined && value <= ExclusiveMinimum) {
failValidation("ExclusiveMinimum", ExclusiveMinimum);
2018-06-19 23:09:51 +03:00
}
if (InclusiveMaximum != undefined && value > InclusiveMaximum) {
failValidation("InclusiveMaximum", InclusiveMaximum);
2018-06-19 23:09:51 +03:00
}
if (InclusiveMinimum != undefined && value < InclusiveMinimum) {
failValidation("InclusiveMinimum", InclusiveMinimum);
2018-06-19 23:09:51 +03:00
}
if (MaxItems != undefined && value.length > MaxItems) {
failValidation("MaxItems", MaxItems);
2018-06-19 23:09:51 +03:00
}
if (MaxLength != undefined && value.length > MaxLength) {
failValidation("MaxLength", MaxLength);
2018-06-19 23:09:51 +03:00
}
if (MinItems != undefined && value.length < MinItems) {
failValidation("MinItems", MinItems);
2018-06-19 23:09:51 +03:00
}
if (MinLength != undefined && value.length < MinLength) {
failValidation("MinLength", MinLength);
2018-06-19 23:09:51 +03:00
}
if (MultipleOf != undefined && value % MultipleOf !== 0) {
failValidation("MultipleOf", MultipleOf);
2018-06-19 23:09:51 +03:00
}
if (Pattern && value.match(Pattern) === null) {
failValidation("Pattern", Pattern);
2018-06-19 23:09:51 +03:00
}
2018-06-22 06:45:36 +03:00
if (UniqueItems && value.some((item: any, i: number, ar: Array<any>) => ar.indexOf(item) !== i)) {
failValidation("UniqueItems", UniqueItems);
2017-10-17 22:10:36 +03:00
}
2017-09-13 19:42:16 +03:00
}
}
/**
* Serialize the given object based on its metadata defined in the mapper
*
* @param {Mapper} mapper The mapper which defines the metadata of the serializable object
*
* @param {object|string|Array|number|boolean|Date|stream} object A valid Javascript object to be serialized
*
* @param {string} objectName Name of the serialized object
*
* @returns {object|string|Array|number|boolean|Date|stream} A valid serialized Javascript object
*/
2018-06-14 23:30:31 +03:00
serialize(mapper: Mapper, object: any, objectName?: string): any {
2017-09-13 19:42:16 +03:00
let payload: any = {};
const mapperType = mapper.type.name as string;
2018-06-14 23:30:31 +03:00
if (!objectName) {
objectName = mapper.serializedName!;
2017-09-13 19:42:16 +03:00
}
2018-06-14 23:30:31 +03:00
if (mapperType.match(/^Sequence$/ig) !== null) {
payload = [];
2017-09-13 19:42:16 +03:00
}
2018-06-14 23:30:31 +03:00
2018-06-28 22:17:31 +03:00
if (object == undefined && (mapper.defaultValue != undefined || mapper.isConstant)) {
object = mapper.defaultValue;
}
// This table of allowed values should help explain
// the mapper.required and mapper.nullable properties.
// X means "neither undefined or null are allowed".
// || required
// || true | false
// nullable || ==========================
// true || null | undefined/null
// false || X | undefined
// undefined || X | undefined/null
const { required, nullable } = mapper;
if (required && nullable && object === undefined) {
throw new Error(`${objectName} cannot be undefined.`);
}
if (required && !nullable && object == undefined) {
throw new Error(`${objectName} cannot be null or undefined.`);
}
if (!required && nullable === false && object === null) {
throw new Error(`${objectName} cannot be null.`);
2018-06-14 23:30:31 +03:00
}
if (object == undefined) {
payload = object;
2018-06-14 23:30:31 +03:00
} else {
// Validate Constraints if any
this.validateConstraints(mapper, object, objectName);
if (mapperType.match(/^any$/ig) !== null) {
payload = object;
} else if (mapperType.match(/^(Number|String|Boolean|Object|Stream|Uuid)$/ig) !== null) {
payload = serializeBasicTypes(mapperType, objectName, object);
2018-06-14 23:30:31 +03:00
} else if (mapperType.match(/^Enum$/ig) !== null) {
const enumMapper: EnumMapper = mapper as EnumMapper;
payload = serializeEnumType(objectName, enumMapper.type.allowedValues, object);
2018-06-14 23:30:31 +03:00
} else if (mapperType.match(/^(Date|DateTime|TimeSpan|DateTimeRfc1123|UnixTime)$/ig) !== null) {
payload = serializeDateTypes(mapperType, object, objectName);
2018-06-14 23:30:31 +03:00
} else if (mapperType.match(/^ByteArray$/ig) !== null) {
payload = serializeByteArrayType(objectName, object);
2018-06-14 23:30:31 +03:00
} else if (mapperType.match(/^Base64Url$/ig) !== null) {
payload = serializeBase64UrlType(objectName, object);
2018-06-14 23:30:31 +03:00
} else if (mapperType.match(/^Sequence$/ig) !== null) {
payload = serializeSequenceType(this, mapper as SequenceMapper, object, objectName);
2018-06-14 23:30:31 +03:00
} else if (mapperType.match(/^Dictionary$/ig) !== null) {
payload = serializeDictionaryType(this, mapper as DictionaryMapper, object, objectName);
2018-06-14 23:30:31 +03:00
} else if (mapperType.match(/^Composite$/ig) !== null) {
payload = serializeCompositeType(this, mapper as CompositeMapper, object, objectName);
2018-06-14 23:30:31 +03:00
}
2017-09-13 19:42:16 +03:00
}
return payload;
}
/**
* Deserialize the given object based on its metadata defined in the mapper
*
* @param {object} mapper The mapper which defines the metadata of the serializable object
*
* @param {object|string|Array|number|boolean|Date|stream} responseBody A valid Javascript entity to be deserialized
*
* @param {string} objectName Name of the deserialized object
*
* @returns {object|string|Array|number|boolean|Date|stream} A valid deserialized Javascript object
*/
deserialize(mapper: Mapper, responseBody: any, objectName: string): any {
if (responseBody == undefined) {
if (this.isXML && mapper.type.name === "Sequence" && !mapper.xmlIsWrapped) {
// Edge case for empty XML non-wrapped lists. xml2js can't distinguish
// between the list being empty versus being missing,
// so let's do the more user-friendly thing and return an empty list.
responseBody = [];
}
return responseBody;
}
2017-09-13 19:42:16 +03:00
let payload: any;
const mapperType = mapper.type.name;
2018-03-29 00:25:36 +03:00
if (!objectName) {
objectName = mapper.serializedName!;
2018-03-29 00:25:36 +03:00
}
2017-09-13 19:42:16 +03:00
if (mapperType.match(/^Number$/ig) !== null) {
payload = parseFloat(responseBody);
if (isNaN(payload)) {
payload = responseBody;
}
} else if (mapperType.match(/^Boolean$/ig) !== null) {
if (responseBody === "true") {
payload = true;
} else if (responseBody === "false") {
payload = false;
} else {
payload = responseBody;
}
} else if (mapperType.match(/^(String|Enum|Object|Stream|Uuid|TimeSpan|any)$/ig) !== null) {
2017-09-13 19:42:16 +03:00
payload = responseBody;
} else if (mapperType.match(/^(Date|DateTime|DateTimeRfc1123)$/ig) !== null) {
payload = new Date(responseBody);
} else if (mapperType.match(/^UnixTime$/ig) !== null) {
payload = unixTimeToDate(responseBody);
2017-09-13 19:42:16 +03:00
} else if (mapperType.match(/^ByteArray$/ig) !== null) {
payload = base64.decodeString(responseBody);
2017-09-13 19:42:16 +03:00
} else if (mapperType.match(/^Base64Url$/ig) !== null) {
payload = base64UrlToByteArray(responseBody);
2017-09-13 19:42:16 +03:00
} else if (mapperType.match(/^Sequence$/ig) !== null) {
payload = deserializeSequenceType(this, mapper as SequenceMapper, responseBody, objectName);
2017-09-13 19:42:16 +03:00
} else if (mapperType.match(/^Dictionary$/ig) !== null) {
payload = deserializeDictionaryType(this, mapper as DictionaryMapper, responseBody, objectName);
2017-09-13 19:42:16 +03:00
} else if (mapperType.match(/^Composite$/ig) !== null) {
payload = deserializeCompositeType(this, mapper as CompositeMapper, responseBody, objectName);
2017-09-13 19:42:16 +03:00
}
2018-03-29 00:25:36 +03:00
if (mapper.isConstant) {
payload = mapper.defaultValue;
}
2017-09-13 19:42:16 +03:00
return payload;
}
}
2017-09-13 19:42:16 +03:00
function trimEnd(str: string, ch: string) {
let len = str.length;
while ((len - 1) >= 0 && str[len - 1] === ch) {
--len;
}
return str.substr(0, len);
}
function bufferToBase64Url(buffer: any): string | undefined {
if (!buffer) {
return undefined;
}
if (!(buffer instanceof Uint8Array)) {
throw new Error(`Please provide an input of type Uint8Array for converting to Base64Url.`);
}
// Uint8Array to Base64.
const str = base64.encodeByteArray(buffer);
// Base64 to Base64Url.
return trimEnd(str, "=").replace(/\+/g, "-").replace(/\//g, "_");
}
function base64UrlToByteArray(str: string): Uint8Array | undefined {
if (!str) {
return undefined;
}
if (str && typeof str.valueOf() !== "string") {
throw new Error("Please provide an input of type string for converting to Uint8Array");
2017-09-13 19:42:16 +03:00
}
// Base64Url to Base64.
str = str.replace(/\-/g, "+").replace(/\_/g, "/");
// Base64 to Uint8Array.
return base64.decodeString(str);
}
2017-09-13 19:42:16 +03:00
function splitSerializeName(prop: string | undefined): string[] {
const classes: string[] = [];
let partialclass = "";
2018-09-05 05:02:45 +03:00
if (prop) {
const subwords = prop.split(".");
2017-09-13 19:42:16 +03:00
2018-09-05 05:02:45 +03:00
for (const item of subwords) {
if (item.charAt(item.length - 1) === "\\") {
partialclass += item.substr(0, item.length - 1) + ".";
} else {
partialclass += item;
classes.push(partialclass);
partialclass = "";
}
2017-09-13 19:42:16 +03:00
}
}
return classes;
}
function dateToUnixTime(d: string | Date): number | undefined {
if (!d) {
return undefined;
}
if (typeof d.valueOf() === "string") {
d = new Date(d as string);
}
return Math.floor((d as Date).getTime() / 1000);
}
2017-09-13 19:42:16 +03:00
function unixTimeToDate(n: number): Date | undefined {
if (!n) {
return undefined;
}
return new Date(n * 1000);
}
function serializeBasicTypes(typeName: string, objectName: string, value: any): any {
if (value !== null && value !== undefined) {
if (typeName.match(/^Number$/ig) !== null) {
if (typeof value !== "number") {
throw new Error(`${objectName} with value ${value} must be of type number.`);
2017-09-13 19:42:16 +03:00
}
} else if (typeName.match(/^String$/ig) !== null) {
if (typeof value.valueOf() !== "string") {
throw new Error(`${objectName} with value "${value}" must be of type string.`);
2017-09-13 19:42:16 +03:00
}
} else if (typeName.match(/^Uuid$/ig) !== null) {
if (!(typeof value.valueOf() === "string" && utils.isValidUuid(value))) {
throw new Error(`${objectName} with value "${value}" must be of type string and a valid uuid.`);
}
} else if (typeName.match(/^Boolean$/ig) !== null) {
if (typeof value !== "boolean") {
throw new Error(`${objectName} with value ${value} must be of type boolean.`);
2017-09-13 19:42:16 +03:00
}
} else if (typeName.match(/^Stream$/ig) !== null) {
const objectType = typeof value;
if (objectType !== "string" &&
objectType !== "function" &&
!(value instanceof ArrayBuffer) &&
!ArrayBuffer.isView(value) &&
!(typeof Blob === "function" && value instanceof Blob)) {
throw new Error(`${objectName} must be a string, Blob, ArrayBuffer, ArrayBufferView, or a function returning NodeJS.ReadableStream.`);
2017-09-13 19:42:16 +03:00
}
}
}
return value;
}
function serializeEnumType(objectName: string, allowedValues: Array<any>, value: any): any {
if (!allowedValues) {
throw new Error(`Please provide a set of allowedValues to validate ${objectName} as an Enum Type.`);
}
const isPresent = allowedValues.some((item) => {
if (typeof item.valueOf() === "string") {
return item.toLowerCase() === value.toLowerCase();
}
return item === value;
});
if (!isPresent) {
throw new Error(`${value} is not a valid value for ${objectName}. The valid values are: ${JSON.stringify(allowedValues)}.`);
}
return value;
}
function serializeByteArrayType(objectName: string, value: any): any {
2018-06-22 06:45:36 +03:00
if (value != undefined) {
if (!(value instanceof Uint8Array)) {
throw new Error(`${objectName} must be of type Uint8Array.`);
}
value = base64.encodeByteArray(value);
}
return value;
}
function serializeBase64UrlType(objectName: string, value: any): any {
2018-06-22 06:45:36 +03:00
if (value != undefined) {
if (!(value instanceof Uint8Array)) {
throw new Error(`${objectName} must be of type Uint8Array.`);
}
value = bufferToBase64Url(value);
}
return value;
}
function serializeDateTypes(typeName: string, value: any, objectName: string) {
if (value != undefined) {
if (typeName.match(/^Date$/ig) !== null) {
if (!(value instanceof Date ||
(typeof value.valueOf() === "string" && !isNaN(Date.parse(value))))) {
throw new Error(`${objectName} must be an instanceof Date or a string in ISO8601 format.`);
}
value = (value instanceof Date) ? value.toISOString().substring(0, 10) : new Date(value).toISOString().substring(0, 10);
} else if (typeName.match(/^DateTime$/ig) !== null) {
if (!(value instanceof Date ||
(typeof value.valueOf() === "string" && !isNaN(Date.parse(value))))) {
throw new Error(`${objectName} must be an instanceof Date or a string in ISO8601 format.`);
}
value = (value instanceof Date) ? value.toISOString() : new Date(value).toISOString();
} else if (typeName.match(/^DateTimeRfc1123$/ig) !== null) {
if (!(value instanceof Date ||
(typeof value.valueOf() === "string" && !isNaN(Date.parse(value))))) {
throw new Error(`${objectName} must be an instanceof Date or a string in RFC-1123 format.`);
}
value = (value instanceof Date) ? value.toUTCString() : new Date(value).toUTCString();
} else if (typeName.match(/^UnixTime$/ig) !== null) {
if (!(value instanceof Date ||
(typeof value.valueOf() === "string" && !isNaN(Date.parse(value))))) {
throw new Error(`${objectName} must be an instanceof Date or a string in RFC-1123/ISO8601 format ` +
`for it to be serialized in UnixTime/Epoch format.`);
}
value = dateToUnixTime(value);
} else if (typeName.match(/^TimeSpan$/ig) !== null) {
if (!utils.isDuration(value)) {
throw new Error(`${objectName} must be a string in ISO 8601 format. Instead was "${value}".`);
}
value = value;
}
}
return value;
}
function serializeSequenceType(serializer: Serializer, mapper: SequenceMapper, object: any, objectName: string) {
if (!Array.isArray(object)) {
throw new Error(`${objectName} must be of type Array.`);
}
2018-06-22 06:45:36 +03:00
const elementType = mapper.type.element;
if (!elementType || typeof elementType !== "object") {
throw new Error(`element" metadata for an Array must be defined in the ` +
`mapper and it must of type "object" in ${objectName}.`);
}
const tempArray = [];
for (let i = 0; i < object.length; i++) {
2018-06-22 06:45:36 +03:00
tempArray[i] = serializer.serialize(elementType, object[i], objectName);
}
return tempArray;
}
function serializeDictionaryType(serializer: Serializer, mapper: DictionaryMapper, object: any, objectName: string) {
if (typeof object !== "object") {
throw new Error(`${objectName} must be of type object.`);
}
2018-06-22 06:45:36 +03:00
const valueType = mapper.type.value;
if (!valueType || typeof valueType !== "object") {
throw new Error(`"value" metadata for a Dictionary must be defined in the ` +
`mapper and it must of type "object" in ${objectName}.`);
}
const tempDictionary: { [key: string]: any } = {};
2018-06-19 23:09:51 +03:00
for (const key of Object.keys(object)) {
2018-06-22 06:45:36 +03:00
tempDictionary[key] = serializer.serialize(valueType, object[key], objectName + "." + key);
}
return tempDictionary;
}
/**
* Resolves a composite mapper's modelProperties.
* @param serializer the serializer containing the entire set of mappers
* @param mapper the composite mapper to resolve
*/
function resolveModelProperties(serializer: Serializer, mapper: CompositeMapper, objectName: string): { [propertyName: string]: Mapper } {
let modelProps = mapper.type.modelProperties;
if (!modelProps) {
const className = mapper.type.className;
if (!className) {
throw new Error(`Class name for model "${objectName}" is not provided in the mapper "${JSON.stringify(mapper, undefined, 2)}".`);
}
const modelMapper = serializer.modelMappers[className];
if (!modelMapper) {
throw new Error(`mapper() cannot be null or undefined for model "${className}".`);
}
modelProps = modelMapper.type.modelProperties;
if (!modelProps) {
throw new Error(`modelProperties cannot be null or undefined in the ` +
`mapper "${JSON.stringify(modelMapper)}" of type "${className}" for object "${objectName}".`);
}
}
return modelProps;
}
function serializeCompositeType(serializer: Serializer, mapper: CompositeMapper, object: any, objectName: string) {
// check for polymorphic discriminator
if (mapper.type.polymorphicDiscriminator) {
mapper = getPolymorphicMapper(serializer, mapper, object, objectName, "serialize");
}
2018-06-28 22:17:31 +03:00
if (object != undefined) {
const payload: any = {};
const modelProps = resolveModelProperties(serializer, mapper, objectName);
for (const key of Object.keys(modelProps)) {
const propertyMapper = modelProps[key];
if (propertyMapper.readOnly) {
continue;
}
let propName: string | undefined;
let parentObject: any = payload;
if (serializer.isXML) {
if (propertyMapper.xmlIsWrapped) {
propName = propertyMapper.xmlName;
} else {
propName = propertyMapper.xmlElementName || propertyMapper.xmlName;
}
} else {
const paths = splitSerializeName(propertyMapper.serializedName!);
propName = paths.pop();
2017-09-13 19:42:16 +03:00
for (const pathName of paths) {
const childObject = parentObject[pathName];
2018-06-28 22:17:31 +03:00
if ((childObject == undefined) && (object[key] != undefined)) {
parentObject[pathName] = {};
}
parentObject = parentObject[pathName];
}
2017-09-13 19:42:16 +03:00
}
2018-06-28 22:17:31 +03:00
if (parentObject != undefined) {
const propertyObjectName = propertyMapper.serializedName !== ""
? objectName + "." + propertyMapper.serializedName
: objectName;
const serializedValue = serializer.serialize(propertyMapper, object[key], propertyObjectName);
2018-06-28 22:17:31 +03:00
if (serializedValue !== undefined && propName != undefined) {
if (propertyMapper.xmlIsAttribute) {
// $ is the key attributes are kept under in xml2js.
// This keeps things simple while preventing name collision
// with names in user documents.
parentObject.$ = parentObject.$ || {};
parentObject.$[propName] = serializedValue;
} else if (propertyMapper.xmlIsWrapped) {
parentObject[propName] = { [propertyMapper.xmlElementName!]: serializedValue };
} else {
parentObject[propName] = serializedValue;
}
}
2017-09-13 19:42:16 +03:00
}
}
const additionalPropertiesMapper = mapper.type.additionalProperties;
if (additionalPropertiesMapper) {
const propNames = Object.keys(modelProps);
for (const clientPropName in object) {
const isAdditionalProperty = propNames.every(pn => pn !== clientPropName);
if (isAdditionalProperty) {
payload[clientPropName] = serializer.serialize(additionalPropertiesMapper, object[clientPropName], objectName + '["' + clientPropName + '"]');
}
}
}
return payload;
}
return object;
}
function deserializeCompositeType(serializer: Serializer, mapper: CompositeMapper, responseBody: any, objectName: string): any {
if (mapper.type.polymorphicDiscriminator) {
mapper = getPolymorphicMapper(serializer, mapper, responseBody, objectName, "deserialize");
}
const modelProps = resolveModelProperties(serializer, mapper, objectName);
let instance: { [key: string]: any } = {};
for (const key of Object.keys(modelProps)) {
const propertyMapper = modelProps[key];
const { serializedName, xmlName, xmlElementName } = propertyMapper;
let propertyObjectName = objectName;
2018-09-05 05:02:45 +03:00
if (serializedName !== "" && serializedName !== undefined) {
propertyObjectName = objectName + "." + serializedName;
}
const headerCollectionPrefix = (propertyMapper as DictionaryMapper).headerCollectionPrefix;
if (headerCollectionPrefix) {
const dictionary: any = {};
for (const headerKey of Object.keys(responseBody)) {
if (headerKey.startsWith(headerCollectionPrefix)) {
2018-06-19 20:47:32 +03:00
dictionary[headerKey.substring(headerCollectionPrefix.length)] = serializer.deserialize((propertyMapper as DictionaryMapper).type.value, responseBody[headerKey], propertyObjectName);
}
}
instance[key] = dictionary;
} else if (serializer.isXML) {
if (propertyMapper.xmlIsAttribute && responseBody.$) {
instance[key] = serializer.deserialize(propertyMapper, responseBody.$[xmlName!], propertyObjectName);
2017-09-13 19:42:16 +03:00
} else {
const propertyName = xmlElementName || xmlName || serializedName;
let unwrappedProperty = responseBody[propertyName!];
if (propertyMapper.xmlIsWrapped) {
unwrappedProperty = responseBody[xmlName!];
unwrappedProperty = unwrappedProperty && unwrappedProperty[xmlElementName!];
const isEmptyWrappedList = unwrappedProperty === undefined;
if (isEmptyWrappedList) {
unwrappedProperty = [];
}
}
instance[key] = serializer.deserialize(propertyMapper, unwrappedProperty, propertyObjectName);
2017-09-13 19:42:16 +03:00
}
} else {
const paths = splitSerializeName(modelProps[key].serializedName!);
// deserialize the property if it is present in the provided responseBody instance
let propertyInstance;
let res = responseBody;
// traversing the object step by step.
for (const item of paths) {
if (!res) break;
res = res[item];
}
propertyInstance = res;
let serializedValue;
// paging
if (Array.isArray(responseBody[key]) && modelProps[key].serializedName === "") {
propertyInstance = responseBody[key];
instance = serializer.deserialize(propertyMapper, propertyInstance, propertyObjectName);
2018-06-28 22:17:31 +03:00
} else if (propertyInstance !== undefined) {
serializedValue = serializer.deserialize(propertyMapper, propertyInstance, propertyObjectName);
instance[key] = serializedValue;
2017-09-13 19:42:16 +03:00
}
}
}
const additionalPropertiesMapper = mapper.type.additionalProperties;
if (additionalPropertiesMapper) {
const isAdditionalProperty = (responsePropName: string) => {
for (const clientPropName in modelProps) {
const paths = splitSerializeName(modelProps[clientPropName].serializedName);
if (paths[0] === responsePropName) {
return false;
}
}
return true;
};
for (const responsePropName in responseBody) {
if (isAdditionalProperty(responsePropName)) {
instance[responsePropName] = serializer.deserialize(additionalPropertiesMapper, responseBody[responsePropName], objectName + '["' + responsePropName + '"]');
}
}
}
return instance;
}
function deserializeDictionaryType(serializer: Serializer, mapper: DictionaryMapper, responseBody: any, objectName: string): any {
/*jshint validthis: true */
2018-06-22 02:26:46 +03:00
const value = mapper.type.value;
if (!value || typeof value !== "object") {
throw new Error(`"value" metadata for a Dictionary must be defined in the ` +
`mapper and it must of type "object" in ${objectName}`);
}
if (responseBody) {
const tempDictionary: { [key: string]: any } = {};
2018-06-22 02:26:46 +03:00
for (const key of Object.keys(responseBody)) {
tempDictionary[key] = serializer.deserialize(value, responseBody[key], objectName);
}
return tempDictionary;
}
return responseBody;
}
function deserializeSequenceType(serializer: Serializer, mapper: SequenceMapper, responseBody: any, objectName: string): any {
/*jshint validthis: true */
2018-06-22 02:26:46 +03:00
const element = mapper.type.element;
if (!element || typeof element !== "object") {
throw new Error(`element" metadata for an Array must be defined in the ` +
`mapper and it must of type "object" in ${objectName}`);
2017-09-13 19:42:16 +03:00
}
if (responseBody) {
if (!Array.isArray(responseBody)) {
// xml2js will interpret a single element array as just the element, so force it to be an array
responseBody = [responseBody];
}
const tempArray = [];
for (let i = 0; i < responseBody.length; i++) {
2018-06-22 02:26:46 +03:00
tempArray[i] = serializer.deserialize(element, responseBody[i], objectName);
}
return tempArray;
}
return responseBody;
}
function getPolymorphicMapper(serializer: Serializer, mapper: CompositeMapper, object: any, objectName: string, mode: string): CompositeMapper {
// check for polymorphic discriminator
// Until version 1.15.1, "polymorphicDiscriminator" in the mapper was a string. This method was not effective when the
// polymorphicDiscriminator property had a dot in it"s name. So we have comeup with a desgin where polymorphicDiscriminator
// will be an object that contains the clientName (normalized property name, ex: "odatatype") and
// the serializedName (ex: "odata.type") (We do not escape the dots with double backslash in this case as it is not required)
// Thus when serializing, the user will give us an object which will contain the normalizedProperty hence we will lookup
2018-06-28 22:17:31 +03:00
// the clientName of the polymorphicDiscriminator in the mapper and during deserialization from the responseBody we will
// lookup the serializedName of the polymorphicDiscriminator in the mapper. This will help us in selecting the correct mapper
// for the model that needs to be serializes or deserialized.
// We need this routing for backwards compatibility. This will absorb the breaking change in the mapper and allow new versions
// of the runtime to work seamlessly with older version (>= 0.17.0-Nightly20161008) of Autorest generated node.js clients.
2018-06-22 02:26:46 +03:00
const polymorphicDiscriminator = mapper.type.polymorphicDiscriminator;
if (polymorphicDiscriminator) {
if (typeof polymorphicDiscriminator.valueOf() === "string") {
return getPolymorphicMapperStringVersion(serializer, mapper, object, objectName);
2018-06-22 02:26:46 +03:00
} else if (polymorphicDiscriminator instanceof Object) {
return getPolymorphicMapperObjectVersion(serializer, mapper, object, objectName, mode);
} else {
throw new Error(`The polymorphicDiscriminator for "${objectName}" is neither a string nor an object.`);
}
}
return mapper;
}
// processes new version of the polymorphicDiscriminator in the mapper.
function getPolymorphicMapperObjectVersion(serializer: Serializer, mapper: CompositeMapper, object: any, objectName: string, mode: string): CompositeMapper {
// check for polymorphic discriminator
let polymorphicPropertyName = "";
if (mode === "serialize") {
polymorphicPropertyName = "clientName";
} else if (mode === "deserialize") {
polymorphicPropertyName = "serializedName";
} else {
throw new Error(`The given mode "${mode}" for getting the polymorphic mapper for "${objectName}" is inavlid.`);
}
const discriminatorAsObject: PolymorphicDiscriminator = mapper.type.polymorphicDiscriminator as PolymorphicDiscriminator;
if (discriminatorAsObject &&
discriminatorAsObject[polymorphicPropertyName] !== null &&
discriminatorAsObject[polymorphicPropertyName] !== undefined) {
if (object === null || object === undefined) {
throw new Error(`${objectName}" cannot be null or undefined. ` +
`"${discriminatorAsObject[polymorphicPropertyName]}" is the ` +
2018-06-28 22:17:31 +03:00
`polymorphicDiscriminator is a required property.`);
}
if (object[discriminatorAsObject[polymorphicPropertyName]] === null ||
object[discriminatorAsObject[polymorphicPropertyName]] === undefined) {
throw new Error(`No discriminator field "${discriminatorAsObject[polymorphicPropertyName]}" was found in "${objectName}".`);
}
let indexDiscriminator = undefined;
if (object[discriminatorAsObject[polymorphicPropertyName]] === mapper.type.uberParent) {
indexDiscriminator = object[discriminatorAsObject[polymorphicPropertyName]];
} else {
indexDiscriminator = mapper.type.uberParent + "." + object[discriminatorAsObject[polymorphicPropertyName]];
}
if (serializer.modelMappers && serializer.modelMappers.discriminators[indexDiscriminator]) {
mapper = serializer.modelMappers.discriminators[indexDiscriminator];
}
}
return mapper;
}
// processes old version of the polymorphicDiscriminator in the mapper.
function getPolymorphicMapperStringVersion(serializer: Serializer, mapper: CompositeMapper, object: any, objectName: string): CompositeMapper {
// check for polymorphic discriminator
const discriminatorAsString: string = mapper.type.polymorphicDiscriminator as string;
2018-06-28 22:17:31 +03:00
if (discriminatorAsString != undefined) {
if (object == undefined) {
throw new Error(`${objectName}" cannot be null or undefined. "${discriminatorAsString}" is the ` +
2018-06-28 22:17:31 +03:00
`polymorphicDiscriminator is a required property.`);
}
2018-06-28 22:17:31 +03:00
if (object[discriminatorAsString] == undefined) {
throw new Error(`No discriminator field "${discriminatorAsString}" was found in "${objectName}".`);
}
let indexDiscriminator = undefined;
if (object[discriminatorAsString] === mapper.type.uberParent) {
indexDiscriminator = object[discriminatorAsString];
} else {
indexDiscriminator = mapper.type.uberParent + "." + object[discriminatorAsString];
}
if (serializer.modelMappers && serializer.modelMappers.discriminators[indexDiscriminator]) {
mapper = serializer.modelMappers.discriminators[indexDiscriminator];
}
}
return mapper;
2017-09-13 19:42:16 +03:00
}
export interface MapperConstraints {
InclusiveMaximum?: number;
ExclusiveMaximum?: number;
InclusiveMinimum?: number;
ExclusiveMinimum?: number;
MaxLength?: number;
MinLength?: number;
Pattern?: RegExp;
2017-09-13 19:42:16 +03:00
MaxItems?: number;
MinItems?: number;
UniqueItems?: true;
MultipleOf?: number;
}
export type MapperType = SimpleMapperType | CompositeMapperType | SequenceMapperType | DictionaryMapperType | EnumMapperType;
export interface SimpleMapperType {
name: "Base64Url"
| "Boolean"
| "ByteArray"
| "Date"
| "DateTime"
| "DateTimeRfc1123"
| "Object"
| "Stream"
| "String"
| "TimeSpan"
| "UnixTime"
| "Uuid"
| "Number"
| "any";
}
export interface CompositeMapperType {
name: "Composite";
// Only one of the two below properties should be present.
// Use className to reference another type definition,
// and use modelProperties/additionalProperties when the reference to the other type has been resolved.
className?: string;
modelProperties?: { [propertyName: string]: Mapper };
additionalProperties?: Mapper;
uberParent?: string;
polymorphicDiscriminator?: string | PolymorphicDiscriminator;
}
export interface SequenceMapperType {
name: "Sequence";
element: Mapper;
}
export interface DictionaryMapperType {
name: "Dictionary";
value: Mapper;
}
export interface EnumMapperType {
name: "Enum";
allowedValues: any[];
2017-09-13 19:42:16 +03:00
}
2018-06-19 20:47:32 +03:00
export interface BaseMapper {
xmlName?: string;
xmlIsAttribute?: boolean;
xmlElementName?: string;
xmlIsWrapped?: boolean;
2017-09-13 19:42:16 +03:00
readOnly?: boolean;
isConstant?: boolean;
required?: boolean;
2018-06-28 22:17:31 +03:00
nullable?: boolean;
serializedName?: string;
type: MapperType;
2017-09-13 19:42:16 +03:00
defaultValue?: any;
constraints?: MapperConstraints;
}
2018-06-19 20:47:32 +03:00
export type Mapper = BaseMapper | CompositeMapper | SequenceMapper | DictionaryMapper | EnumMapper;
2017-09-13 19:42:16 +03:00
export interface PolymorphicDiscriminator {
serializedName: string;
clientName: string;
[key: string]: string;
}
2018-06-19 20:47:32 +03:00
export interface CompositeMapper extends BaseMapper {
type: CompositeMapperType;
2017-09-13 19:42:16 +03:00
}
2018-06-19 20:47:32 +03:00
export interface SequenceMapper extends BaseMapper {
type: SequenceMapperType;
2017-09-13 19:42:16 +03:00
}
2018-06-19 20:47:32 +03:00
export interface DictionaryMapper extends BaseMapper {
type: DictionaryMapperType;
headerCollectionPrefix?: string;
2017-09-13 19:42:16 +03:00
}
2018-06-19 20:47:32 +03:00
export interface EnumMapper extends BaseMapper {
type: EnumMapperType;
2017-09-13 19:42:16 +03:00
}
export interface UrlParameterValue {
value: string;
skipUrlEncoding: boolean;
}
2018-06-22 06:45:36 +03:00
// TODO: why is this here?
2017-09-13 19:42:16 +03:00
export function serializeObject(toSerialize: any): any {
2018-06-22 06:45:36 +03:00
if (toSerialize == undefined) return undefined;
if (toSerialize instanceof Uint8Array) {
toSerialize = base64.encodeByteArray(toSerialize);
2017-09-13 19:42:16 +03:00
return toSerialize;
}
else if (toSerialize instanceof Date) {
return toSerialize.toISOString();
}
else if (Array.isArray(toSerialize)) {
const array = [];
for (let i = 0; i < toSerialize.length; i++) {
array.push(serializeObject(toSerialize[i]));
}
return array;
} else if (typeof toSerialize === "object") {
const dictionary: { [key: string]: any } = {};
for (const property in toSerialize) {
dictionary[property] = serializeObject(toSerialize[property]);
}
return dictionary;
}
return toSerialize;
}
/**
* Utility function to create a K:V from a list of strings
*/
function strEnum<T extends string>(o: Array<T>): { [K in T]: K } {
const result: any = {};
for (const key of o) {
result[key] = key;
}
return result;
}
export const MapperType = strEnum([
2017-09-13 19:42:16 +03:00
"Base64Url",
"Boolean",
"ByteArray",
"Composite",
"Date",
"DateTime",
"DateTimeRfc1123",
"Dictionary",
"Enum",
"Number",
"Object",
"Sequence",
"String",
"Stream",
"TimeSpan",
"UnixTime"
]);