Skip to content

Commit

Permalink
Client Operations (#554)
Browse files Browse the repository at this point in the history
* Inline operations in client when there is only one OperationGroup

* Update git ignore

* Update generated test files

* Only inline topLevel operation groups

* Address PR comments

* Small fixes
  • Loading branch information
joheredi authored Jan 29, 2020
1 parent 4cf8616 commit e9b38f0
Show file tree
Hide file tree
Showing 24 changed files with 195 additions and 103 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ dist
package-lock.json
node_modules
test.json
test/**/dist
test/**/esm
test/**/node_modules
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ More information about these can be found [here](https://github.com/Azure/autore
```yaml
version: 3.0.6192
use-extension:
"@autorest/modelerfour": "4.3.142"
"@autorest/modelerfour": "4.4.158"

pipeline:
typescript: # <- name of plugin
Expand Down
13 changes: 5 additions & 8 deletions src/generators/clientContextFileGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function generateClientContext(
);

const sourceFile = project.createSourceFile(
`src/${clientContextFileName}.ts`,
`${clientDetails.srcPath}/${clientContextFileName}.ts`,
undefined,
{
overwrite: true
Expand Down Expand Up @@ -76,11 +76,6 @@ function writeImports(sourceFile: SourceFile) {
namespaceImport: "coreHttp",
moduleSpecifier: "@azure/core-http"
});

sourceFile.addImportDeclaration({
namespaceImport: "Models",
moduleSpecifier: "./models"
});
}

function writePackageInfo(
Expand Down Expand Up @@ -129,10 +124,12 @@ function writeConstructorBody(
writeStatement(writeDefaultOptions(hasCredentials)),
requiredParameters.length ? "// Parameter assignments" : "",
writeStatements(getRequiredParamAssignments(requiredPaams), addBlankLine),
constantParameters ? "// Assigning values to Constant parameters" : "",
constantParameters.length
? "// Assigning values to Constant parameters"
: "",
writeStatements(constantParameters, addBlankLine),
writeStatement(getBaseUriStatement(clientDetails.baseUrl), addBlankLine),
optionalParameters
optionalParameters.length
? "// Replacing parameter defaults with user-provided parameters."
: "",
writeStatements(optionalParameters)
Expand Down
145 changes: 109 additions & 36 deletions src/generators/clientFileGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { Project, PropertyDeclarationStructure } from "ts-morph";
import {
Project,
PropertyDeclarationStructure,
ClassDeclaration,
SourceFile
} from "ts-morph";
import { ClientDetails } from "../models/clientDetails";
import { addOperationSpecs, writeOperations } from "./operationGenerator";
import {
getModelsName,
getMappersName,
Expand All @@ -12,20 +18,46 @@ import {
import { ParameterDetails } from "../models/parameterDetails";
import { ImplementationLocation, SchemaType } from "@azure-tools/codemodel";
import { getCredentialsParameter } from "./utils/parameterUtils";
import { OperationGroupDetails } from "../models/operationDetails";

type OperationDeclarationDetails = { name: string; typeName: string };

export function generateClient(clientDetails: ClientDetails, project: Project) {
const modelsName = getModelsName(clientDetails.className);
const mappersName = getMappersName(clientDetails.className);
const clientContextClassName = `${clientDetails.className}Context`;

// A client has inline operations when it has a toplevel operation group
const hasInlineOperations = clientDetails.operationGroups.some(
og => og.isTopLevel
);

const hasCredentials = !!clientDetails.options.addCredentials;

const clientFile = project.createSourceFile(
`src/${clientDetails.sourceFileName}.ts`,
`${clientDetails.srcPath}/${clientDetails.sourceFileName}.ts`,
undefined,
{
overwrite: true
}
);

(hasCredentials || hasInlineOperations) &&
clientFile.addImportDeclaration({
namespaceImport: "coreHttp",
moduleSpecifier: "@azure/core-http"
});

hasInlineOperations
? clientFile.addImportDeclaration({
namespaceImport: "Parameters",
moduleSpecifier: "./models/parameters"
})
: clientFile.addImportDeclaration({
namespaceImport: "operations",
moduleSpecifier: "./operations"
});

clientFile.addImportDeclaration({
namespaceImport: "Models",
moduleSpecifier: "./models"
Expand All @@ -36,11 +68,6 @@ export function generateClient(clientDetails: ClientDetails, project: Project) {
moduleSpecifier: "./models/mappers"
});

clientFile.addImportDeclaration({
namespaceImport: "operations",
moduleSpecifier: "./operations"
});

clientFile.addImportDeclaration({
namedImports: [clientContextClassName],
moduleSpecifier: `./${clientDetails.sourceFileName}Context`
Expand All @@ -51,22 +78,31 @@ export function generateClient(clientDetails: ClientDetails, project: Project) {
extends: clientContextClassName
});

const operations = clientDetails.operationGroups.map(og => {
return {
name: normalizeName(og.name, NameType.Property),
typeName: `operations.${normalizeName(og.name, NameType.Class)}`
};
writeConstructor(clientDetails, clientClass, hasCredentials);
writeClientOperations(clientFile, clientClass, clientDetails);

clientFile.addExportDeclaration({
leadingTrivia: (writer: any) =>
writer.write("// Operation Specifications\n\n"),
namedExports: [
{ name: clientDetails.className },
{ name: clientContextClassName },
{ name: "Models", alias: modelsName },
{ name: "Mappers", alias: mappersName }
]
});

clientClass.addProperties(
operations.map(op => {
return {
name: op.name,
type: op.typeName
} as PropertyDeclarationStructure;
})
);
const hasCredentials = !!clientDetails.options.addCredentials;
clientDetails.operationGroups.some(og => !og.isTopLevel) &&
clientFile.addExportDeclaration({
moduleSpecifier: "./operations"
});
}

function writeConstructor(
clientDetails: ClientDetails,
classDeclaration: ClassDeclaration,
hasCredentials: boolean
) {
const requiredParams = clientDetails.parameters.filter(
param =>
param.required &&
Expand All @@ -75,7 +111,7 @@ export function generateClient(clientDetails: ClientDetails, project: Project) {
param.schemaType !== SchemaType.Constant
);

const clientConstructor = clientClass.addConstructor({
const clientConstructor = classDeclaration.addConstructor({
docs: [
`Initializes a new instance of the ${clientDetails.className} class.
@param options The parameter options`
Expand All @@ -96,26 +132,63 @@ export function generateClient(clientDetails: ClientDetails, project: Project) {
});

clientConstructor.addStatements([
`super(${getSuperParams(requiredParams, hasCredentials)});`,
...operations.map(
`super(${getSuperParams(requiredParams, hasCredentials)});`
]);

const operationDeclarationDetails: OperationDeclarationDetails[] = getOperationGroupsDeclarationDetails(
clientDetails.operationGroups.filter(og => !og.isTopLevel)
);

clientConstructor.addStatements([
...operationDeclarationDetails.map(
({ name, typeName }) => `this.${name} = new ${typeName}(this)`
)
]);
}

clientFile.addExportDeclaration({
leadingTrivia: (writer: any) =>
writer.write("// Operation Specifications\n\n"),
namedExports: [
{ name: clientDetails.className },
{ name: clientContextClassName },
{ name: "Models", alias: modelsName },
{ name: "Mappers", alias: mappersName }
]
function getOperationGroupsDeclarationDetails(
operationGroups: OperationGroupDetails[]
) {
return operationGroups.map(og => {
return {
name: normalizeName(og.name, NameType.Property),
typeName: `operations.${normalizeName(og.name, NameType.Class)}`
};
});
}

clientFile.addExportDeclaration({
moduleSpecifier: "./operations"
});
function writeClientOperations(
file: SourceFile,
classDeclaration: ClassDeclaration,
clientDetails: ClientDetails
) {
const topLevelGroup = clientDetails.operationGroups.find(og => og.isTopLevel);

// Add top level operation groups as client properties
if (!!topLevelGroup) {
writeOperations(
topLevelGroup,
classDeclaration,
clientDetails.parameters,
true // isInline
);
addOperationSpecs(topLevelGroup, file, clientDetails.parameters);
}

const operationsDeclarationDetails = getOperationGroupsDeclarationDetails(
clientDetails.operationGroups.filter(og => !og.isTopLevel)
);

// Each operation group will have its class
// and the client class will have each group as properties
classDeclaration.addProperties(
operationsDeclarationDetails.map(op => {
return {
name: op.name,
type: op.typeName
} as PropertyDeclarationStructure;
})
);
}

function getSuperParams(
Expand Down
2 changes: 1 addition & 1 deletion src/generators/mappersGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function generateMappers(
project: Project
) {
const mappersFile = project.createSourceFile(
"src/models/mappers.ts",
`${clientDetails.srcPath}/models/mappers.ts`,
undefined,
{ overwrite: true }
);
Expand Down
2 changes: 1 addition & 1 deletion src/generators/modelsGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { ParameterDetails } from "../models/parameterDetails";

export function generateModels(clientDetails: ClientDetails, project: Project) {
const modelsIndexFile = project.createSourceFile(
"src/models/index.ts",
`${clientDetails.srcPath}/models/index.ts`,
undefined,
{ overwrite: true }
);
Expand Down
52 changes: 31 additions & 21 deletions src/generators/operationGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,32 @@ export function generateOperations(
project: Project
): void {
let fileNames: string[] = [];
clientDetails.operationGroups.forEach(operationDetails => {

// Toplevel operations are inlined in the client
const operationGroups = clientDetails.operationGroups.filter(
og => !og.isTopLevel
);

operationGroups.forEach(operationDetails => {
fileNames.push(normalizeName(operationDetails.name, NameType.File));
generateOperation(operationDetails, clientDetails, project);
});

const operationIndexFile = project.createSourceFile(
"src/operations/index.ts",
undefined,
{ overwrite: true }
);
if (operationGroups.length) {
const operationIndexFile = project.createSourceFile(
`${clientDetails.srcPath}/operations/index.ts`,
undefined,
{ overwrite: true }
);

operationIndexFile.addExportDeclarations(
fileNames.map(fileName => {
return {
moduleSpecifier: `./${fileName}`
} as ExportDeclarationStructure;
})
);
operationIndexFile.addExportDeclarations(
fileNames.map(fileName => {
return {
moduleSpecifier: `./${fileName}`
} as ExportDeclarationStructure;
})
);
}
}

/**
Expand All @@ -70,7 +78,7 @@ function generateOperation(
const name = normalizeName(operationGroupDetails.name, NameType.File);

const operationGroupFile = project.createSourceFile(
`src/operations/${name}.ts`,
`${clientDetails.srcPath}/operations/${name}.ts`,
undefined,
{ overwrite: true }
);
Expand Down Expand Up @@ -220,7 +228,7 @@ function addClass(
});

constructorDefinition.addStatements(["this.client = client"]);
writeOperationOverrides(
writeOperations(
operationGroupDetails,
operationGroupClass,
clientDetails.parameters
Expand All @@ -232,13 +240,13 @@ type ParameterWithDescription = OptionalKind<
>;

/**
* Add all the required operations whith their overloads,
* extracted from OperationGroupDetails, to the generated file
* Write operations implementation, extracted from OperationGroupDetails, to the generated file
*/
function writeOperationOverrides(
export function writeOperations(
operationGroupDetails: OperationGroupDetails,
operationGroupClass: ClassDeclaration,
parameters: ParameterDetails[]
parameters: ParameterDetails[],
isInline = false
) {
operationGroupDetails.operations.forEach(operation => {
const responseName = getResponseType(operation);
Expand Down Expand Up @@ -274,7 +282,9 @@ function writeOperationOverrides(

const sendParams = paramDeclarations.map(p => p.name).join(",");
operationMethod.addStatements(
`return this.client.sendOperationRequest({${sendParams}${
`return this${
isInline ? "" : ".client"
}.sendOperationRequest({${sendParams}${
!!sendParams ? "," : ""
} options}, ${operation.name}OperationSpec) as Promise<${responseName}>`
);
Expand Down Expand Up @@ -313,7 +323,7 @@ function generateOperationJSDoc(
* Generates and inserts into the file the operation specs
* for a given operation group
*/
function addOperationSpecs(
export function addOperationSpecs(
operationGroupDetails: OperationGroupDetails,
file: SourceFile,
parameters: ParameterDetails[]
Expand Down
2 changes: 1 addition & 1 deletion src/generators/parametersGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function generateParameters(
project: Project
): void {
const parametersFile = project.createSourceFile(
"src/models/parameters.ts",
`${clientDetails.srcPath}/models/parameters.ts`,
undefined,
{ overwrite: true }
);
Expand Down
2 changes: 1 addition & 1 deletion src/generators/static/packageFileGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function generatePackageJson(
"esm/**/*.js.map",
"esm/**/*.d.ts",
"esm/**/*.d.ts.map",
"src/**/*.ts",
`${clientDetails.srcPath}/**/*.ts`,
"README.md",
"rollup.config.js",
"tsconfig.json"
Expand Down
Loading

0 comments on commit e9b38f0

Please sign in to comment.