Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/http-client-csharp/emitter/src/lib/type-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
InputDurationType,
InputEnumType,
InputEnumValueType,
InputExternalType,
InputLiteralType,
InputModelProperty,
InputModelType,
Expand Down Expand Up @@ -80,6 +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 = {
Expand Down Expand Up @@ -463,6 +471,20 @@ 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 Down
10 changes: 9 additions & 1 deletion packages/http-client-csharp/emitter/src/type/input-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export type InputType =
| InputEnumValueType
| InputArrayType
| InputDictionaryType
| InputNullableType;
| InputNullableType
| InputExternalType;

export interface InputPrimitiveType extends InputTypeBase {
kind: SdkBuiltInKinds;
Expand Down Expand Up @@ -271,3 +272,10 @@ 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 @@ -87,3 +87,88 @@ describe("Enum value references", () => {
}
});
});

describe("External types", () => {
let runner: TestHost;

beforeEach(async () => {
runner = await createEmitterTestHost();
});

it("should convert external type from @alternateType decorator", async () => {
const program = await typeSpecCompile(
`
@alternateType({
identity: "Azure.Core.Expressions.DataFactoryExpression",
package: "Azure.Core.Expressions",
minVersion: "1.0.0",
}, "csharp")
union Dfe<T> {
T,
DfeExpression: string
}

model TestModel {
prop: Dfe<string>;
}

op test(@body input: TestModel): void;
`,
runner,
{ IsTCGCNeeded: true },
);
const context = createEmitterContext(program);
const sdkContext = await createCSharpSdkContext(context);
const root = createModel(sdkContext);

const testModel = root.models.find((m) => m.name === "TestModel");
ok(testModel, "TestModel should exist");

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");
});

it("should convert external type on model", async () => {
const program = await typeSpecCompile(
`
@alternateType({
identity: "System.Text.Json.JsonElement",
package: "System.Text.Json",
minVersion: "8.0.0",
}, "csharp")
model JsonData {
data: string;
}

model TestModel {
jsonElement: JsonData;
}

op test(@body input: TestModel): void;
`,
runner,
{ IsTCGCNeeded: true },
);
const context = createEmitterContext(program);
const sdkContext = await createCSharpSdkContext(context);
const root = createModel(sdkContext);

const testModel = root.models.find((m) => m.name === "TestModel");
ok(testModel, "TestModel should exist");

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");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.TypeSpec.Generator.Input
{
/// <summary>
/// Represents an external type reference in the input model.
/// </summary>
public sealed class InputExternalType : InputType
{
/// <summary>
/// Construct a new <see cref="InputExternalType"/> 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")
{
Identity = identity;
Package = package;
MinVersion = minVersion;
}

/// <summary>
/// The fully qualified name of the external type. For example, "Azure.Core.Expressions.DataFactoryExpression"
/// </summary>
public string Identity { get; }

/// <summary>
/// The package that exports the external type. For example, "Azure.Core.Expressions"
/// </summary>
public string? Package { get; }

/// <summary>
/// The minimum version of the package to use for the external type. For example, "1.0.0"
/// </summary>
public string? MinVersion { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Microsoft.TypeSpec.Generator.Input
{
internal class InputExternalTypeConverter : JsonConverter<InputExternalType>
{
private readonly TypeSpecReferenceHandler _referenceHandler;

public InputExternalTypeConverter(TypeSpecReferenceHandler referenceHandler)
{
_referenceHandler = referenceHandler;
}

public override InputExternalType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> reader.ReadReferenceAndResolve<InputExternalType>(_referenceHandler.CurrentResolver) ?? CreateInputExternalType(ref reader, null, options, _referenceHandler.CurrentResolver);

public override void Write(Utf8JsonWriter writer, InputExternalType value, JsonSerializerOptions options)
=> throw new NotSupportedException("Writing not supported");

public static InputExternalType CreateInputExternalType(ref Utf8JsonReader reader, string? id, JsonSerializerOptions options, ReferenceResolver resolver)
{
string? identity = null;
string? package = null;
string? minVersion = null;
IReadOnlyList<InputDecoratorInfo>? decorators = null;

while (reader.TokenType != JsonTokenType.EndObject)
{
var isKnownProperty = reader.TryReadReferenceId(ref id)
|| reader.TryReadString("identity", ref identity)
|| reader.TryReadString("package", ref package)
|| reader.TryReadString("minVersion", ref minVersion)
|| reader.TryReadComplexType("decorators", options, ref decorators);

if (!isKnownProperty)
{
reader.SkipProperty();
}
}

identity = identity ?? throw new JsonException("InputExternalType must have identity");

var externalType = new InputExternalType(identity, package, minVersion)
{
Decorators = decorators ?? []
};

if (id != null)
{
resolver.AddReference(id, externalType);
}

return externalType;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ private static InputType CreateInputType(ref Utf8JsonReader reader, JsonSerializ
private const string UtcDateTimeKind = "utcDateTime";
private const string OffsetDateTimeKind = "offsetDateTime";
private const string DurationKind = "duration";
private const string ExternalKind = "external";

private static InputType CreateDerivedType(ref Utf8JsonReader reader, string? id, string? kind, string? name, JsonSerializerOptions options, ReferenceResolver resolver) => kind switch
{
Expand All @@ -71,6 +72,7 @@ private static InputType CreateInputType(ref Utf8JsonReader reader, JsonSerializ
UtcDateTimeKind or OffsetDateTimeKind => InputDateTimeTypeConverter.CreateDateTimeType(ref reader, id, name, options, resolver),
DurationKind => InputDurationTypeConverter.CreateDurationType(ref reader, id, name, options, resolver),
NullableKind => TypeSpecInputNullableTypeConverter.CreateNullableType(ref reader, id, name, options, resolver),
ExternalKind => InputExternalTypeConverter.CreateInputExternalType(ref reader, id, options, resolver),
_ => InputPrimitiveTypeConverter.CreatePrimitiveType(ref reader, id, kind, name, options, resolver),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ protected internal TypeFactory()
case InputNullableType nullableType:
type = CreateCSharpType(nullableType.Type)?.WithNullable(true);
break;
case InputExternalType externalType:
type = CreateExternalType(externalType);
break;
default:
type = CreatePrimitiveCSharpTypeCore(inputType);
break;
Expand Down Expand Up @@ -229,6 +232,29 @@ protected internal TypeFactory()
protected virtual EnumProvider? CreateEnumCore(InputEnumType enumType, TypeProvider? declaringType)
=> EnumProvider.Create(enumType, declaringType);

/// <summary>
/// Factory method for creating a <see cref="CSharpType"/> based on an external type reference <paramref name="externalType"/>.
/// </summary>
/// <param name="externalType">The <see cref="InputExternalType"/> to convert.</param>
/// <returns>A <see cref="CSharpType"/> representing the external type, or null if the type cannot be resolved.</returns>
private CSharpType? CreateExternalType(InputExternalType externalType)
{
// Try to create a framework type from the fully qualified name
var frameworkType = CreateFrameworkType(externalType.Identity);
if (frameworkType != null)
{
return new CSharpType(frameworkType);
}

// External types that cannot be resolved as framework types are not supported
// Report a diagnostic to inform the user
CodeModelGenerator.Instance.Emitter.ReportDiagnostic(
"unsupported-external-type",
$"External type '{externalType.Identity}' is not currently supported.");

return null;
}

/// <summary>
/// Factory method for creating a <see cref="ParameterProvider"/> based on an input parameter <paramref name="parameter"/>.
/// </summary>
Expand Down
Loading
Loading