Skip to content
52 changes: 30 additions & 22 deletions packages/http-client-csharp/emitter/src/lib/type-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
InputDurationType,
InputEnumType,
InputEnumValueType,
InputExternalType,
InputExternalTypeMetadata,
InputLiteralType,
InputModelProperty,
InputModelType,
Expand Down Expand Up @@ -81,19 +81,13 @@ export function fromSdkType<T extends SdkType>(
return retVar as any;
}

// Check if this type references an external type
if ((sdkType as any).external) {
retVar = fromSdkExternalType(sdkContext, sdkType);
sdkContext.__typeCache.updateSdkTypeReferences(sdkType, retVar);
return retVar as any;
}

switch (sdkType.kind) {
case "nullable":
const nullableType: InputNullableType = {
kind: "nullable",
type: fromSdkType(sdkContext, sdkType.type, sdkProperty, namespace),
namespace: sdkType.namespace,
external: fromSdkExternalTypeInfo(sdkType),
};
retVar = nullableType;
break;
Expand Down Expand Up @@ -146,6 +140,7 @@ export function fromSdkType<T extends SdkType>(
name: "tuple",
crossLanguageDefinitionId: "",
decorators: sdkType.decorators,
external: fromSdkExternalTypeInfo(sdkType),
};
retVar = tupleType;
break;
Expand All @@ -165,6 +160,7 @@ export function fromSdkType<T extends SdkType>(
name: "credential",
crossLanguageDefinitionId: "",
decorators: sdkType.decorators,
external: fromSdkExternalTypeInfo(sdkType),
};
retVar = credentialType;
break;
Expand Down Expand Up @@ -200,6 +196,7 @@ function fromSdkModelType(
summary: modelType.summary,
discriminatorValue: modelType.discriminatorValue,
decorators: decorators,
external: fromSdkExternalTypeInfo(modelType),
} as InputModelType;

sdkContext.__typeCache.updateSdkTypeReferences(modelType, inputModelType);
Expand Down Expand Up @@ -313,6 +310,7 @@ function createEnumType(
// constantType.usage, TODO - constant type now does not have usage. TCGC will add it later
usage: sdkType.kind === "enum" ? sdkType.usage : UsageFlags.None,
decorators: sdkType.decorators,
external: fromSdkExternalTypeInfo(sdkType),
};

sdkContext.__typeCache.updateSdkTypeReferences(sdkType, inputEnumType);
Expand Down Expand Up @@ -340,6 +338,7 @@ function fromSdkDateTimeType(
crossLanguageDefinitionId: dateTimeType.crossLanguageDefinitionId,
baseType: dateTimeType.baseType ? fromSdkType(sdkContext, dateTimeType.baseType) : undefined,
decorators: dateTimeType.decorators,
external: fromSdkExternalTypeInfo(dateTimeType),
};
}

Expand All @@ -355,6 +354,7 @@ function fromSdkDurationType(
crossLanguageDefinitionId: durationType.crossLanguageDefinitionId,
baseType: durationType.baseType ? fromSdkType(sdkContext, durationType.baseType) : undefined,
decorators: durationType.decorators,
external: fromSdkExternalTypeInfo(durationType),
};
}

Expand All @@ -369,6 +369,7 @@ function fromSdkBuiltInType(
crossLanguageDefinitionId: builtInType.crossLanguageDefinitionId,
baseType: builtInType.baseType ? fromSdkType(sdkContext, builtInType.baseType) : undefined,
decorators: builtInType.decorators,
external: fromSdkExternalTypeInfo(builtInType),
};
}

Expand All @@ -385,6 +386,7 @@ function fromUnionType(sdkContext: CSharpEmitterContext, union: SdkUnionType): I
variantTypes: variantTypes,
namespace: union.namespace,
decorators: union.decorators,
external: fromSdkExternalTypeInfo(union),
};
}

Expand Down Expand Up @@ -447,6 +449,7 @@ function fromSdkDictionaryType(
keyType: fromSdkType(sdkContext, dictionaryType.keyType),
valueType: fromSdkType(sdkContext, dictionaryType.valueType),
decorators: dictionaryType.decorators,
external: fromSdkExternalTypeInfo(dictionaryType),
};
}

Expand All @@ -460,6 +463,7 @@ function fromSdkArrayType(
valueType: fromSdkType(sdkContext, arrayType.valueType),
crossLanguageDefinitionId: arrayType.crossLanguageDefinitionId,
decorators: arrayType.decorators,
external: fromSdkExternalTypeInfo(arrayType),
};
}

Expand All @@ -471,20 +475,6 @@ function fromSdkEndpointType(): InputPrimitiveType {
};
}

function fromSdkExternalType(
sdkContext: CSharpEmitterContext,
sdkType: SdkType,
): InputExternalType {
const external = (sdkType as any).external;
return {
kind: "external",
identity: external.identity,
package: external.package,
minVersion: external.minVersion,
decorators: sdkType.decorators,
};
}

/**
* @beta
*/
Expand All @@ -510,3 +500,21 @@ export function getAllModelDecorators(

return Array.from(decoratorMap.values());
}

/**
* Converts TCGC external type information to InputExternalTypeMetadata
* @param sdkType - The SDK type that may have external type information
* @returns InputExternalTypeMetadata if the type has external info, undefined otherwise
*/
function fromSdkExternalTypeInfo(sdkType: SdkType): InputExternalTypeMetadata | undefined {
const external = (sdkType as any).external;
if (!external) {
return undefined;
}

return {
identity: external.identity,
package: external.package,
minVersion: external.minVersion,
};
}
21 changes: 12 additions & 9 deletions packages/http-client-csharp/emitter/src/type/input-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ import { InputParameterScope } from "./input-parameter-scope.js";
import { InputServiceMethod } from "./input-service-method.js";
import { RequestLocation } from "./request-location.js";

/**
* External type information for types that map to external library types.
* @beta
*/
export interface InputExternalTypeMetadata {
identity: string;
package?: string;
minVersion?: string;
}

/**
* The input client type for the CSharp emitter.
* @beta
Expand Down Expand Up @@ -54,6 +64,7 @@ interface InputTypeBase extends DecoratedType {
summary?: string;
doc?: string;
deprecation?: string;
external?: InputExternalTypeMetadata;
}

export type InputType =
Expand All @@ -67,8 +78,7 @@ export type InputType =
| InputEnumValueType
| InputArrayType
| InputDictionaryType
| InputNullableType
| InputExternalType;
| InputNullableType;

export interface InputPrimitiveType extends InputTypeBase {
kind: SdkBuiltInKinds;
Expand Down Expand Up @@ -274,10 +284,3 @@ export interface InputDictionaryType extends InputTypeBase {
keyType: InputType;
valueType: InputType;
}

export interface InputExternalType extends InputTypeBase {
kind: "external";
identity: string;
package?: string;
minVersion?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,18 @@ describe("External types", () => {
const prop = testModel.properties.find((p) => p.name === "prop");
ok(prop, "prop should exist");

// The type should be an external type
strictEqual(prop.type.kind, "external");
strictEqual((prop.type as any).identity, "Azure.Core.Expressions.DataFactoryExpression");
strictEqual((prop.type as any).package, "Azure.Core.Expressions");
strictEqual((prop.type as any).minVersion, "1.0.0");
// The type should remain a union but with external info
strictEqual(prop.type.kind, "union");
ok((prop.type as any).external, "Type should have external info");
strictEqual(
(prop.type as any).external.identity,
"Azure.Core.Expressions.DataFactoryExpression",
);
strictEqual((prop.type as any).external.package, "Azure.Core.Expressions");
strictEqual((prop.type as any).external.minVersion, "1.0.0");
// Verify union variants are preserved
ok((prop.type as any).variantTypes, "Union should have variant types");
strictEqual((prop.type as any).variantTypes.length, 2, "Union should have 2 variant types");
});

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

// The type should be an external type
strictEqual(jsonElementProp.type.kind, "external");
strictEqual((jsonElementProp.type as any).identity, "System.Text.Json.JsonElement");
strictEqual((jsonElementProp.type as any).package, "System.Text.Json");
strictEqual((jsonElementProp.type as any).minVersion, "8.0.0");
// The type should remain a model but with external info
strictEqual(jsonElementProp.type.kind, "model");
ok((jsonElementProp.type as any).external, "Type should have external info");
strictEqual((jsonElementProp.type as any).external.identity, "System.Text.Json.JsonElement");
strictEqual((jsonElementProp.type as any).external.package, "System.Text.Json");
strictEqual((jsonElementProp.type as any).external.minVersion, "8.0.0");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
namespace Microsoft.TypeSpec.Generator.Input
{
/// <summary>
/// Represents an external type reference in the input model.
/// External type information for types that map to external library types.
/// </summary>
public sealed class InputExternalType : InputType
public sealed class InputExternalTypeMetadata
{
/// <summary>
/// Construct a new <see cref="InputExternalType"/> instance
/// Construct a new <see cref="InputExternalTypeMetadata"/> instance
/// </summary>
/// <param name="identity">The fully qualified name of the external type.</param>
/// <param name="package">The package that exports the external type.</param>
/// <param name="minVersion">The minimum version of the package.</param>
public InputExternalType(string identity, string? package, string? minVersion) : base("external")
public InputExternalTypeMetadata(string identity, string? package, string? minVersion)
{
Identity = identity;
Package = package;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ protected InputType(string name)

public string Name { get; internal set; }
public IReadOnlyList<InputDecoratorInfo> Decorators { get; internal set; } = new List<InputDecoratorInfo>();
public InputExternalTypeMetadata? External { get; internal set; }

internal InputType GetCollectionEquivalent(InputType inputType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ public static InputArrayType CreateListType(ref Utf8JsonReader reader, string? i
string? crossLanguageDefinitionId = null;
InputType? valueType = null;
IReadOnlyList<InputDecoratorInfo>? decorators = null;
InputExternalTypeMetadata? external = null;
while (reader.TokenType != JsonTokenType.EndObject)
{
var isKnownProperty = reader.TryReadReferenceId(ref id)
|| reader.TryReadString("name", ref name)
|| reader.TryReadString("crossLanguageDefinitionId", ref crossLanguageDefinitionId)
|| reader.TryReadComplexType("valueType", options, ref valueType)
|| reader.TryReadComplexType("decorators", options, ref decorators);
|| reader.TryReadComplexType("decorators", options, ref decorators)
|| reader.TryReadComplexType("external", options, ref external);

if (!isKnownProperty)
{
Expand All @@ -45,7 +47,8 @@ public static InputArrayType CreateListType(ref Utf8JsonReader reader, string? i
valueType = valueType ?? throw new JsonException("List must have element type");
var listType = new InputArrayType(name ?? "Array", crossLanguageDefinitionId ?? string.Empty, valueType)
{
Decorators = decorators ?? []
Decorators = decorators ?? [],
External = external
};
if (id != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static InputDateTimeType CreateDateTimeType(ref Utf8JsonReader reader, st
InputPrimitiveType? wireType = null;
IReadOnlyList<InputDecoratorInfo>? decorators = null;
InputDateTimeType? baseType = null;
InputExternalTypeMetadata? external = null;

while (reader.TokenType != JsonTokenType.EndObject)
{
Expand All @@ -38,7 +39,8 @@ public static InputDateTimeType CreateDateTimeType(ref Utf8JsonReader reader, st
|| reader.TryReadString("encode", ref encode)
|| reader.TryReadComplexType("wireType", options, ref wireType)
|| reader.TryReadComplexType("baseType", options, ref baseType)
|| reader.TryReadComplexType("decorators", options, ref decorators);
|| reader.TryReadComplexType("decorators", options, ref decorators)
|| reader.TryReadComplexType("external", options, ref external);

if (!isKnownProperty)
{
Expand All @@ -52,7 +54,7 @@ public static InputDateTimeType CreateDateTimeType(ref Utf8JsonReader reader, st
wireType = wireType ?? throw new JsonException("DateTime type must have wireType");

var dateTimeType = Enum.TryParse<DateTimeKnownEncoding>(encode, ignoreCase: true, out var encodeKind)
? new InputDateTimeType(encodeKind, name, crossLanguageDefinitionId, wireType, baseType) { Decorators = decorators ?? [] }
? new InputDateTimeType(encodeKind, name, crossLanguageDefinitionId, wireType, baseType) { Decorators = decorators ?? [], External = external }
: throw new JsonException($"Encoding of DateTime type {encode} is unknown.");

if (id != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ public static InputDictionaryType CreateDictionaryType(ref Utf8JsonReader reader
InputType? keyType = null;
InputType? valueType = null;
IReadOnlyList<InputDecoratorInfo>? decorators = null;
InputExternalTypeMetadata? external = null;
while (reader.TokenType != JsonTokenType.EndObject)
{
var isKnownProperty = reader.TryReadReferenceId(ref id)
|| reader.TryReadComplexType("keyType", options, ref keyType)
|| reader.TryReadComplexType("valueType", options, ref valueType)
|| reader.TryReadComplexType("decorators", options, ref decorators);
|| reader.TryReadComplexType("decorators", options, ref decorators)
|| reader.TryReadComplexType("external", options, ref external);

if (!isKnownProperty)
{
Expand All @@ -47,6 +49,7 @@ public static InputDictionaryType CreateDictionaryType(ref Utf8JsonReader reader
var dictType = new InputDictionaryType("Dictionary", keyType, valueType)
{
Decorators = decorators ?? [],
External = external
};
if (id != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public static InputDurationType CreateDurationType(ref Utf8JsonReader reader, st
InputPrimitiveType? wireType = null;
IReadOnlyList<InputDecoratorInfo>? decorators = null;
InputDurationType? baseType = null;
InputExternalTypeMetadata? external = null;

while (reader.TokenType != JsonTokenType.EndObject)
{
Expand All @@ -39,7 +40,8 @@ public static InputDurationType CreateDurationType(ref Utf8JsonReader reader, st
|| reader.TryReadString("encode", ref encode)
|| reader.TryReadComplexType("wireType", options, ref wireType)
|| reader.TryReadComplexType("baseType", options, ref baseType)
|| reader.TryReadComplexType("decorators", options, ref decorators);
|| reader.TryReadComplexType("decorators", options, ref decorators)
|| reader.TryReadComplexType("external", options, ref external);

if (!isKnownProperty)
{
Expand All @@ -53,7 +55,7 @@ public static InputDurationType CreateDurationType(ref Utf8JsonReader reader, st
wireType = wireType ?? throw new JsonException("Duration type must have wireType");

var dateTimeType = DurationKnownEncodingExtensions.TryParse(encode, out var encodeKind)
? new InputDurationType(encodeKind.Value, name, crossLanguageDefinitionId, wireType, baseType) { Decorators = decorators ?? [] }
? new InputDurationType(encodeKind.Value, name, crossLanguageDefinitionId, wireType, baseType) { Decorators = decorators ?? [], External = external }
: throw new JsonException($"Encoding of Duration type {encode} is unknown.");

if (id != null)
Expand Down
Loading
Loading