import { CodeModel , Parameter , ParameterLocation , SchemaType , Schema , ImplementationLocation , SerializationStyle , ConstantSchema , VirtualParameter } from "@autorest/codemodel" ; import { QueryCollectionFormat } from "@azure/core-http" ; import { getLanguageMetadata } from "../utils/languageHelpers" ; import { normalizeName , NameType , getOperationFullName } from "../utils/nameUtils" ; import { ParameterDetails } from "../models/parameterDetails" ; import { isSchemaType , getMapperClassName , transformMapper } from "./mapperTransforms" ; import { isEqual , isNil } from "lodash" ; import { getTypeForSchema , getSchemaTypeDocumentation } from "../utils/schemaHelpers" ; import { getStringForValue } from "../utils/valueHelpers" ; import { ClientOptions } from "../models/clientDetails" ; import { PropertyKind } from "../models/modelDetails" ; import { KnownMediaType } from "@azure-tools/codegen" ; import { getAutorestOptions } from "../autorestSession" ; import { DictionaryMapper } from "@azure/core-client" ; interface OperationParameterDetails { parameter : Parameter ; operationName : string ; /**
     * Only specified when an operation has multiple requests.
     * This is used to identify which request a parameter belongs to.
     */ targetMediaType ? : KnownMediaType ; } const buildCredentialsParameter = ( ) : ParameterDetails => { const { useCoreV2 } = getAutorestOptions ( ) ; return { nameRef : "credentials" , description : "Subscription credentials which uniquely identify client subscription." , name : "credentials" , serializedName : "credentials" , location : ParameterLocation . None , required : true , nullable : false , parameterPath : "credentials" , mapper : "any" , isGlobal : true , parameter : { } as Parameter , implementationLocation : ImplementationLocation . Client , typeDetails : { typeName : ! useCoreV2 ? "coreHttp.TokenCredential | coreHttp.ServiceClientCredentials" : "coreAuth.TokenCredential" , kind : PropertyKind . Primitive , usedModels : [ ] } , isSynthetic : true , schemaType : SchemaType . Object , isFlattened : false } ; } ; const buildEndpointParameter = ( ) : ParameterDetails => ( { nameRef : "endpoint" , description : "Overrides client endpoint." , name : "endpoint" , serializedName : "endpoint" , location : ParameterLocation . None , required : false , nullable : true , parameterPath : "endpoint" , mapper : "any" , isGlobal : true , parameter : { } as Parameter , implementationLocation : ImplementationLocation . Client , typeDetails : { typeName : "string" , kind : PropertyKind . Primitive , usedModels : [ ] } , isSynthetic : true , schemaType : SchemaType . String , isFlattened : false } ) ; export function transformParameters ( codeModel , options ) { let parameters = [ ] ; const { addCredentials } = getAutorestOptions ( ) ; const hasXmlMetadata = ! ! options . mediaTypes ?. has ( KnownMediaType . Xml ) ; extractOperationParameters ( codeModel ) . forEach ( p => populateOperationParameters ( p . parameter , parameters , p . operationName , hasXmlMetadata , p . targetMediaType ) ) ; if ( addCredentials ) { const creds = buildCredentialsParameter ( ) ; parameters . unshift ( creds ) ; } parameters . push ( buildEndpointParameter ( ) ) ; return parameters ; } const extractOperationParameters = ( codeModel ) => codeModel . operationGroups . reduce < OperationParameterDetails [ ] > ( ( acc , og ) => { const clientName = getLanguageMetadata ( codeModel . language ) . name ; return [ ... acc , ... og . operations . reduce < OperationParameterDetails [ ] > ( ( operations , operation ) => { const operationName = getOperationFullName ( og , operation , clientName ) ; const requests = operation . requests ; if ( requests === undefined ) { throw new Error ( `No request object was found for operation: ${ operationName }` ) ; } const operationParams = ( operation . parameters || [ ] ) . map ( p => ( { parameter : p , operationName } ) ) ; const requestParams = [ ] ; requests . forEach ( request => { request . parameters ?. forEach ( parameter => { requestParams . push ( { operationName , parameter , targetMediaType : request . protocol . http ?. knownMediaType } ) ; } ) ; } ) ; return [ ... operations , ... requestParams , ... operationParams ] ; } , [ ] ) ] ; } , [ ] ) ; function getDefaultValue ( parameter ) { if ( parameter . schema . type === SchemaType . Constant ) { const constantSchema = parameter . schema as ConstantSchema ; let defaultValue = getStringForValue ( constantSchema . value . value , constantSchema . valueType . type ) ; if ( constantSchema . valueType . type !== SchemaType . String && constantSchema . defaultValue ) { defaultValue = constantSchema . defaultValue ; } return defaultValue ; } if ( ! ! parameter . clientDefaultValue ) { return getStringForValue ( parameter . clientDefaultValue , parameter . schema . type ) ; } return undefined ; } export function populateOperationParameters ( parameter , operationParameters , operationName , hasXmlMetadata , targetMediaType ? ) { const { useCoreV2 } = getAutorestOptions ( ) ; const parameterName = getParameterName ( parameter ) ; const parameterMetadata = getLanguageMetadata ( parameter . language ) ; const schemaMetadata = getLanguageMetadata ( parameter . schema . language ) ; if ( ! parameterName ) { throw new Error ( `Couldn't get parameter name for operation: ${ operationName }` ) ; } const serializedName = parameterMetadata . serializedName || schemaMetadata . serializedName || parameterName ; let description = parameterMetadata . description || schemaMetadata . description ; if ( parameter . schema . type === SchemaType . Group ) { return ; } const name = normalizeName ( parameterName , NameType . Parameter , true ) ; const sameNameParams = operationParameters . filter ( p => p . name === name || p . nameRef === name ) ; description += getSchemaTypeDocumentation ( parameter . schema ) ; const isRequired = getParameterRequired ( parameter ) ; const isNullable = ! ! parameter . nullable ; const collectionFormat = getCollectionFormat ( parameter ) ; const typeDetails = getTypeForSchema ( parameter . schema , false , useCoreV2 ) ; const paramDetails = { nameRef : name , description : isRequired && parameter . implementation === ImplementationLocation . Method ? undefined : description , name , serializedName , operationsIn : { [ operationName ] : { description } } , location : getParameterLocation ( parameter ) , required : isRequired , nullable : isNullable , schemaType : parameter . schema . type , parameterPath : getParameterPath ( parameter ) , mapper : getMapperOrRef ( ( parameter as VirtualParameter ) . originalParameter ? ( parameter as VirtualParameter ) . originalParameter . schema : parameter . schema , serializedName , parameter . required , hasXmlMetadata ) , isGlobal : getIsGlobal ( parameter ) , parameter , collectionFormat , implementationLocation : parameter . implementation , typeDetails , defaultValue : getDefaultValue ( parameter ) , skipEncoding : getSkipEncoding ( parameter ) , targetMediaType , isFlattened : ! ! parameter . flattened } ; if ( parameter . extensions && parameter . extensions [ "x-ms-header-collection-prefix" ] && typeof paramDetails . mapper !== "string" ) { ( paramDetails . mapper as DictionaryMapper ) [ "headerCollectionPrefix" ] = parameter . extensions [ "x-ms-header-collection-prefix" ] ; } if ( ! sameNameParams . length ) { operationParameters . push ( paramDetails ) ; return ; } disambiguateParameter ( paramDetails , operationParameters , sameNameParams , operationName , description ) ; } function getSkipEncoding ( parameter ) { return parameter . extensions && parameter . extensions [ "x-ms-skip-url-encoding" ] ; } function getParameterRequired ( parameter ) { const requiredExtension = ( parameter . extensions || { } ) [ "x-required" ] ; if ( ! isNil ( requiredExtension ) ) { return requiredExtension ; } return parameter . required ; } function getIsGlobal ( parameter ) { return parameter . extensions ? ! isNil ( parameter . extensions [ "x-ms-priority" ] ) : false ; } function getParameterPath ( parameter ) { const metadata = getLanguageMetadata ( parameter . language ) ; const name = normalizeName ( metadata . name , NameType . Parameter , true ) ; if ( parameter . groupedBy ) { const groupedByName = getLanguageMetadata ( parameter . groupedBy . language ) . name ; return [ ... ( ! parameter . required && ! parameter . groupedBy . required ? [ "options" ] : [ ] ) , normalizeName ( groupedByName , NameType . Parameter , true ) , name ] ; } return isClientImplementation ( parameter ) || parameter . required ? name : [ "options" , name ] ; } const isClientImplementation = ( parameter ) => parameter . implementation === ImplementationLocation . Client ; function getParameterLocation ( parameter ) { const originalLocaltion = parameter . protocol . http ?. in || ( parameter as VirtualParameter ) . originalParameter . protocol . http ?. in ; const locationExtension = parameter . extensions && parameter . extensions [ "x-in" ] ; if ( ! originalLocaltion && ! locationExtension ) { throw new Error ( "Expected parameter to contain HTTP Protocol information" ) ; } return locationExtension || originalLocaltion ; } /**
 * Serialization styles used by ModelerFour but not present in SerializationStyle
 */ enum AdditionalStyles { TabDelimited = "tabDelimited" } const AllSerializationStyles = { ... SerializationStyle , ... AdditionalStyles } ; function getCollectionFormat ( parameter ) { const httpInfo = parameter . protocol . http || ( parameter as VirtualParameter ) . originalParameter . protocol . http ; if ( ! httpInfo ) { throw new Error ( "Expected parameter to contain HTTP Protocol information" ) ; } const style = httpInfo . style ; if ( httpInfo . in !== ParameterLocation . Query || ! style ) { return undefined ; } const getStyle = ( value ) => Object . keys ( QueryCollectionFormat ) . find ( key => ( QueryCollectionFormat as any ) [ key ] === value ) ; let queryCollectionFormat ; switch ( style ) { case AllSerializationStyles . SpaceDelimited : queryCollectionFormat = QueryCollectionFormat . Ssv ; break ; case AllSerializationStyles . Form : queryCollectionFormat = httpInfo ?. explode ? QueryCollectionFormat . Multi : QueryCollectionFormat . Csv ; break ; case AllSerializationStyles . TabDelimited : queryCollectionFormat = QueryCollectionFormat . Tsv ; break ; case AllSerializationStyles . PipeDelimited : queryCollectionFormat = QueryCollectionFormat . Pipes ; break ; case AllSerializationStyles . Simple : return undefined ; default : throw new Error ( `Handling query parameter format: ${ style } hasn't bee implemented yet` ) ; } return getStyle ( queryCollectionFormat ) ; } function getParameterName ( parameter ) { const nameFromExtension = parameter . extensions && parameter . extensions [ "x-ms-requestBody-name" ] ; const { name : originalName } = getLanguageMetadata ( parameter . language ) ; const name = nameFromExtension || originalName ; if ( ! name ) { throw new Error ( `ParameterTransform: Expected parameter to have a name ${ JSON . stringify ( parameter . language ) }` ) ; } return name ; } /**
 * Extracts the properties from ParameterDetails to use for equality comparison
 */ function getComparableParameter ( { name , serializedName , location , required , parameterPath , mapper , collectionFormat , schemaType , implementationLocation , typeDetails , skipEncoding , isSynthetic , targetMediaType , parameter } ) { return { name , serializedName , location , required , parameterPath , mapper , collectionFormat , schemaType , implementationLocation , typeDetails , skipEncoding , isSynthetic , targetMediaType : targetMediaType || KnownMediaType . Json , isFlattened : parameter . flattened } ; } /**
 * This function takes care of disambiguating parameters with different schemas but
 * using the same name. If it is the first time a parameter is seen, we store it in the
 * operationParameters array.
 *
 * If there is already a parameter with the same name we check if they are the same, if so
 * we just add the current operationName to the operationsIn array.
 *
 * Otherwise we add a suffix to the parameter name and store it as a different parameter.
 */ export function disambiguateParameter ( parameter , operationParameters , sameNameParams , operationName , description ) { const param = getComparableParameter ( parameter ) ; const existingParam = sameNameParams . find ( p => isEqual ( getComparableParameter ( p ) , param ) ) ; if ( existingParam ) { if ( existingParam . operationsIn ) { existingParam . operationsIn [ operationName ] = { description } ; } else { existingParam . operationsIn = { [ operationName ] : { description } } ; } return ; } else { const nameRef = `${ parameter . name }${ sameNameParams . length }` ; let description = parameter . description ; if ( parameter . schemaType === SchemaType . Time ) { description += `\nThis value should be an ISO-8601 formatted string representing time. E.g. "HH:MM:SS" or "HH:MM:SS.mm".` ; } operationParameters . push ( { ... parameter , nameRef , description } ) ; } } function getMapperOrRef ( schema , serializedName , required ? , hasXmlMetadata = false ) { if ( isSchemaType ( [ SchemaType . Object ] , schema ) ) { const className = getMapperClassName ( schema ) ; return className ; } return transformMapper ( { schema , options : { serializedName , required , hasXmlMetadata } } ) ; }