Skip to content

Commit

Permalink
Support @projectedName (#3147)
Browse files Browse the repository at this point in the history
* fix a typo

* support projected name

* update shared code

* remove an unused function

* the generation is incorrect

* fixed issue

* add cadl ranch test cases

* regenerate

* move the constants to a standalone file

* fix prettier

---------

Co-authored-by: Mingzhe Huang <[email protected]>
  • Loading branch information
ArcturusZhang and archerzz authored Feb 21, 2023
1 parent 44a88c1 commit aa049aa
Show file tree
Hide file tree
Showing 17 changed files with 1,350 additions and 106 deletions.
3 changes: 2 additions & 1 deletion eng/Generate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ $cadlRanchProjectPaths =
'authentication/union',
'models/property-optional',
'models/property-types',
'models/usage'
'models/usage',
"projection"

if (!($Exclude -contains "CadlRanchProjects"))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ private static InputModelProperty ReadInputModelProperty(ref Utf8JsonReader read
{
var isKnownProperty = reader.TryReadReferenceId(ref isFirstProperty, ref id)
|| reader.TryReadString(nameof(InputModelProperty.Name), ref name)
|| reader.TryReadString(nameof(InputModelProperty.SerializedName), ref name)
|| reader.TryReadString(nameof(InputModelProperty.SerializedName), ref serializedName)
|| reader.TryReadString(nameof(InputModelProperty.Description), ref description)
|| reader.TryReadWithConverter(nameof(InputModelProperty.Type), options, ref propertyType)
|| reader.TryReadBoolean(nameof(InputModelProperty.IsReadOnly), ref isReadOnly)
Expand Down
4 changes: 4 additions & 0 deletions src/AutoRest.CSharp/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@
"commandName": "Project",
"commandLineArgs": "--standalone $(SolutionDir)\\test\\CadlRanchProjects\\models\\usage\\Generated"
},
"cadl-projection": {
"commandName": "Project",
"commandLineArgs": "--standalone $(SolutionDir)\\test\\CadlRanchProjects\\projection\\Generated"
},
"ClientAndOperationGroup-Cadl": {
"commandName": "Project",
"commandLineArgs": "--standalone $(SolutionDir)\\test\\TestProjects\\ClientAndOperationGroup-Cadl\\Generated"
Expand Down
6 changes: 6 additions & 0 deletions src/CADL.Extension/Emitter.Csharp/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

export const projectedNameJsonKey = "json";
export const projectedNameCSharpKey = "csharp";
export const projectedNameClientKey = "client";
170 changes: 74 additions & 96 deletions src/CADL.Extension/Emitter.Csharp/src/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,18 +211,10 @@ function deleteFile(filePath: string) {
function prettierOutput(output: string) {
return output + "\n";
}
function getClient(
clients: InputClient[],
clientName: string
): InputClient | undefined {
for (const client of clients) {
if (client.Name === clientName) return client;
}

return undefined;
}

export function createModel(context: EmitContext<NetEmitterOptions>): any {
export function createModel(
context: EmitContext<NetEmitterOptions>
): CodeModel {
const services = listServices(context.program);
if (services.length === 0) {
services.push({ type: context.program.getGlobalNamespaceType() });
Expand All @@ -241,7 +233,7 @@ export function createModel(context: EmitContext<NetEmitterOptions>): any {
export function createModelForService(
context: EmitContext<NetEmitterOptions>,
service: Service
): any {
): CodeModel {
const program = context.program;
const title = service.title;
const serviceNamespaceType = service.type;
Expand Down Expand Up @@ -305,98 +297,84 @@ export function createModelForService(
let url: string = "";
const convenienceOperations: HttpOperation[] = [];
let lroMonitorOperations: Set<Operation>;
const dpgContext = createDpgContext(context as EmitContext<any>);
try {
//create endpoint parameter from servers
if (servers !== undefined) {
const cadlServers = resolveServers(
program,
servers,
modelMap,
enumMap
);
if (cadlServers.length > 0) {
/* choose the first server as endpoint. */
url = cadlServers[0].url;
urlParameters = cadlServers[0].parameters;
}
}
const [services] = getAllHttpServices(program);
const routes = services[0].operations;
if (routes.length === 0) {
throw `No Route for service ${services[0].namespace.name}`;
const dpgContext = createDpgContext(context);

//create endpoint parameter from servers
if (servers !== undefined) {
const cadlServers = resolveServers(program, servers, modelMap, enumMap);
if (cadlServers.length > 0) {
/* choose the first server as endpoint. */
url = cadlServers[0].url;
urlParameters = cadlServers[0].parameters;
}
console.log("routes:" + routes.length);

lroMonitorOperations = getAllLroMonitorOperations(routes, program);
const clients: InputClient[] = [];
const dpgClients = listClients(dpgContext);
for (const client of dpgClients) {
clients.push(emitClient(client));
const dpgOperationGroups = listOperationGroups(dpgContext, client);
for (const dpgGroup of dpgOperationGroups) {
clients.push(emitClient(dpgGroup, client));
}
}
const [services] = getAllHttpServices(program);
const routes = services[0].operations;
if (routes.length === 0) {
throw `No Route for service ${services[0].namespace.name}`;
}
console.log("routes:" + routes.length);

lroMonitorOperations = getAllLroMonitorOperations(routes, program);
const clients: InputClient[] = [];
const dpgClients = listClients(dpgContext);
for (const client of dpgClients) {
clients.push(emitClient(client));
const dpgOperationGroups = listOperationGroups(dpgContext, client);
for (const dpgGroup of dpgOperationGroups) {
clients.push(emitClient(dpgGroup, client));
}
}

for (const client of clients) {
for (const op of client.Operations) {
const apiVersionIndex = op.Parameters.findIndex(
(value) => value.IsApiVersion
);
if (apiVersionIndex !== -1) {
const apiVersionInOperation =
op.Parameters[apiVersionIndex];
if (!apiVersionInOperation.DefaultValue?.Value) {
apiVersionInOperation.DefaultValue =
apiVersionParam.DefaultValue;
}
/**
* replace to the global apiVerison parameter if the apiVersion defined in the operation is the same as the global service apiVersion parameter.
* Three checkpoints:
* the parameter is query parameter,
* it is client parameter
* it does not has default value, or the default value is included in the global service apiVersion.
*/
if (
apiVersions.has(
apiVersionInOperation.DefaultValue?.Value
) &&
apiVersionInOperation.Kind ===
InputOperationParameterKind.Client &&
apiVersionInOperation.Location ===
apiVersionParam.Location
) {
op.Parameters[apiVersionIndex] = apiVersionParam;
}
} else {
op.Parameters.push(apiVersionParam);
for (const client of clients) {
for (const op of client.Operations) {
const apiVersionIndex = op.Parameters.findIndex(
(value) => value.IsApiVersion
);
if (apiVersionIndex !== -1) {
const apiVersionInOperation = op.Parameters[apiVersionIndex];
if (!apiVersionInOperation.DefaultValue?.Value) {
apiVersionInOperation.DefaultValue =
apiVersionParam.DefaultValue;
}
/**
* replace to the global apiVerison parameter if the apiVersion defined in the operation is the same as the global service apiVersion parameter.
* Three checkpoints:
* the parameter is query parameter,
* it is client parameter
* it does not has default value, or the default value is included in the global service apiVersion.
*/
if (
apiVersions.has(
apiVersionInOperation.DefaultValue?.Value
) &&
apiVersionInOperation.Kind ===
InputOperationParameterKind.Client &&
apiVersionInOperation.Location === apiVersionParam.Location
) {
op.Parameters[apiVersionIndex] = apiVersionParam;
}
} else {
op.Parameters.push(apiVersionParam);
}
}

const usages = getUsages(program, convenienceOperations);
setUsage(usages, modelMap);
setUsage(usages, enumMap);

const clientModel = {
Name: namespace,
Description: description,
ApiVersions: Array.from(apiVersions.values()),
Enums: Array.from(enumMap.values()),
Models: Array.from(modelMap.values()),
Clients: clients,
Auth: auth
} as CodeModel;
return clientModel;
} catch (err) {
if (err instanceof ErrorTypeFoundError) {
return;
} else {
throw err;
}
}

const usages = getUsages(program, convenienceOperations);
setUsage(usages, modelMap);
setUsage(usages, enumMap);

const clientModel = {
Name: namespace,
Description: description,
ApiVersions: Array.from(apiVersions.values()),
Enums: Array.from(enumMap.values()),
Models: Array.from(modelMap.values()),
Clients: clients,
Auth: auth
} as CodeModel;
return clientModel;

function emitClient(
client: Client | OperationGroup,
parent?: Client
Expand Down
27 changes: 19 additions & 8 deletions src/CADL.Extension/Emitter.Csharp/src/lib/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,14 @@ import {
resolveUsages,
Type,
UsageFlags,
UsageTracker,
TrackableType,
getDiscriminator,
IntrinsicType,
isVoidType,
isArrayModelType,
isRecordModelType,
Scalar,
Union
Union,
getProjectedNames
} from "@cadl-lang/compiler";
import { getResourceOperation } from "@cadl-lang/rest";
import {
Expand All @@ -39,6 +38,11 @@ import {
HttpOperation,
isStatusCode
} from "@cadl-lang/rest/http";
import {
projectedNameClientKey,
projectedNameCSharpKey,
projectedNameJsonKey
} from "../constants.js";
import { InputEnumTypeValue } from "../type/InputEnumTypeValue.js";
import { InputModelProperty } from "../type/InputModelProperty.js";
import {
Expand Down Expand Up @@ -72,8 +76,8 @@ export function mapCadlTypeToCSharpInputTypeKind(
case "Enum":
return InputTypeKind.Enum;
case "Number":
let nubmerValue = cadlType.value;
if (nubmerValue % 1 === 0) {
let numberValue = cadlType.value;
if (numberValue % 1 === 0) {
return InputTypeKind.Int32;
}
return InputTypeKind.Float64;
Expand Down Expand Up @@ -512,15 +516,22 @@ export function getInputType(
isReadOnly = true;
}
if (isNeverType(value.type) || isVoidType(value.type)) return;
const projectedNamesMap = getProjectedNames(program, value);
const name =
projectedNamesMap?.get(projectedNameCSharpKey) ??
projectedNamesMap?.get(projectedNameClientKey) ??
value.name;
const serializedName =
projectedNamesMap?.get(projectedNameJsonKey) ?? value.name;
const inputProp = {
Name: value.name,
SerializedName: value.name,
Name: name,
SerializedName: serializedName,
Description: getDoc(program, value) ?? "",
Type: getInputType(program, value.type, models, enums),
IsRequired: !value.optional,
IsReadOnly: isReadOnly,
IsDiscriminator: false
};
} as InputModelProperty;
outputProperties.push(inputProp);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export interface InputModelProperty {
Type: InputType;
IsRequired: boolean;
IsReadOnly: boolean;
IsDiscriminator: boolean;
}
48 changes: 48 additions & 0 deletions test/CadlRanchProjects.Tests/projection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Threading.Tasks;
using AutoRest.TestServer.Tests.Infrastructure;
using Azure;
using NUnit.Framework;
using ProjectedName;
using ProjectedName.Models;

namespace CadlRanchProjects.Tests
{
public class ProjectionTests : CadlRanchTestBase
{
[Test]
public Task ProjectedName_jsonProjection() => Test(async (host) =>
{
Project project = new Project()
{
ProducedBy = "DPG",
};
Response response = await new ProjectedNameClient(host, null).JsonProjectionAsync(project);
Assert.AreEqual(204, response.Status);
});

[Test]
public Task ProjectedName_clientProjection() => Test(async (host) =>
{
Project project = new Project()
{
CreatedBy = "DPG",
};
Response response = await new ProjectedNameClient(host, null).ClientProjectionAsync(project);
Assert.AreEqual(204, response.Status);
});

[Test]
public Task ProjectedName_languageProjection() => Test(async (host) =>
{
Project project = new Project()
{
MadeForCS = "customers"
};
Response response = await new ProjectedNameClient(host, null).LanguageProjectionAsync(project);
Assert.AreEqual(204, response.Status);
});
}
}
10 changes: 10 additions & 0 deletions test/CadlRanchProjects/projection/Generated/Configuration.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit aa049aa

Please sign in to comment.