import { CodeModel , Operation , ParameterLocation , ImplementationLocation } from "@autorest/codemodel" ; import { getResponseTypeName } from "./operationHelpers" ; import { CallSignatureDeclarationStructure , Project , SourceFile , StructureKind , Writers } from "ts-morph" ; import * as path from 'path' ; import { getAutorestOptions , getSession } from "../autorestSession" ; import { transformBaseUrl } from "../transforms/urlTransforms" ; import { NameType , normalizeName } from "../utils/nameUtils" ; import { getLanguageMetadata } from "../utils/languageHelpers" ; import { buildMethodDefinitions , getOperationParameters , getPathParamDefinitions } from "./helpers/operationHelpers" ; import { generateMethodShortcuts } from "./generateMethodShortcuts" ; import { Methods , PathParameter , Paths } from "./interfaces" ; export let pathDictionary = { } ; export function generatePathFirstClient ( model , project ) { const name = normalizeName ( getLanguageMetadata ( model . language ) . name , NameType . File ) ; const { srcPath } = getAutorestOptions ( ) ; const clientFile = project . createSourceFile ( path . join ( srcPath , `clientDefinitions.ts` ) , undefined , { overwrite : true } ) ; const importedParameters = new Set < string > ( ) ; const importedResponses = new Set < string > ( ) ; pathDictionary = { } ; for ( const operationGroup of model . operationGroups ) { for ( const operation of operationGroup . operations ) { const operationName = getLanguageMetadata ( operation . language ) . name ; const operationDescription = getLanguageMetadata ( operation . language ) . description ; const pathParameters = operation . parameters ?. filter ( p => p . protocol . http ?. in === ParameterLocation . Path ) . map ( p => { const languageMetadata = getLanguageMetadata ( p . language ) ; return { name : languageMetadata . serializedName || languageMetadata . name , schema : p . schema , description : languageMetadata . description } ; } ) || [ ] ; const path = operation . requests ?. [ 0 ] . protocol . http ?. path ; pathParameters . sort ( function compare ( a , b ) { return path . indexOf ( a . name ) - path . indexOf ( b . name ) ; } ) ; for ( const request of operation . requests || [ ] ) { const path = ( request . protocol . http ?. path as string ) || "" ; const method = request . protocol . http ?. method ; if ( path && method ) { if ( ! pathDictionary [ path ] ) { pathDictionary [ path ] = { pathParameters , methods : { } , name : operationName } ; } const hasOptionalOptions = ! hasRequiredOptions ( operation ) ; const newMethod = { description : operationDescription , optionsName : getOperationOptionsType ( operation , importedParameters ) , hasOptionalOptions , returnType : `Promise<${ getOperationReturnType ( operation , importedResponses ) }>` } ; if ( pathDictionary [ path ] . methods [ `${ method }` ] ) { pathDictionary [ path ] . methods [ `${ method }` ] . push ( newMethod ) ; } else { pathDictionary [ path ] . methods [ `${ method }` ] = [ newMethod ] ; } } } } } writeShortcutInterface ( model , pathDictionary , clientFile ) ; clientFile . addInterface ( { name : "Routes" , isExported : true , callSignatures : getPathFirstRoutesInterfaceDefinition ( pathDictionary , clientFile ) } ) ; const clientName = getLanguageMetadata ( model . language ) . name ; const uriParameter = getClientUriParameter ( ) ; const { addCredentials , credentialKeyHeaderName } = getAutorestOptions ( ) ; const credentialTypes = addCredentials ? [ "TokenCredential" ] : [ ] ; if ( credentialKeyHeaderName ) { credentialTypes . push ( "KeyCredential" ) ; } const clientIterfaceName = `${ clientName }RestClient` ; const { rlcShortcut } = getAutorestOptions ( ) ; let shortcutElements = ! rlcShortcut ? [ ] : model . operationGroups . map ( og => { const groupName = og . language . default . name ; const name = normalizeName ( groupName , NameType . Property ) ; const interfaceName = normalizeName ( `${ name }Operations` , NameType . Interface ) ; return { name , type : interfaceName } ; } ) ; const shortcutsInOperationGroup = shortcutElements . filter ( s => s . name ) ; clientFile . addTypeAlias ( { isExported : true , name : clientIterfaceName , type : Writers . intersectionType ( "Client" , Writers . objectType ( { properties : [ { name : "path" , type : "Routes" } , ... shortcutsInOperationGroup ] } ) , ... ( shortcutsInOperationGroup . length !== shortcutElements . length ? [ `ClientOperations` ] : [ ] ) ) } ) ; if ( importedParameters . size ) { clientFile . addImportDeclaration ( { namedImports : [ ... importedParameters ] , moduleSpecifier : "./parameters" } ) ; } if ( importedResponses . size ) { clientFile . addImportDeclaration ( { namedImports : [ ... importedResponses ] , moduleSpecifier : "./responses" } ) ; } clientFile . addImportDeclarations ( [ { namedImports : [ "Client" ] , moduleSpecifier : "@azure-rest/core-client" } ] ) ; clientFile . addImportDeclarations ( [ { namedImports : credentialTypes , moduleSpecifier : "@azure/core-auth" } ] ) ; } function writeShortcutInterface ( model , pathDictionary , clientFile ) { const { rlcShortcut } = getAutorestOptions ( ) ; if ( ! rlcShortcut ) { return ; } const descriptions = model . operationGroups . reduce ( ( map , current ) => { const { name , description } = current . language . default ; map . set ( name , description ) ; return map ; } , new Map < string , string > ( ) ) ; const shortcuts = generateMethodShortcuts ( model , pathDictionary ) ; for ( const group of Object . keys ( shortcuts ) ) { const groupName = normalizeName ( group , NameType . Interface ) || "Client" ; const groupOperations = shortcuts [ group ] ; clientFile . addInterface ( { docs : [ descriptions . get ( group ) || `Contains operations for ${ groupName } operations` ] , name : `${ groupName }Operations` , isExported : true , methods : groupOperations } ) ; } } function hasRequiredOptions ( operation ) { return getOperationParameters ( operation ) . filter ( p => p . implementation === ImplementationLocation . Method ) . filter ( p => [ "query" , "body" , "headers" ] . includes ( p . protocol . http ?. in ) ) . some ( p => p . required ) ; } function getOperationOptionsType ( operation , importedParameters = new Set < string > ( ) ) { const paramsName = `${ getLanguageMetadata ( operation . language ) . name }Parameters` ; importedParameters . add ( paramsName ) ; return paramsName ; } function getOperationReturnType ( operation , importedResponses = new Set < string > ( ) ) { let returnType = "HttpResponse" ; if ( operation . responses && operation . responses . length ) { const responses = [ ... operation . responses , ... ( operation . exceptions || [ ] ) ] ; const responseTypes = responses . filter ( r => r . protocol . http ?. statusCodes && r . protocol . http ?. statusCodes . length ) . map ( r => { const responseName = getResponseTypeName ( operation , r ) ; importedResponses . add ( responseName ) ; return responseName ; } ) ; if ( responseTypes . length ) { returnType = responseTypes . join ( " | " ) ; } } return returnType ; } function getPathFirstRoutesInterfaceDefinition ( paths , sourcefile ) { const signatures = [ ] ; for ( const key of Object . keys ( paths ) ) { generatePathFirstRouteMethodsDefinition ( paths [ key ] . name , paths [ key ] . methods , sourcefile ) ; const pathParams = paths [ key ] . pathParameters ; signatures . push ( { docs : [ `Resource for '${ key . replace ( /}/g , "\\}" ) . replace ( /{/g , "\\{" ) }' has methods for the following verbs: ${ Object . keys ( paths [ key ] . methods ) . join ( ", " ) }` ] , parameters : [ { name : "path" , type : `"${ key }"` } , ... getPathParamDefinitions ( pathParams ) ] , returnType : paths [ key ] . name , kind : StructureKind . CallSignature } ) ; } return signatures ; } function getClientUriParameter ( ) { const { model } = getSession ( ) ; const { parameterName } = transformBaseUrl ( model ) ; return parameterName ; } function generatePathFirstRouteMethodsDefinition ( operationName , methods , file ) { const methodDefinitions = buildMethodDefinitions ( methods ) ; file . addInterface ( { methods : methodDefinitions , name : operationName , isExported : true } ) ; }