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 utils from "./util/utils" ;
2018-03-31 06:19:52 +03:00
const isBuffer : ( obj : any ) = > boolean = require ( "is-buffer" ) ;
2017-09-13 19:42:16 +03:00
export class Serializer {
modelMappers ? : { [ key : string ] : any } ;
2018-03-27 03:54:26 +03:00
constructor ( mappers ? : { [ key : string ] : any } , private isXML? : boolean ) {
2017-09-13 19:42:16 +03:00
this . modelMappers = mappers ;
}
validateConstraints ( mapper : Mapper , value : any , objectName : string ) : void {
if ( mapper . constraints && ( value !== null || value !== undefined ) ) {
2017-10-17 22:10:36 +03:00
for ( const constraintType of Object . keys ( mapper . constraints ) ) {
2017-09-13 19:42:16 +03:00
if ( constraintType . match ( /^ExclusiveMaximum$/ig ) !== null ) {
if ( value >= ( ( mapper . constraints as MapperConstraints ) . ExclusiveMaximum as number ) ) {
throw new Error ( ` " ${ objectName } " with value " ${ value } " should satify the constraint "ExclusiveMaximum": ${ ( ( mapper . constraints as MapperConstraints ) . ExclusiveMaximum as number ) } . ` ) ;
}
} else if ( constraintType . match ( /^ExclusiveMinimum$/ig ) !== null ) {
if ( value <= ( ( mapper . constraints as MapperConstraints ) . ExclusiveMinimum as number ) ) {
throw new Error ( ` ${ objectName } " with value " ${ value } " should satify the constraint "ExclusiveMinimum": ${ ( ( mapper . constraints as MapperConstraints ) . ExclusiveMinimum as number ) } . ` ) ;
}
} else if ( constraintType . match ( /^InclusiveMaximum$/ig ) !== null ) {
if ( value > ( ( mapper . constraints as MapperConstraints ) . InclusiveMaximum as number ) ) {
throw new Error ( ` ${ objectName } " with value " ${ value } " should satify the constraint "InclusiveMaximum": ${ ( ( mapper . constraints as MapperConstraints ) . InclusiveMaximum as number ) } . ` ) ;
}
} else if ( constraintType . match ( /^InclusiveMinimum$/ig ) !== null ) {
if ( value < ( ( mapper . constraints as MapperConstraints ) . InclusiveMinimum as number ) ) {
throw new Error ( ` ${ objectName } " with value " ${ value } " should satify the constraint "InclusiveMinimum": ${ ( ( mapper . constraints as MapperConstraints ) . InclusiveMinimum as number ) } . ` ) ;
}
} else if ( constraintType . match ( /^MaxItems$/ig ) !== null ) {
if ( value . length > ( ( mapper . constraints as MapperConstraints ) . MaxItems as number ) ) {
throw new Error ( ` ${ objectName } " with value " ${ value } " should satify the constraint "MaxItems": ${ ( ( mapper . constraints as MapperConstraints ) . MaxItems as number ) } . ` ) ;
}
} else if ( constraintType . match ( /^MaxLength$/ig ) !== null ) {
if ( value . length > ( ( mapper . constraints as MapperConstraints ) . MaxLength as number ) ) {
throw new Error ( ` ${ objectName } " with value " ${ value } " should satify the constraint "MaxLength": ${ ( ( mapper . constraints as MapperConstraints ) . MaxLength as number ) } . ` ) ;
}
} else if ( constraintType . match ( /^MinItems$/ig ) !== null ) {
if ( value . length < ( ( mapper . constraints as MapperConstraints ) . MinItems as number ) ) {
throw new Error ( ` ${ objectName } " with value " ${ value } " should satify the constraint "MinItems": ${ ( ( mapper . constraints as MapperConstraints ) . MinItems as number ) } . ` ) ;
}
} else if ( constraintType . match ( /^MinLength$/ig ) !== null ) {
if ( value . length < ( ( mapper . constraints as MapperConstraints ) . MinLength as number ) ) {
throw new Error ( ` ${ objectName } " with value " ${ value } " should satify the constraint "MinLength": ${ ( ( mapper . constraints as MapperConstraints ) . MinLength as number ) } . ` ) ;
}
} else if ( constraintType . match ( /^MultipleOf$/ig ) !== null ) {
2018-05-31 22:51:44 +03:00
if ( value % ( ( mapper . constraints as MapperConstraints ) . MultipleOf as number ) !== 0 ) {
2017-09-13 19:42:16 +03:00
throw new Error ( ` ${ objectName } " with value " ${ value } " should satify the constraint "MultipleOf": ${ ( ( mapper . constraints as MapperConstraints ) . MultipleOf as number ) } . ` ) ;
}
} else if ( constraintType . match ( /^Pattern$/ig ) !== null ) {
2018-05-31 22:51:44 +03:00
const regexp : RegExp = ( mapper . constraints as MapperConstraints ) . Pattern ! ;
const match : any = value . match ( regexp ) ;
if ( match === null ) {
throw new Error ( ` ${ objectName } " with value " ${ value } " should satify the constraint "Pattern": ${ regexp } . ` ) ;
2017-09-13 19:42:16 +03:00
}
} else if ( constraintType . match ( /^UniqueItems/ig ) !== null ) {
if ( ( ( mapper . constraints as MapperConstraints ) . UniqueItems as boolean ) ) {
if ( value . length !== value . filter ( ( item : any , i : number , ar : Array < any > ) = > { { return ar . indexOf ( item ) === i ; } } ) . length ) {
throw new Error ( ` ${ objectName } " with value " ${ value } " should satify the constraint "UniqueItems": ${ ( ( mapper . constraints as MapperConstraints ) . UniqueItems as boolean ) } ` ) ;
}
}
}
2017-10-17 22:10:36 +03:00
}
2017-09-13 19:42:16 +03:00
}
}
private trimEnd ( str : string , ch : string ) {
let len = str . length ;
while ( ( len - 1 ) >= 0 && str [ len - 1 ] === ch ) {
-- len ;
}
return str . substr ( 0 , len ) ;
}
private bufferToBase64Url ( buffer : any ) : string | undefined {
if ( ! buffer ) {
return undefined ;
}
if ( ! isBuffer ( buffer ) ) {
throw new Error ( ` Please provide an input of type Buffer for converting to Base64Url. ` ) ;
}
// Buffer to Base64.
const str = buffer . toString ( "base64" ) ;
// Base64 to Base64Url.
return this . trimEnd ( str , "=" ) . replace ( /\+/g , "-" ) . replace ( /\//g , "_" ) ;
}
private base64UrlToBuffer ( str : string ) : any {
if ( ! str ) {
return undefined ;
}
if ( str && typeof str . valueOf ( ) !== "string" ) {
throw new Error ( "Please provide an input of type string for converting to Buffer" ) ;
}
// Base64Url to Base64.
str = str . replace ( /\-/g , "+" ) . replace ( /\_/g , "/" ) ;
// Base64 to Buffer.
return Buffer . from ( str , "base64" ) ;
}
private splitSerializeName ( prop : string ) : Array < string > {
const classes : Array < string > = [ ] ;
let partialclass = "" ;
const subwords = prop . split ( "." ) ;
2017-10-17 22:10:36 +03:00
for ( const item of subwords ) {
2017-09-13 19:42:16 +03:00
if ( item . charAt ( item . length - 1 ) === "\\" ) {
partialclass += item . substr ( 0 , item . length - 1 ) + "." ;
} else {
partialclass += item ;
classes . push ( partialclass ) ;
partialclass = "" ;
}
2017-10-17 22:10:36 +03:00
}
2017-09-13 19:42:16 +03:00
return classes ;
}
private 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 ) ;
}
private unixTimeToDate ( n : number ) : Date | undefined {
if ( ! n ) {
return undefined ;
}
return new Date ( n * 1000 ) ;
}
private 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. ` ) ;
}
} else if ( typeName . match ( /^String$/ig ) !== null ) {
if ( typeof value . valueOf ( ) !== "string" ) {
throw new Error ( ` ${ objectName } with value " ${ value } " must be of type string. ` ) ;
}
} 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. ` ) ;
}
} else if ( typeName . match ( /^Stream$/ig ) !== null ) {
2018-05-25 22:36:29 +03:00
const objectType = typeof value ;
if ( objectType !== "string" &&
2018-06-01 01:31:45 +03:00
objectType !== "function" &&
! ( value instanceof ArrayBuffer ) &&
! ArrayBuffer . isView ( value ) &&
! ( typeof Blob === "function" && value instanceof Blob ) ) {
2018-05-25 22:36:29 +03:00
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 ;
}
private 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 ;
}
private serializeBufferType ( objectName : string , value : any ) : any {
if ( value !== null && value !== undefined ) {
if ( ! isBuffer ( value ) ) {
throw new Error ( ` ${ objectName } must be of type Buffer. ` ) ;
}
value = value . toString ( "base64" ) ;
}
return value ;
}
private serializeBase64UrlType ( objectName : string , value : any ) : any {
if ( value !== null && value !== undefined ) {
if ( ! isBuffer ( value ) ) {
throw new Error ( ` ${ objectName } must be of type Buffer. ` ) ;
}
value = this . bufferToBase64Url ( value ) ;
}
return value ;
}
private serializeDateTypes ( typeName : string , value : any , objectName : string ) {
if ( value !== null && 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 = this . dateToUnixTime ( value ) ;
} else if ( typeName . match ( /^TimeSpan$/ig ) !== null ) {
2018-05-04 00:29:31 +03:00
if ( ! utils . isDuration ( value ) ) {
throw new Error ( ` ${ objectName } must be a string in ISO 8601 format. Instead was " ${ value } ". ` ) ;
2017-09-13 19:42:16 +03:00
}
2018-05-03 20:25:17 +03:00
value = value ;
2017-09-13 19:42:16 +03:00
}
}
return value ;
}
private serializeSequenceType ( mapper : SequenceMapper , object : any , objectName : string ) {
if ( ! Array . isArray ( object ) ) {
throw new Error ( ` ${ objectName } must be of type Array. ` ) ;
}
if ( ! mapper . type . element || typeof mapper . type . element !== "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 ++ ) {
tempArray [ i ] = this . serialize ( mapper . type . element , object [ i ] , objectName ) ;
}
return tempArray ;
}
private serializeDictionaryType ( mapper : DictionaryMapper , object : any , objectName : string ) {
if ( typeof object !== "object" ) {
throw new Error ( ` ${ objectName } must be of type object. ` ) ;
}
if ( ! mapper . type . value || typeof mapper . type . value !== "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 } = { } ;
for ( const key in object ) {
if ( object . hasOwnProperty ( key ) ) {
tempDictionary [ key ] = this . serialize ( mapper . type . value , object [ key ] , objectName ) ;
}
}
return tempDictionary ;
}
private serializeCompositeType ( mapper : CompositeMapper , object : any , objectName : string ) {
// check for polymorphic discriminator
if ( mapper . type . polymorphicDiscriminator ) {
mapper = this . getPolymorphicMapper ( mapper , object , objectName , "serialize" ) ;
}
const payload : any = { } ;
let modelMapper : CompositeMapper = {
required : false ,
serializedName : "serializedName" ,
type : {
name : "Composite" ,
className : "className" ,
modelProperties : { }
}
} ;
if ( object !== null && object !== undefined ) {
let modelProps = mapper . type . modelProperties ;
if ( ! modelProps ) {
if ( ! mapper . type . className ) {
throw new Error ( ` Class name for model " ${ objectName } " is not provided in the mapper " ${ JSON . stringify ( mapper , undefined , 2 ) } ". ` ) ;
}
// get the mapper if modelProperties of the CompositeType is not present and
// then get the modelProperties from it.
modelMapper = ( this . modelMappers as { [ key : string ] : any } ) [ mapper . type . className ] ;
if ( ! modelMapper ) {
throw new Error ( ` mapper() cannot be null or undefined for model " ${ mapper . type . className } ". ` ) ;
}
modelProps = modelMapper . type . modelProperties ;
if ( ! modelProps ) {
throw new Error ( ` modelProperties cannot be null or undefined in the ` +
` mapper " ${ JSON . stringify ( modelMapper ) } " of type " ${ mapper . type . className } " for object " ${ objectName } ". ` ) ;
}
}
2018-03-28 00:55:32 +03:00
for ( const key of Object . keys ( modelProps ) ) {
const propertyMapper = modelProps [ key ] ;
let propName : string | undefined ;
let parentObject : any = payload ;
if ( this . isXML ) {
2018-03-28 01:14:27 +03:00
if ( propertyMapper . xmlIsWrapped ) {
propName = propertyMapper . xmlName ;
} else {
propName = propertyMapper . xmlElementName || propertyMapper . xmlName ;
}
2018-03-28 00:55:32 +03:00
} else {
const paths = this . splitSerializeName ( propertyMapper . serializedName ) ;
propName = paths . pop ( ) ;
2017-09-13 19:42:16 +03:00
2017-10-17 22:10:36 +03:00
for ( const pathName of paths ) {
2017-09-13 19:42:16 +03:00
const childObject = parentObject [ pathName ] ;
if ( ( childObject === null || childObject === undefined ) && ( object [ key ] !== null && object [ key ] !== undefined ) ) {
parentObject [ pathName ] = { } ;
}
parentObject = parentObject [ pathName ] ;
2017-10-17 22:10:36 +03:00
}
2018-03-28 00:55:32 +03:00
}
2017-09-13 19:42:16 +03:00
2018-03-28 00:55:32 +03:00
// make sure required properties of the CompositeType are present
2018-03-31 06:19:52 +03:00
if ( propertyMapper . required && ! propertyMapper . isConstant ) {
if ( object [ key ] == undefined ) {
2018-03-28 00:55:32 +03:00
throw new Error ( ` ${ key } " cannot be null or undefined in " ${ objectName } ". ` ) ;
2017-09-13 19:42:16 +03:00
}
2018-03-28 00:55:32 +03:00
}
// make sure that readOnly properties are not sent on the wire
2018-03-31 06:19:52 +03:00
if ( propertyMapper . readOnly ) {
2018-03-28 00:55:32 +03:00
continue ;
}
// serialize the property if it is present in the provided object instance
2018-03-31 06:19:52 +03:00
if ( ( ( parentObject !== null && parentObject !== undefined ) && ( propertyMapper . defaultValue !== null && propertyMapper . defaultValue !== undefined ) ) ||
2018-03-28 00:55:32 +03:00
( object [ key ] !== null && object [ key ] !== undefined ) ) {
2018-03-31 06:19:52 +03:00
const propertyObjectName = propertyMapper . serializedName !== ""
? objectName + "." + propertyMapper . serializedName
2018-03-28 00:55:32 +03:00
: objectName ;
const serializedValue = this . serialize ( propertyMapper , object [ key ] , propertyObjectName ) ;
if ( propName !== null && propName !== undefined ) {
if ( propertyMapper . xmlIsAttribute ) {
2018-03-31 06:19:52 +03:00
// $ 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 ;
2018-03-28 01:14:27 +03:00
} else if ( propertyMapper . xmlIsWrapped ) {
2018-03-28 02:48:56 +03:00
parentObject [ propName ] = { [ propertyMapper . xmlElementName ! ] : serializedValue } ;
2018-03-28 00:55:32 +03:00
} else {
parentObject [ propName ] = serializedValue ;
}
2017-09-13 19:42:16 +03:00
}
}
}
return payload ;
}
return object ;
}
/ * *
* 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
* /
serialize ( mapper : Mapper , object : any , objectName : string ) : any {
let payload : any = { } ;
const mapperType = mapper . type . name as string ;
if ( ! objectName ) objectName = mapper . serializedName ;
if ( mapperType . match ( /^Sequence$/ig ) !== null ) payload = [ ] ;
// Throw if required and object is null or undefined
if ( mapper . required && ( object === null || object === undefined ) && ! mapper . isConstant ) {
throw new Error ( ` ${ objectName } cannot be null or undefined. ` ) ;
}
// Set Defaults
if ( ( mapper . defaultValue !== null && mapper . defaultValue !== undefined ) &&
( object === null || object === undefined ) ) {
object = mapper . defaultValue ;
}
if ( mapper . isConstant ) object = mapper . defaultValue ;
// Validate Constraints if any
this . validateConstraints ( mapper , object , objectName ) ;
2018-02-23 04:16:50 +03:00
if ( mapperType . match ( /^any$/ig ) !== null ) {
payload = object ;
} else if ( mapperType . match ( /^(Number|String|Boolean|Object|Stream|Uuid)$/ig ) !== null ) {
2018-03-27 20:04:21 +03:00
payload = this . serializeBasicTypes ( mapperType , objectName , object ) ;
2017-09-13 19:42:16 +03:00
} else if ( mapperType . match ( /^Enum$/ig ) !== null ) {
const enumMapper : EnumMapper = mapper as EnumMapper ;
payload = this . serializeEnumType ( objectName , enumMapper . type . allowedValues , object ) ;
} else if ( mapperType . match ( /^(Date|DateTime|TimeSpan|DateTimeRfc1123|UnixTime)$/ig ) !== null ) {
payload = this . serializeDateTypes ( mapperType , object , objectName ) ;
} else if ( mapperType . match ( /^ByteArray$/ig ) !== null ) {
payload = this . serializeBufferType ( objectName , object ) ;
} else if ( mapperType . match ( /^Base64Url$/ig ) !== null ) {
payload = this . serializeBase64UrlType ( objectName , object ) ;
} else if ( mapperType . match ( /^Sequence$/ig ) !== null ) {
payload = this . serializeSequenceType ( mapper as SequenceMapper , object , objectName ) ;
} else if ( mapperType . match ( /^Dictionary$/ig ) !== null ) {
payload = this . serializeDictionaryType ( mapper as DictionaryMapper , object , objectName ) ;
} else if ( mapperType . match ( /^Composite$/ig ) !== null ) {
payload = this . serializeCompositeType ( mapper as CompositeMapper , object , objectName ) ;
}
return payload ;
}
private deserializeCompositeType ( mapper : CompositeMapper , responseBody : any , objectName : string ) : any {
/*jshint validthis: true */
// check for polymorphic discriminator
if ( mapper . type . polymorphicDiscriminator ) {
mapper = this . getPolymorphicMapper ( mapper , responseBody , objectName , "deserialize" ) ;
}
let instance : { [ key : string ] : any } = { } ;
let modelMapper : Mapper = {
required : false ,
serializedName : "serializedName" ,
type : {
name : "Composite"
}
} ;
2018-03-28 01:33:10 +03:00
responseBody = responseBody || { } ;
2018-03-31 06:19:52 +03:00
let modelProps = mapper . type . modelProperties ;
if ( ! modelProps ) {
if ( ! mapper . type . className ) {
throw new Error ( ` Class name for model " ${ objectName } " is not provided in the mapper " ${ JSON . stringify ( mapper ) } " ` ) ;
}
// get the mapper if modelProperties of the CompositeType is not present and
// then get the modelProperties from it.
modelMapper = ( this . modelMappers as { [ key : string ] : any } ) [ mapper . type . className ] ;
if ( ! modelMapper ) {
throw new Error ( ` mapper() cannot be null or undefined for model " ${ mapper . type . className } " ` ) ;
}
modelProps = ( modelMapper as CompositeMapper ) . type . modelProperties ;
2017-09-13 19:42:16 +03:00
if ( ! modelProps ) {
2018-03-31 06:19:52 +03:00
throw new Error ( ` modelProperties cannot be null or undefined in the ` +
` mapper " ${ JSON . stringify ( modelMapper ) } " of type " ${ mapper . type . className } " for responseBody " ${ objectName } ". ` ) ;
2017-09-13 19:42:16 +03:00
}
2018-03-31 06:19:52 +03:00
}
2017-09-13 19:42:16 +03:00
2018-03-31 06:19:52 +03:00
for ( const key of Object . keys ( modelProps ) ) {
const propertyMapper = modelProps [ key ] ;
let propertyObjectName = objectName ;
if ( propertyMapper . serializedName !== "" ) {
propertyObjectName = objectName + "." + propertyMapper . serializedName ;
}
2018-03-27 03:54:26 +03:00
2018-03-31 06:19:52 +03:00
if ( this . isXML ) {
if ( propertyMapper . xmlIsAttribute && responseBody . $ ) {
instance [ key ] = this . deserialize ( propertyMapper , responseBody . $ [ propertyMapper . xmlName ! ] , propertyObjectName ) ;
2018-03-27 03:54:26 +03:00
} else {
2018-03-31 06:19:52 +03:00
const propertyName = propertyMapper . xmlElementName || propertyMapper . xmlName ;
let unwrappedProperty = responseBody [ propertyName ! ] ;
if ( propertyMapper . xmlIsWrapped ) {
unwrappedProperty = responseBody [ propertyMapper . xmlName ! ] ;
unwrappedProperty = unwrappedProperty && unwrappedProperty [ propertyMapper . xmlElementName ! ] ;
if ( unwrappedProperty === undefined ) {
// undefined means a wrapped list was empty
unwrappedProperty = [ ] ;
}
2017-09-13 19:42:16 +03:00
}
2018-03-31 06:19:52 +03:00
instance [ key ] = this . deserialize ( propertyMapper , unwrappedProperty , propertyObjectName ) ;
}
} else {
const paths = this . 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 = this . deserialize ( propertyMapper , propertyInstance , propertyObjectName ) ;
} else if ( propertyInstance !== null && propertyInstance !== undefined ) {
serializedValue = this . deserialize ( propertyMapper , propertyInstance , propertyObjectName ) ;
instance [ key ] = serializedValue ;
2017-09-13 19:42:16 +03:00
}
}
}
2018-03-31 06:19:52 +03:00
return instance ;
2017-09-13 19:42:16 +03:00
}
private deserializeDictionaryType ( mapper : DictionaryMapper , responseBody : any , objectName : string ) : any {
/*jshint validthis: true */
if ( ! mapper . type . value || typeof mapper . type . 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 } = { } ;
for ( const key in responseBody ) {
if ( responseBody . hasOwnProperty ( key ) ) {
tempDictionary [ key ] = this . deserialize ( mapper . type . value , responseBody [ key ] , objectName ) ;
}
}
return tempDictionary ;
}
return responseBody ;
}
private deserializeSequenceType ( mapper : SequenceMapper , responseBody : any , objectName : string ) : any {
/*jshint validthis: true */
if ( ! mapper . type . element || typeof mapper . type . element !== "object" ) {
throw new Error ( ` element" metadata for an Array must be defined in the ` +
` mapper and it must of type "object" in ${ objectName } ` ) ;
}
if ( responseBody ) {
2018-03-31 06:19:52 +03:00
if ( ! Array . isArray ( responseBody ) ) {
2018-03-28 20:27:33 +03:00
// xml2js will interpret a single element array as just the element, so force it to be an array
responseBody = [ responseBody ] ;
}
2017-09-13 19:42:16 +03:00
const tempArray = [ ] ;
for ( let i = 0 ; i < responseBody . length ; i ++ ) {
tempArray [ i ] = this . deserialize ( mapper . type . element , responseBody [ i ] , objectName ) ;
}
return tempArray ;
}
return responseBody ;
}
/ * *
* 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 {
2018-03-29 21:39:59 +03:00
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 = [ ] ;
} else {
return responseBody ;
}
2018-03-27 03:54:26 +03:00
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 ;
}
2017-09-13 19:42:16 +03:00
2018-03-29 00:14:56 +03:00
if ( mapperType . match ( /^Number$/ig ) !== null ) {
2018-05-23 03:17:25 +03:00
payload = parseFloat ( responseBody ) ;
if ( isNaN ( payload ) ) {
2018-03-29 00:14:56 +03:00
payload = responseBody ;
}
} else if ( mapperType . match ( /^Boolean$/ig ) !== null ) {
2018-05-23 03:17:25 +03:00
if ( responseBody === "true" ) {
payload = true ;
} else if ( responseBody === "false" ) {
payload = false ;
2018-03-29 00:14:56 +03:00
} else {
payload = responseBody ;
}
2018-05-03 20:25:17 +03:00
} 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 = this . unixTimeToDate ( responseBody ) ;
} else if ( mapperType . match ( /^ByteArray$/ig ) !== null ) {
payload = Buffer . from ( responseBody , "base64" ) ;
} else if ( mapperType . match ( /^Base64Url$/ig ) !== null ) {
payload = this . base64UrlToBuffer ( responseBody ) ;
} else if ( mapperType . match ( /^Sequence$/ig ) !== null ) {
payload = this . deserializeSequenceType ( mapper as SequenceMapper , responseBody , objectName ) ;
} else if ( mapperType . match ( /^Dictionary$/ig ) !== null ) {
payload = this . deserializeDictionaryType ( mapper as DictionaryMapper , responseBody , objectName ) ;
} else if ( mapperType . match ( /^Composite$/ig ) !== null ) {
payload = this . deserializeCompositeType ( mapper as CompositeMapper , responseBody , objectName ) ;
}
2018-03-29 00:25:36 +03:00
if ( mapper . isConstant ) {
payload = mapper . defaultValue ;
}
2017-09-13 19:42:16 +03:00
return payload ;
}
private getPolymorphicMapper ( 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
// the clientName of the polmorphicDiscriminator in the mapper and during deserialization from the responseBody we will
// lookup the serializedName of the polmorphicDiscriminator 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.
if ( mapper . type . polymorphicDiscriminator ) {
if ( typeof mapper . type . polymorphicDiscriminator . valueOf ( ) === "string" ) {
return this . getPolymorphicMapperStringVersion ( mapper , object , objectName ) ;
} else if ( mapper . type . polymorphicDiscriminator instanceof Object ) {
return this . getPolymorphicMapperObjectVersion ( 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.
private getPolymorphicMapperObjectVersion ( 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 ` +
` polmorphicDiscriminator and 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 ] ] ;
}
2018-04-02 23:01:32 +03:00
if ( this . modelMappers && this . modelMappers . discriminators [ indexDiscriminator ] ) {
mapper = this . modelMappers . discriminators [ indexDiscriminator ] ;
2017-09-13 19:42:16 +03:00
}
}
return mapper ;
}
// processes old version of the polymorphicDiscriminator in the mapper.
private getPolymorphicMapperStringVersion ( mapper : CompositeMapper , object : any , objectName : string ) : CompositeMapper {
// check for polymorphic discriminator
const discriminatorAsString : string = mapper . type . polymorphicDiscriminator as string ;
if ( discriminatorAsString !== null && discriminatorAsString !== undefined ) {
if ( object === null || object === undefined ) {
throw new Error ( ` ${ objectName } " cannot be null or undefined. " ${ discriminatorAsString } " is the ` +
` polmorphicDiscriminator and is a required property. ` ) ;
}
if ( object [ discriminatorAsString ] === null || 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 ] ;
}
2018-04-02 23:01:32 +03:00
if ( this . modelMappers && this . modelMappers . discriminators [ indexDiscriminator ] ) {
mapper = this . modelMappers . discriminators [ indexDiscriminator ] ;
2017-09-13 19:42:16 +03:00
}
}
2018-04-02 23:01:32 +03:00
2017-09-13 19:42:16 +03:00
return mapper ;
}
}
export interface MapperConstraints {
InclusiveMaximum? : number ;
ExclusiveMaximum? : number ;
InclusiveMinimum? : number ;
ExclusiveMinimum? : number ;
MaxLength? : number ;
MinLength? : number ;
2018-05-31 22:51:44 +03:00
Pattern? : RegExp ;
2017-09-13 19:42:16 +03:00
MaxItems? : number ;
MinItems? : number ;
UniqueItems? : true ;
MultipleOf? : number ;
}
export interface BaseMapperType {
name : string ;
[ key : string ] : any ;
}
export interface Mapper {
2018-03-27 03:54:26 +03:00
xmlName? : string ;
xmlIsAttribute? : boolean ;
xmlElementName? : string ;
2018-03-28 00:55:32 +03:00
xmlIsWrapped? : boolean ;
2017-09-13 19:42:16 +03:00
readOnly? : boolean ;
isConstant? : boolean ;
2018-05-31 22:51:44 +03:00
required? : boolean ;
2017-09-13 19:42:16 +03:00
serializedName : string ;
type : BaseMapperType ;
defaultValue? : any ;
constraints? : MapperConstraints ;
}
export interface PolymorphicDiscriminator {
serializedName : string ;
clientName : string ;
[ key : string ] : string ;
}
export interface CompositeMapper extends Mapper {
type : {
name : "Composite" ;
className : string ;
modelProperties ? : { [ propertyName : string ] : Mapper } ;
uberParent? : string ;
polymorphicDiscriminator? : string | PolymorphicDiscriminator ;
} ;
}
export interface SequenceMapper extends Mapper {
type : {
name : "Sequence" ;
element : Mapper ;
} ;
}
export interface DictionaryMapper extends Mapper {
type : {
name : "Dictionary" ;
value : Mapper ;
} ;
}
export interface EnumMapper extends Mapper {
type : {
name : "Enum" ;
allowedValues : Array < any > ;
} ;
}
export interface UrlParameterValue {
value : string ;
skipUrlEncoding : boolean ;
}
export function serializeObject ( toSerialize : any ) : any {
if ( toSerialize === null || toSerialize === undefined ) return undefined ;
if ( isBuffer ( toSerialize ) ) {
toSerialize = toSerialize . toString ( "base64" ) ;
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 ;
}
2018-05-23 19:50:08 +03:00
/ * *
* Utility function to create a K :V from a list of strings
* /
2018-05-31 22:51:44 +03:00
function strEnum < T extends string > ( o : Array < T > ) : { [ K in T ] : K } {
2018-05-23 19:50:08 +03:00
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"
] ) ;