Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement tsp namespace for http-client-csharp #5443

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4e7b1e3
add clientnamespace to relevant types
ArcturusZhang Dec 25, 2024
6738385
Merge branch 'main' into implement-namespace
ArcturusZhang Jan 2, 2025
73e8358
format code
ArcturusZhang Jan 2, 2025
bf11ad7
model done
ArcturusZhang Jan 2, 2025
0ebfb9c
enum done
ArcturusZhang Jan 2, 2025
3380f8c
fix a bug
ArcturusZhang Jan 2, 2025
22a3a40
regen
ArcturusZhang Jan 2, 2025
c137e51
regen and fix test cases
ArcturusZhang Jan 2, 2025
bd03a7d
regen everything and remove a test until we have a solution
ArcturusZhang Jan 2, 2025
6514f2d
regen everything
ArcturusZhang Jan 3, 2025
b1b5f23
Merge remote-tracking branch 'origin/main' into implement-namespace
ArcturusZhang Jan 8, 2025
66a9da5
add a mechanism to automatically prepend underscore when the namespac…
ArcturusZhang Jan 8, 2025
94287c7
Merge branch 'main' into implement-namespace
ArcturusZhang Jan 8, 2025
66acf99
regen everything
ArcturusZhang Jan 8, 2025
3e651c4
format
ArcturusZhang Jan 8, 2025
fc83d8b
fix linter
ArcturusZhang Jan 8, 2025
271151a
a workaround
ArcturusZhang Jan 8, 2025
c114fda
fix and regen
ArcturusZhang Jan 9, 2025
ade80c5
Merge remote-tracking branch 'origin/main' into implement-namespace
ArcturusZhang Jan 9, 2025
66c349d
fix test case compilation
ArcturusZhang Jan 9, 2025
c426628
now client also honors namespace
ArcturusZhang Jan 9, 2025
fd933a7
it is so complicated to make everything compile
ArcturusZhang Jan 9, 2025
a544bef
make the test project individually for each cadl ranch project
ArcturusZhang Jan 9, 2025
1fe6ef8
Merge remote-tracking branch 'origin/main' into implement-namespace
ArcturusZhang Jan 14, 2025
f01d39d
some progress
ArcturusZhang Jan 14, 2025
4c549d8
remove usemodelnamespace and modelnamespace configuration
ArcturusZhang Jan 14, 2025
cc6244a
Merge remote-tracking branch 'origin/main' into implement-namespace
ArcturusZhang Jan 17, 2025
b79e1d4
remove the added csproj for test projects
ArcturusZhang Jan 17, 2025
dc641fe
revert unnecessary changes
ArcturusZhang Jan 17, 2025
9f72224
update
ArcturusZhang Jan 17, 2025
141db44
regen everything
ArcturusZhang Jan 17, 2025
8dcff70
fix all test cases
ArcturusZhang Jan 17, 2025
13b0401
fix a few unit tests
ArcturusZhang Jan 17, 2025
6a06f2c
revert changes to factory method for input model
ArcturusZhang Jan 17, 2025
39a9676
fix all unit tests
ArcturusZhang Jan 17, 2025
3132c21
fix cadl ranch test issues
ArcturusZhang Jan 17, 2025
9a05ba1
format
ArcturusZhang Jan 17, 2025
8730887
Merge branch 'main' into implement-namespace
ArcturusZhang Jan 17, 2025
ecb32ea
fix test failures and regen
ArcturusZhang Jan 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import { InputParameter } from "../type/input-parameter.js";
import { InputEnumType, InputModelType, InputType } from "../type/input-type.js";
import { RequestLocation } from "../type/request-location.js";
import { SdkTypeMap } from "../type/sdk-type-map.js";
import { fromSdkType } from "./converter.js";
import { reportDiagnostic } from "./lib.js";
import { Logger } from "./logger.js";
import { navigateModels } from "./model.js";
import { fromSdkServiceMethod, getParameterDefaultValue } from "./operation-converter.js";
import { processServiceAuthentication } from "./service-authentication.js";
import { fromSdkType } from "./type-converter.js";

export function createModel(sdkContext: SdkContext<NetEmitterOptions>): CodeModel {
const sdkPackage = sdkContext.sdkPackage;
Expand All @@ -51,8 +51,9 @@ export function createModel(sdkContext: SdkContext<NetEmitterOptions>): CodeMode
? sdkApiVersionEnums[0].values.map((v) => v.value as string).flat()
: rootClients[0].apiVersions;

// this is a set tracking the bad namespace segments
const inputClients: InputClient[] = [];
fromSdkClients(rootClients, inputClients, []);
fromSdkClients(sdkContext, rootClients, inputClients, []);

const clientModel: CodeModel = {
Name: sdkPackage.rootNamespace,
Expand All @@ -62,33 +63,53 @@ export function createModel(sdkContext: SdkContext<NetEmitterOptions>): CodeMode
Clients: inputClients,
Auth: processServiceAuthentication(sdkContext, sdkPackage),
};

return clientModel;

function fromSdkClients(
sdkContext: SdkContext<NetEmitterOptions>,
clients: SdkClientType<SdkHttpOperation>[],
inputClients: InputClient[],
parentClientNames: string[],
) {
for (const client of clients) {
const inputClient = emitClient(client, parentClientNames);
const inputClient = fromSdkClient(sdkContext, client, parentClientNames);
inputClients.push(inputClient);
const subClients = client.methods
.filter((m) => m.kind === "clientaccessor")
.map((m) => m.response as SdkClientType<SdkHttpOperation>);
parentClientNames.push(inputClient.Name);
fromSdkClients(subClients, inputClients, parentClientNames);
fromSdkClients(sdkContext, subClients, inputClients, parentClientNames);
parentClientNames.pop();
}
}

function emitClient(client: SdkClientType<SdkHttpOperation>, parentNames: string[]): InputClient {
function fromSdkClient(
sdkContext: SdkContext<NetEmitterOptions>,
client: SdkClientType<SdkHttpOperation>,
parentNames: string[],
): InputClient {
const endpointParameter = client.initialization.properties.find(
(p) => p.kind === "endpoint",
) as SdkEndpointParameter;
const uri = getMethodUri(endpointParameter);
const clientParameters = fromSdkEndpointParameter(endpointParameter);
const clientName = getClientName(client, parentNames);
// see if this namespace is a sub-namespace of an existing bad namespace
const segments = client.clientNamespace.split(".");
const lastSegment = segments[segments.length - 1];
if (lastSegment === clientName) {
// this segment is bad
reportDiagnostic(sdkContext.program, {
code: "bad-namespace",
format: { clientNamespace: client.clientNamespace, clientName },
target: client.__raw.type ?? NoTarget,
});
}

return {
Name: getClientName(client, parentNames),
Name: clientName,
ClientNamespace: client.clientNamespace,
Summary: client.summary,
Doc: client.doc,
Operations: client.methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import {
} from "../type/input-type.js";
import { OperationResponse } from "../type/operation-response.js";
import { SdkTypeMap } from "../type/sdk-type-map.js";
import { fromSdkType } from "./converter.js";
import { fromSdkType } from "./type-converter.js";

export function fromSdkHttpExamples(
sdkContext: SdkContext<NetEmitterOptions>,
Expand Down
6 changes: 6 additions & 0 deletions packages/http-client-csharp/emitter/src/lib/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ const $lib = createTypeSpecLibrary({
default: paramMessage`${"message"}`,
},
},
"bad-namespace": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "client-namespace-conflict"?

severity: "warning",
messages: {
default: paramMessage`bad namespace ${"clientNamespace"} for client ${"clientName"}, please use @clientName to specify a different name for the client.`,
}
}
},
emitter: {
options: NetEmitterOptionsSchema,
Expand Down
10 changes: 5 additions & 5 deletions packages/http-client-csharp/emitter/src/lib/logger.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

import { NoTarget, Program, Tracer } from "@typespec/compiler";
import { DiagnosticTarget, NoTarget, Program, Tracer } from "@typespec/compiler";
import { getTracer, reportDiagnostic } from "./lib.js";
import { LoggerLevel } from "./log-level.js";

Expand Down Expand Up @@ -63,19 +63,19 @@ export class Logger {
}
}

warn(message: string): void {
warn(message: string, target?: DiagnosticTarget | typeof NoTarget): void {
reportDiagnostic(this.program, {
code: "general-warning",
format: { message: message },
target: NoTarget,
target: target ?? NoTarget,
});
}

error(message: string): void {
error(message: string, target?: DiagnosticTarget | typeof NoTarget): void {
reportDiagnostic(this.program, {
code: "general-error",
format: { message: message },
target: NoTarget,
target: target ?? NoTarget,
});
}
}
2 changes: 1 addition & 1 deletion packages/http-client-csharp/emitter/src/lib/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import { NetEmitterOptions } from "../options.js";
import { InputType } from "../type/input-type.js";
import { LiteralTypeContext } from "../type/literal-type-context.js";
import { SdkTypeMap } from "../type/sdk-type-map.js";
import { fromSdkEnumType, fromSdkModelType, fromSdkType } from "./converter.js";
import { Logger } from "./logger.js";
import { fromSdkEnumType, fromSdkModelType, fromSdkType } from "./type-converter.js";

/**
* If type is an anonymous model, tries to find a named model that has the same
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ import { OperationResponse } from "../type/operation-response.js";
import { RequestLocation } from "../type/request-location.js";
import { parseHttpRequestMethod } from "../type/request-method.js";
import { SdkTypeMap } from "../type/sdk-type-map.js";
import { fromSdkModelType, fromSdkType } from "./converter.js";
import { getExternalDocs, getOperationId } from "./decorators.js";
import { fromSdkHttpExamples } from "./example-converter.js";
import { Logger } from "./logger.js";
import { fromSdkModelType, fromSdkType } from "./type-converter.js";
import { isSdkPathParameter } from "./utils.js";

export function fromSdkServiceMethod(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function fromSdkType(
retVar = {
kind: "nullable",
type: inputType,
clientNamespace: sdkType.clientNamespace,
};
break;
case "model":
Expand Down Expand Up @@ -115,6 +116,7 @@ export function fromSdkModelType(
inputModelType = {
kind: "model",
name: modelTypeName,
clientNamespace: modelType.clientNamespace,
crossLanguageDefinitionId: modelType.crossLanguageDefinitionId,
access: getAccessOverride(
context,
Expand Down Expand Up @@ -142,6 +144,7 @@ export function fromSdkModelType(
const ourProperty = fromSdkModelProperty(property, {
ModelName: modelTypeName,
Usage: modelType.usage,
ClientNamespace: modelType.clientNamespace,
} as LiteralTypeContext);
propertiesDict.set(property, ourProperty);
}
Expand Down Expand Up @@ -226,6 +229,7 @@ export function fromSdkEnumType(
context,
enumType.__raw as any,
) /* when tcgc provide a way to identify if the access is override or not, we can get the accessibility from the enumType.access,*/,
clientNamespace: enumType.clientNamespace,
deprecation: enumType.deprecation,
summary: enumType.summary,
doc: enumType.doc,
Expand Down Expand Up @@ -303,6 +307,7 @@ function fromUnionType(
kind: "union",
name: union.name,
variantTypes: variantTypes,
clientNamespace: union.clientNamespace,
decorators: union.decorators,
};
}
Expand Down Expand Up @@ -340,6 +345,7 @@ function fromSdkConstantType(
values: values,
crossLanguageDefinitionId: "",
access: undefined,
clientNamespace: literalTypeContext.ClientNamespace,
doc: `The ${enumName}`, // TODO -- what should we put here?
isFixed: false,
isFlags: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Protocols } from "./protocols.js";

export interface InputClient {
Name: string;
ClientNamespace: string;
Summary?: string;
Doc?: string;
Operations: InputOperation[];
Expand Down
4 changes: 4 additions & 0 deletions packages/http-client-csharp/emitter/src/type/input-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export interface InputUnionType extends InputTypeBase {
kind: "union";
name: string;
variantTypes: InputType[];
clientNamespace: string;
}

export function isInputUnionType(type: InputType): type is InputUnionType {
Expand All @@ -91,6 +92,7 @@ export interface InputModelType extends InputTypeBase {
crossLanguageDefinitionId: string;
access?: AccessFlags;
usage: UsageFlags;
clientNamespace: string;
additionalProperties?: InputType;
discriminatorValue?: string;
discriminatedSubtypes?: Record<string, InputModelType>;
Expand Down Expand Up @@ -124,6 +126,7 @@ export interface InputEnumType extends InputTypeBase {
isFlags: boolean;
usage: UsageFlags;
access?: AccessFlags;
clientNamespace: string;
}

export interface InputEnumTypeValue extends InputTypeBase {
Expand All @@ -137,6 +140,7 @@ export interface InputEnumTypeValue extends InputTypeBase {
export interface InputNullableType extends InputTypeBase {
kind: "nullable";
type: InputType;
clientNamespace: string;
}

export function isInputEnumType(type: InputType): type is InputEnumType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export interface LiteralTypeContext {
ModelName: string;
PropertyName: string;
Usage: UsageFlags;
ClientNamespace: string;
}
3 changes: 2 additions & 1 deletion packages/http-client-csharp/eng/scripts/Generate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ $failingSpecs = @(
Join-Path 'http' 'payload' 'xml'
Join-Path 'http' 'type' 'model' 'flatten'
Join-Path 'http' 'type' 'model' 'templated'
Join-Path 'http' 'client' 'naming'
)

$azureAllowSpecs = @(
Join-Path 'http' 'client' 'naming'
# Join-Path 'http' 'client' 'naming'
Join-Path 'http' 'client' 'structure' 'client-operation-group'
Join-Path 'http' 'client' 'structure' 'default'
Join-Path 'http' 'client' 'structure' 'multi-client'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ public ClientProvider(InputClient inputClient)
_subClients = new(GetSubClients);
}

protected override string BuildNamespace() => string.IsNullOrEmpty(_inputClient.ClientNamespace) ?
base.BuildNamespace() :
ClientModelPlugin.Instance.TypeFactory.GetCleanNameSpace(_inputClient.ClientNamespace);

private IReadOnlyList<ParameterProvider> GetSubClientInternalConstructorParameters()
{
var subClientParameters = new List<ParameterProvider>
Expand Down Expand Up @@ -311,7 +315,7 @@ protected override ConstructorProvider[] BuildConstructors()
AppendConstructors(_apiKeyAuthFields, primaryConstructors, secondaryConstructors);
}
// if there is oauth2 auth
if (_oauth2Fields!= null)
if (_oauth2Fields != null)
{
AppendConstructors(_oauth2Fields, primaryConstructors, secondaryConstructors);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public RestClientProvider(InputClient inputClient, ClientProvider clientProvider

protected override string BuildName() => _inputClient.Name.ToCleanName();

protected override string BuildNamespace() => ClientProvider.Namespace;

protected override PropertyProvider[] BuildProperties()
{
return [.. _pipelineMessage20xClassifiers.Values.OrderBy(v => v.Name)];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void PreVisitsMethods()
var inputOperation = InputFactory.Operation("testOperation", parameters: [param], responses: [InputFactory.OperationResponse(bodytype: InputPrimitiveType.Any)]);
var inputClient = InputFactory.Client("fooClient", operations: [inputOperation], parameters: [param]);
_mockInputLibrary.Setup(l => l.InputNamespace).Returns(InputFactory.Namespace(
"test library",
"Sample",
models: [inputModel],
clients: [inputClient]));

Expand All @@ -57,7 +57,7 @@ public void PreVisitsClients()

var inputClient = InputFactory.Client("fooClient");
_mockInputLibrary.Setup(l => l.InputNamespace).Returns(InputFactory.Namespace(
"test library",
"Sample",
clients: [inputClient]));

var mockOutputLibrary = new Mock<ScmOutputLibrary> { CallBase = true };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

using System.ClientModel;
using System.IO;
using NUnit.Framework;
using UnbrandedTypeSpec.Models;
using Microsoft.Generator.CSharp.Tests.Common;
using NUnit.Framework;
using UnbrandedTypeSpec;

namespace Microsoft.Generator.CSharp.ClientModel.Tests.ModelReaderWriterValidation.TestProjects.Unbranded_TypeSpec
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Text.Json;
using Microsoft.Generator.CSharp.Tests.Common;
using NUnit.Framework;
using UnbrandedTypeSpec.Models;
using UnbrandedTypeSpec;

namespace Microsoft.Generator.CSharp.ClientModel.Tests.ModelReaderWriterValidation.TestProjects.Unbranded_TypeSpec
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.IO;
using Microsoft.Generator.CSharp.Tests.Common;
using NUnit.Framework;
using UnbrandedTypeSpec.Models;
using UnbrandedTypeSpec;

namespace Microsoft.Generator.CSharp.ClientModel.Tests.ModelReaderWriterValidation.TestProjects.Unbranded_TypeSpec
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.IO;
using Microsoft.Generator.CSharp.Tests.Common;
using NUnit.Framework;
using UnbrandedTypeSpec.Models;
using UnbrandedTypeSpec;

namespace Microsoft.Generator.CSharp.ClientModel.Tests.ModelReaderWriterValidation.TestProjects.Unbranded_TypeSpec
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Text.Json;
using Microsoft.Generator.CSharp.Tests.Common;
using NUnit.Framework;
using UnbrandedTypeSpec.Models;
using UnbrandedTypeSpec;

namespace Microsoft.Generator.CSharp.ClientModel.Tests.ModelReaderWriterValidation.TestProjects.Unbranded_TypeSpec
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Text.Json;
using Microsoft.Generator.CSharp.Tests.Common;
using NUnit.Framework;
using UnbrandedTypeSpec.Models;
using UnbrandedTypeSpec;

namespace Microsoft.Generator.CSharp.ClientModel.Tests.ModelReaderWriterValidation.TestProjects.Unbranded_TypeSpec
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void ValidateBodyOfRestClientOperationIsOverridden()

private static ClientProvider CreateTestClient()
{
var client = InputFactory.Client("TestClient", [InputFactory.Operation("foo")]);
var client = InputFactory.Client("TestClient", operations: [InputFactory.Operation("foo")]);
MockHelpers.LoadMockPlugin(clientPipelineApi: TestClientPipelineApi.Instance);
var clientProvider = ClientModelPlugin.Instance.TypeFactory.CreateClient(client);
Assert.IsNotNull(clientProvider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void ValidateBodyOfClientOperationIsOverridden()

private static ClientProvider CreateMockClientProvider()
{
var client = InputFactory.Client("TestClient", [InputFactory.Operation("foo")]);
var client = InputFactory.Client("TestClient", operations: [InputFactory.Operation("foo")]);
MockHelpers.LoadMockPlugin(clientResponseApi: TestClientResponseApi.Instance);
var clientProvider = ClientModelPlugin.Instance.TypeFactory.CreateClient(client);
Assert.IsNotNull(clientProvider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public void ValidateBodyOfProcessMessageIsOverridden()

private static ClientProvider CreateTestClient()
{
var client = InputFactory.Client("TestClient", [InputFactory.Operation("foo")]);
var client = InputFactory.Client("TestClient", operations: [InputFactory.Operation("foo")]);
MockHelpers.LoadMockPlugin(httpMessageApi: TestHttpMessageApi.Instance);
var clientProvider = ClientModelPlugin.Instance.TypeFactory.CreateClient(client);
Assert.IsNotNull(clientProvider);
Expand Down
Loading
Loading