Skip to content

Commit ce03a0a

Browse files
Add External property to InputType to align with TCGC
Co-authored-by: JoshLove-msft <[email protected]>
1 parent 4c0f22d commit ce03a0a

File tree

9 files changed

+181
-46
lines changed

9 files changed

+181
-46
lines changed

packages/http-client-csharp/emitter/src/lib/type-converter.ts

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
InputPrimitiveType,
3939
InputType,
4040
InputUnionType,
41+
ExternalTypeInfo,
4142
} from "../type/input-type.js";
4243
import { isReadOnly } from "./utils.js";
4344

@@ -81,36 +82,39 @@ export function fromSdkType<T extends SdkType>(
8182
return retVar as any;
8283
}
8384

84-
// Check if this type references an external type
85-
if ((sdkType as any).external) {
86-
retVar = fromSdkExternalType(sdkContext, sdkType);
87-
sdkContext.__typeCache.updateSdkTypeReferences(sdkType, retVar);
88-
return retVar as any;
89-
}
85+
// Extract external type information if present
86+
const externalInfo = (sdkType as any).external
87+
? {
88+
identity: (sdkType as any).external.identity,
89+
package: (sdkType as any).external.package,
90+
minVersion: (sdkType as any).external.minVersion,
91+
}
92+
: undefined;
9093

9194
switch (sdkType.kind) {
9295
case "nullable":
9396
const nullableType: InputNullableType = {
9497
kind: "nullable",
9598
type: fromSdkType(sdkContext, sdkType.type, sdkProperty, namespace),
9699
namespace: sdkType.namespace,
100+
external: externalInfo,
97101
};
98102
retVar = nullableType;
99103
break;
100104
case "model":
101-
retVar = fromSdkModelType(sdkContext, sdkType);
105+
retVar = fromSdkModelType(sdkContext, sdkType, externalInfo);
102106
break;
103107
case "enum":
104-
retVar = fromSdkEnumType(sdkContext, sdkType);
108+
retVar = fromSdkEnumType(sdkContext, sdkType, externalInfo);
105109
break;
106110
case "enumvalue":
107111
retVar = fromSdkEnumValueType(sdkContext, sdkType);
108112
break;
109113
case "dict":
110-
retVar = fromSdkDictionaryType(sdkContext, sdkType);
114+
retVar = fromSdkDictionaryType(sdkContext, sdkType, externalInfo);
111115
break;
112116
case "array":
113-
retVar = fromSdkArrayType(sdkContext, sdkType);
117+
retVar = fromSdkArrayType(sdkContext, sdkType, externalInfo);
114118
break;
115119
case "constant":
116120
if (
@@ -120,20 +124,20 @@ export function fromSdkType<T extends SdkType>(
120124
sdkType.valueType.kind !== "boolean"
121125
) {
122126
// turn the constant into an extensible enum
123-
retVar = createEnumType(sdkContext, sdkType, namespace!);
127+
retVar = createEnumType(sdkContext, sdkType, namespace!, externalInfo);
124128
} else {
125129
retVar = fromSdkConstantType(sdkContext, sdkType);
126130
}
127131
break;
128132
case "union":
129-
retVar = fromUnionType(sdkContext, sdkType);
133+
retVar = fromUnionType(sdkContext, sdkType, externalInfo);
130134
break;
131135
case "utcDateTime":
132136
case "offsetDateTime":
133-
retVar = fromSdkDateTimeType(sdkContext, sdkType);
137+
retVar = fromSdkDateTimeType(sdkContext, sdkType, externalInfo);
134138
break;
135139
case "duration":
136-
retVar = fromSdkDurationType(sdkContext, sdkType);
140+
retVar = fromSdkDurationType(sdkContext, sdkType, externalInfo);
137141
break;
138142
case "tuple":
139143
sdkContext.logger.reportDiagnostic({
@@ -146,6 +150,7 @@ export function fromSdkType<T extends SdkType>(
146150
name: "tuple",
147151
crossLanguageDefinitionId: "",
148152
decorators: sdkType.decorators,
153+
external: externalInfo,
149154
};
150155
retVar = tupleType;
151156
break;
@@ -165,11 +170,12 @@ export function fromSdkType<T extends SdkType>(
165170
name: "credential",
166171
crossLanguageDefinitionId: "",
167172
decorators: sdkType.decorators,
173+
external: externalInfo,
168174
};
169175
retVar = credentialType;
170176
break;
171177
default:
172-
retVar = fromSdkBuiltInType(sdkContext, sdkType);
178+
retVar = fromSdkBuiltInType(sdkContext, sdkType, externalInfo);
173179
break;
174180
}
175181

@@ -181,6 +187,7 @@ export function fromSdkType<T extends SdkType>(
181187
function fromSdkModelType(
182188
sdkContext: CSharpEmitterContext,
183189
modelType: SdkModelType,
190+
externalInfo?: ExternalTypeInfo,
184191
): InputModelType {
185192
// get all unique decorators for the model type from the namespace level and the model level
186193
let decorators: DecoratorInfo[] = modelType.decorators;
@@ -200,6 +207,7 @@ function fromSdkModelType(
200207
summary: modelType.summary,
201208
discriminatorValue: modelType.discriminatorValue,
202209
decorators: decorators,
210+
external: externalInfo,
203211
} as InputModelType;
204212

205213
sdkContext.__typeCache.updateSdkTypeReferences(modelType, inputModelType);
@@ -281,14 +289,15 @@ function fromSdkModelProperty(
281289
return property;
282290
}
283291

284-
function fromSdkEnumType(sdkContext: CSharpEmitterContext, enumType: SdkEnumType): InputEnumType {
285-
return createEnumType(sdkContext, enumType, enumType.namespace);
292+
function fromSdkEnumType(sdkContext: CSharpEmitterContext, enumType: SdkEnumType, externalInfo?: ExternalTypeInfo): InputEnumType {
293+
return createEnumType(sdkContext, enumType, enumType.namespace, externalInfo);
286294
}
287295

288296
function createEnumType(
289297
sdkContext: CSharpEmitterContext,
290298
sdkType: SdkConstantType | SdkEnumType,
291299
namespace: string,
300+
externalInfo?: ExternalTypeInfo,
292301
): InputEnumType {
293302
const values: InputEnumValueType[] = [];
294303

@@ -313,6 +322,7 @@ function createEnumType(
313322
// constantType.usage, TODO - constant type now does not have usage. TCGC will add it later
314323
usage: sdkType.kind === "enum" ? sdkType.usage : UsageFlags.None,
315324
decorators: sdkType.decorators,
325+
external: externalInfo,
316326
};
317327

318328
sdkContext.__typeCache.updateSdkTypeReferences(sdkType, inputEnumType);
@@ -331,6 +341,7 @@ function createEnumType(
331341
function fromSdkDateTimeType(
332342
sdkContext: CSharpEmitterContext,
333343
dateTimeType: SdkDateTimeType,
344+
externalInfo?: ExternalTypeInfo,
334345
): InputDateTimeType {
335346
return {
336347
kind: dateTimeType.kind,
@@ -340,12 +351,14 @@ function fromSdkDateTimeType(
340351
crossLanguageDefinitionId: dateTimeType.crossLanguageDefinitionId,
341352
baseType: dateTimeType.baseType ? fromSdkType(sdkContext, dateTimeType.baseType) : undefined,
342353
decorators: dateTimeType.decorators,
354+
external: externalInfo,
343355
};
344356
}
345357

346358
function fromSdkDurationType(
347359
sdkContext: CSharpEmitterContext,
348360
durationType: SdkDurationType,
361+
externalInfo?: ExternalTypeInfo,
349362
): InputDurationType {
350363
return {
351364
kind: durationType.kind,
@@ -355,12 +368,14 @@ function fromSdkDurationType(
355368
crossLanguageDefinitionId: durationType.crossLanguageDefinitionId,
356369
baseType: durationType.baseType ? fromSdkType(sdkContext, durationType.baseType) : undefined,
357370
decorators: durationType.decorators,
371+
external: externalInfo,
358372
};
359373
}
360374

361375
function fromSdkBuiltInType(
362376
sdkContext: CSharpEmitterContext,
363377
builtInType: SdkBuiltInType,
378+
externalInfo?: ExternalTypeInfo,
364379
): InputPrimitiveType {
365380
return {
366381
kind: builtInType.kind,
@@ -369,10 +384,11 @@ function fromSdkBuiltInType(
369384
crossLanguageDefinitionId: builtInType.crossLanguageDefinitionId,
370385
baseType: builtInType.baseType ? fromSdkType(sdkContext, builtInType.baseType) : undefined,
371386
decorators: builtInType.decorators,
387+
external: externalInfo,
372388
};
373389
}
374390

375-
function fromUnionType(sdkContext: CSharpEmitterContext, union: SdkUnionType): InputUnionType {
391+
function fromUnionType(sdkContext: CSharpEmitterContext, union: SdkUnionType, externalInfo?: ExternalTypeInfo): InputUnionType {
376392
const variantTypes: InputType[] = [];
377393
for (const value of union.variantTypes) {
378394
const variantType = fromSdkType(sdkContext, value);
@@ -385,6 +401,7 @@ function fromUnionType(sdkContext: CSharpEmitterContext, union: SdkUnionType): I
385401
variantTypes: variantTypes,
386402
namespace: union.namespace,
387403
decorators: union.decorators,
404+
external: externalInfo,
388405
};
389406
}
390407

@@ -441,25 +458,29 @@ function createEnumValueType(
441458
function fromSdkDictionaryType(
442459
sdkContext: CSharpEmitterContext,
443460
dictionaryType: SdkDictionaryType,
461+
externalInfo?: ExternalTypeInfo,
444462
): InputDictionaryType {
445463
return {
446464
kind: "dict",
447465
keyType: fromSdkType(sdkContext, dictionaryType.keyType),
448466
valueType: fromSdkType(sdkContext, dictionaryType.valueType),
449467
decorators: dictionaryType.decorators,
468+
external: externalInfo,
450469
};
451470
}
452471

453472
function fromSdkArrayType(
454473
sdkContext: CSharpEmitterContext,
455474
arrayType: SdkArrayType,
475+
externalInfo?: ExternalTypeInfo,
456476
): InputArrayType {
457477
return {
458478
kind: "array",
459479
name: arrayType.name,
460480
valueType: fromSdkType(sdkContext, arrayType.valueType),
461481
crossLanguageDefinitionId: arrayType.crossLanguageDefinitionId,
462482
decorators: arrayType.decorators,
483+
external: externalInfo,
463484
};
464485
}
465486

@@ -471,20 +492,6 @@ function fromSdkEndpointType(): InputPrimitiveType {
471492
};
472493
}
473494

474-
function fromSdkExternalType(
475-
sdkContext: CSharpEmitterContext,
476-
sdkType: SdkType,
477-
): InputExternalType {
478-
const external = (sdkType as any).external;
479-
return {
480-
kind: "external",
481-
identity: external.identity,
482-
package: external.package,
483-
minVersion: external.minVersion,
484-
decorators: sdkType.decorators,
485-
};
486-
}
487-
488495
/**
489496
* @beta
490497
*/

packages/http-client-csharp/emitter/src/type/input-type.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ import { InputParameterScope } from "./input-parameter-scope.js";
1616
import { InputServiceMethod } from "./input-service-method.js";
1717
import { RequestLocation } from "./request-location.js";
1818

19+
/**
20+
* External type information for types that map to external library types.
21+
* @beta
22+
*/
23+
export interface ExternalTypeInfo {
24+
identity: string;
25+
package?: string;
26+
minVersion?: string;
27+
}
28+
1929
/**
2030
* The input client type for the CSharp emitter.
2131
* @beta
@@ -54,6 +64,7 @@ interface InputTypeBase extends DecoratedType {
5464
summary?: string;
5565
doc?: string;
5666
deprecation?: string;
67+
external?: ExternalTypeInfo;
5768
}
5869

5970
export type InputType =

packages/http-client-csharp/emitter/test/Unit/type-converter.test.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,15 @@ describe("External types", () => {
127127
const prop = testModel.properties.find((p) => p.name === "prop");
128128
ok(prop, "prop should exist");
129129

130-
// The type should be an external type
131-
strictEqual(prop.type.kind, "external");
132-
strictEqual((prop.type as any).identity, "Azure.Core.Expressions.DataFactoryExpression");
133-
strictEqual((prop.type as any).package, "Azure.Core.Expressions");
134-
strictEqual((prop.type as any).minVersion, "1.0.0");
130+
// The type should remain a union but with external info
131+
strictEqual(prop.type.kind, "union");
132+
ok((prop.type as any).external, "Type should have external info");
133+
strictEqual((prop.type as any).external.identity, "Azure.Core.Expressions.DataFactoryExpression");
134+
strictEqual((prop.type as any).external.package, "Azure.Core.Expressions");
135+
strictEqual((prop.type as any).external.minVersion, "1.0.0");
136+
// Verify union variants are preserved
137+
ok((prop.type as any).variantTypes, "Union should have variant types");
138+
strictEqual((prop.type as any).variantTypes.length, 2, "Union should have 2 variant types");
135139
});
136140

137141
it("should convert external type on model", async () => {
@@ -165,10 +169,11 @@ describe("External types", () => {
165169
const jsonElementProp = testModel.properties.find((p) => p.name === "jsonElement");
166170
ok(jsonElementProp, "jsonElement property should exist");
167171

168-
// The type should be an external type
169-
strictEqual(jsonElementProp.type.kind, "external");
170-
strictEqual((jsonElementProp.type as any).identity, "System.Text.Json.JsonElement");
171-
strictEqual((jsonElementProp.type as any).package, "System.Text.Json");
172-
strictEqual((jsonElementProp.type as any).minVersion, "8.0.0");
172+
// The type should remain a model but with external info
173+
strictEqual(jsonElementProp.type.kind, "model");
174+
ok((jsonElementProp.type as any).external, "Type should have external info");
175+
strictEqual((jsonElementProp.type as any).external.identity, "System.Text.Json.JsonElement");
176+
strictEqual((jsonElementProp.type as any).external.package, "System.Text.Json");
177+
strictEqual((jsonElementProp.type as any).external.minVersion, "8.0.0");
173178
});
174179
});
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
namespace Microsoft.TypeSpec.Generator.Input
5+
{
6+
/// <summary>
7+
/// External type information for types that map to external library types.
8+
/// </summary>
9+
public sealed class ExternalTypeInfo
10+
{
11+
/// <summary>
12+
/// Construct a new <see cref="ExternalTypeInfo"/> instance
13+
/// </summary>
14+
/// <param name="identity">The fully qualified name of the external type.</param>
15+
/// <param name="package">The package that exports the external type.</param>
16+
/// <param name="minVersion">The minimum version of the package.</param>
17+
public ExternalTypeInfo(string identity, string? package, string? minVersion)
18+
{
19+
Identity = identity;
20+
Package = package;
21+
MinVersion = minVersion;
22+
}
23+
24+
/// <summary>
25+
/// The fully qualified name of the external type. For example, "Azure.Core.Expressions.DataFactoryExpression"
26+
/// </summary>
27+
public string Identity { get; }
28+
29+
/// <summary>
30+
/// The package that exports the external type. For example, "Azure.Core.Expressions"
31+
/// </summary>
32+
public string? Package { get; }
33+
34+
/// <summary>
35+
/// The minimum version of the package to use for the external type. For example, "1.0.0"
36+
/// </summary>
37+
public string? MinVersion { get; }
38+
}
39+
}

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputType.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ protected InputType(string name)
2121

2222
public string Name { get; internal set; }
2323
public IReadOnlyList<InputDecoratorInfo> Decorators { get; internal set; } = new List<InputDecoratorInfo>();
24+
public ExternalTypeInfo? External { get; internal set; }
2425

2526
internal InputType GetCollectionEquivalent(InputType inputType)
2627
{

0 commit comments

Comments
 (0)