Skip to content

Commit

Permalink
support enum discrimintor (#4279)
Browse files Browse the repository at this point in the history
Fix #4002

- implement inheritance with enum discriminator
- onboard http\type\model\inheritance\emum-discriminator cadl-ranch test
project
  • Loading branch information
chunyu3 authored Sep 2, 2024
1 parent 8635849 commit c2b80f7
Show file tree
Hide file tree
Showing 25 changed files with 1,518 additions and 3 deletions.
1 change: 0 additions & 1 deletion packages/http-client-csharp/eng/scripts/Generate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ $failingSpecs = @(
Join-Path 'http' 'special-headers' 'repeatability'
Join-Path 'http' 'type' 'model' 'flatten'
Join-Path 'http' 'type' 'model' 'visibility'
Join-Path 'http' 'type' 'model' 'inheritance' 'enum-discriminator'
Join-Path 'http' 'type' 'model' 'inheritance' 'nested-discriminator'
Join-Path 'http' 'type' 'model' 'inheritance' 'not-discriminated'
Join-Path 'http' 'type' 'model' 'inheritance' 'recursive'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@
"commandName": "Executable",
"executablePath": "$(SolutionDir)/../dist/generator/Microsoft.Generator.CSharp.exe"
},
"http-type-model-inheritance-enum-discriminator": {
"commandLineArgs": "$(SolutionDir)/TestProjects/CadlRanch/http/type/model/inheritance/enum-discriminator -p StubLibraryPlugin",
"commandName": "Executable",
"executablePath": "$(SolutionDir)/../dist/generator/Microsoft.Generator.CSharp.exe"
},
"http-type-model-inheritance-single-discriminator": {
"commandLineArgs": "$(SolutionDir)/TestProjects/CadlRanch/http/type/model/inheritance/single-discriminator -p StubLibraryPlugin",
"commandName": "Executable",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public ModelProvider(InputModelType inputModel)
if (inputModel.BaseModel is not null)
{
_baseTypeProvider = new(() => CodeModelPlugin.Instance.TypeFactory.CreateModel(inputModel.BaseModel));
DiscriminatorValueExpression = EnsureDiscriminatorValueExpression();
}

_isStruct = inputModel.ModelAsStruct;
Expand All @@ -62,6 +63,7 @@ public ModelProvider(InputModelType inputModel)
public bool IsUnknownDiscriminatorModel => _inputModel.IsUnknownDiscriminatorModel;

public string? DiscriminatorValue => _inputModel.DiscriminatorValue;
public ValueExpression? DiscriminatorValueExpression { get; init; }

private IReadOnlyList<ModelProvider>? _derivedModels;
public IReadOnlyList<ModelProvider> DerivedModels => _derivedModels ??= BuildDerivedModels();
Expand Down Expand Up @@ -245,11 +247,61 @@ private ConstructorProvider BuildFullConstructor()
return (constructorParameters, constructorInitializer);
}

private ValueExpression? EnsureDiscriminatorValueExpression()
{
if (_inputModel.BaseModel is not null && _inputModel.DiscriminatorValue is not null)
{
var discriminator = BaseModelProvider?.Properties.Where(p => p.IsDiscriminator).FirstOrDefault();
if (discriminator != null)
{
var type = discriminator.Type;
if (IsUnknownDiscriminatorModel)
{
var discriminatorExpression = discriminator.AsParameter.AsExpression;
if (!type.IsFrameworkType && type.IsEnum)
{
if (type.IsStruct)
{
/* kind != default ? kind : "unknown" */
return new TernaryConditionalExpression(discriminatorExpression.NotEqual(Default), discriminatorExpression, Literal(_inputModel.DiscriminatorValue));
}
else
{
return discriminatorExpression;
}
}
else
{
/* kind ?? "unknown" */
return NullCoalescing(discriminatorExpression, Literal(_inputModel.DiscriminatorValue));
}
}
else
{
if (!type.IsFrameworkType && type.IsEnum)
{
/* TODO: when customize the discriminator type to a enum, then we may not be able to get the correct TypeProvider in this way.
* We will handle this when issue https://github.com/microsoft/typespec/issues/4313 is resolved.
* */
var discriminatorProvider = CodeModelPlugin.Instance.TypeFactory.CreateEnum(enumType: (InputEnumType)_inputModel.BaseModel.DiscriminatorProperty!.Type);
var enumMember = discriminatorProvider!.EnumValues.FirstOrDefault(e => e.Value.ToString() == _inputModel.DiscriminatorValue) ?? throw new InvalidOperationException($"invalid discriminator value {_inputModel.DiscriminatorValue}");
/* {KindType}.{enumMember} */
return TypeReferenceExpression.FromType(type).Property(enumMember.Name);
}
else
{
return Literal(_inputModel.DiscriminatorValue);
}
}
}
}
return null;
}
private ValueExpression GetExpression(ParameterProvider parameter)
{
if (parameter.Property is not null && parameter.Property.IsDiscriminator)
if (parameter.Property is not null && parameter.Property.IsDiscriminator && _inputModel.DiscriminatorValue != null)
{
return IsUnknownDiscriminatorModel ? NullCoalescing(parameter.AsExpression, Literal(_inputModel.DiscriminatorValue)) : Literal(_inputModel.DiscriminatorValue);
return DiscriminatorValueExpression ?? throw new InvalidOperationException($"invalid discriminator {_inputModel.DiscriminatorValue}");
}

return parameter.AsExpression;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Threading.Tasks;
using _Type.Model.Inheritance.EnumDiscriminator;
using _Type.Model.Inheritance.EnumDiscriminator.Models;
using NUnit.Framework;

namespace TestProjects.CadlRanch.Tests.Http._Type.Model.Inheritance.EnumDiscriminator
{
public class EnumDiscriminatorTests : CadlRanchTestBase
{
[CadlRanchTest]
public Task PutExtensibleEnum() => Test(async (host) =>
{
var result = await new EnumDiscriminatorClient(host, null).PutExtensibleModelAsync(new Golden(10));
Assert.AreEqual(204, result.GetRawResponse().Status);
});

[CadlRanchTest]
public Task GetExtensibleEnum() => Test(async (host) =>
{
var result = await new EnumDiscriminatorClient(host, null).GetExtensibleModelAsync();
Assert.AreEqual(200, result.GetRawResponse().Status);
Assert.AreEqual(10, result.Value.Weight);
Assert.IsInstanceOf<Golden>(result.Value);
});

[CadlRanchTest]
public Task GetExtensibleModelMissingDiscriminator() => Test(async (host) =>
{
var result = await new EnumDiscriminatorClient(host, null).GetExtensibleModelMissingDiscriminatorAsync();
Assert.AreEqual(200, result.GetRawResponse().Status);
Assert.IsNotInstanceOf<Golden>(result.Value);
Assert.AreEqual(10, result.Value.Weight);
});

[CadlRanchTest]
public Task GetExtensibleModelWrongDiscriminator() => Test(async (host) =>
{
var result = await new EnumDiscriminatorClient(host, null).GetExtensibleModelWrongDiscriminatorAsync();
Assert.AreEqual(200, result.GetRawResponse().Status);
Assert.IsNotInstanceOf<Golden>(result.Value);
Assert.AreEqual(8, result.Value.Weight);
});

[CadlRanchTest]
public Task PutFixedEnum() => Test(async (host) =>
{
var result = await new EnumDiscriminatorClient(host, null).PutFixedModelAsync(new Cobra(10));
Assert.AreEqual(204, result.GetRawResponse().Status);
});

[CadlRanchTest]
public Task GetFixedEnum() => Test(async (host) =>
{
var result = await new EnumDiscriminatorClient(host, null).GetFixedModelAsync();
Assert.AreEqual(200, result.GetRawResponse().Status);
Assert.AreEqual(10, result.Value.Length);
Assert.IsInstanceOf<Cobra>(result.Value);
});

[CadlRanchTest]
public Task GetFixedModelMissingDiscriminator() => Test(async (host) =>
{
var response = await new EnumDiscriminatorClient(host, null).GetExtensibleModelMissingDiscriminatorAsync();
Assert.AreEqual(200, response.GetRawResponse().Status);
Assert.IsNotInstanceOf<Golden>(response.Value);
Assert.AreEqual(10, response.Value.Weight);
});

[CadlRanchTest]
public Task GetFixedModelWrongDiscriminator() => Test(async (host) =>
{
var result = await new EnumDiscriminatorClient(host, null).GetExtensibleModelWrongDiscriminatorAsync();
Assert.AreEqual(200, result.GetRawResponse().Status);
Assert.IsNotInstanceOf<Golden>(result.Value);
Assert.AreEqual(8, result.Value.Weight);
});

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"output-folder": ".",
"namespace": "Type.Model.Inheritance.EnumDiscriminator",
"library-name": "Type.Model.Inheritance.EnumDiscriminator",
"use-model-reader-writer": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29709.97
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_Type.Model.Inheritance.EnumDiscriminator", "src\_Type.Model.Inheritance.EnumDiscriminator.csproj", "{28FF4005-4467-4E36-92E7-DEA27DEB1519}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B0C276D1-2930-4887-B29A-D1A33E7009A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0C276D1-2930-4887-B29A-D1A33E7009A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0C276D1-2930-4887-B29A-D1A33E7009A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B0C276D1-2930-4887-B29A-D1A33E7009A2}.Release|Any CPU.Build.0 = Release|Any CPU
{8E9A77AC-792A-4432-8320-ACFD46730401}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8E9A77AC-792A-4432-8320-ACFD46730401}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E9A77AC-792A-4432-8320-ACFD46730401}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E9A77AC-792A-4432-8320-ACFD46730401}.Release|Any CPU.Build.0 = Release|Any CPU
{A4241C1F-A53D-474C-9E4E-075054407E74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A4241C1F-A53D-474C-9E4E-075054407E74}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A4241C1F-A53D-474C-9E4E-075054407E74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A4241C1F-A53D-474C-9E4E-075054407E74}.Release|Any CPU.Build.0 = Release|Any CPU
{FA8BD3F1-8616-47B6-974C-7576CDF4717E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA8BD3F1-8616-47B6-974C-7576CDF4717E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA8BD3F1-8616-47B6-974C-7576CDF4717E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA8BD3F1-8616-47B6-974C-7576CDF4717E}.Release|Any CPU.Build.0 = Release|Any CPU
{85677AD3-C214-42FA-AE6E-49B956CAC8DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{85677AD3-C214-42FA-AE6E-49B956CAC8DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{85677AD3-C214-42FA-AE6E-49B956CAC8DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{85677AD3-C214-42FA-AE6E-49B956CAC8DC}.Release|Any CPU.Build.0 = Release|Any CPU
{28FF4005-4467-4E36-92E7-DEA27DEB1519}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{28FF4005-4467-4E36-92E7-DEA27DEB1519}.Debug|Any CPU.Build.0 = Debug|Any CPU
{28FF4005-4467-4E36-92E7-DEA27DEB1519}.Release|Any CPU.ActiveCfg = Release|Any CPU
{28FF4005-4467-4E36-92E7-DEA27DEB1519}.Release|Any CPU.Build.0 = Release|Any CPU
{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A97F4B90-2591-4689-B1F8-5F21FE6D6CAE}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// <auto-generated/>

#nullable disable

using System;
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Threading.Tasks;
using _Type.Model.Inheritance.EnumDiscriminator.Models;

namespace _Type.Model.Inheritance.EnumDiscriminator
{
public partial class EnumDiscriminatorClient
{
public EnumDiscriminatorClient() : this(new Uri("http://localhost:3000"), new EnumDiscriminatorClientOptions()) => throw null;

public EnumDiscriminatorClient(Uri endpoint, EnumDiscriminatorClientOptions options) => throw null;

public ClientPipeline Pipeline => throw null;

public virtual ClientResult GetExtensibleModel(RequestOptions options) => throw null;

public virtual Task<ClientResult> GetExtensibleModelAsync(RequestOptions options) => throw null;

public virtual ClientResult<Dog> GetExtensibleModel() => throw null;

public virtual Task<ClientResult<Dog>> GetExtensibleModelAsync() => throw null;

public virtual ClientResult PutExtensibleModel(BinaryContent content, RequestOptions options) => throw null;

public virtual Task<ClientResult> PutExtensibleModelAsync(BinaryContent content, RequestOptions options) => throw null;

public virtual ClientResult PutExtensibleModel(Dog input) => throw null;

public virtual Task<ClientResult> PutExtensibleModelAsync(Dog input) => throw null;

public virtual ClientResult GetExtensibleModelMissingDiscriminator(RequestOptions options) => throw null;

public virtual Task<ClientResult> GetExtensibleModelMissingDiscriminatorAsync(RequestOptions options) => throw null;

public virtual ClientResult<Dog> GetExtensibleModelMissingDiscriminator() => throw null;

public virtual Task<ClientResult<Dog>> GetExtensibleModelMissingDiscriminatorAsync() => throw null;

public virtual ClientResult GetExtensibleModelWrongDiscriminator(RequestOptions options) => throw null;

public virtual Task<ClientResult> GetExtensibleModelWrongDiscriminatorAsync(RequestOptions options) => throw null;

public virtual ClientResult<Dog> GetExtensibleModelWrongDiscriminator() => throw null;

public virtual Task<ClientResult<Dog>> GetExtensibleModelWrongDiscriminatorAsync() => throw null;

public virtual ClientResult GetFixedModel(RequestOptions options) => throw null;

public virtual Task<ClientResult> GetFixedModelAsync(RequestOptions options) => throw null;

public virtual ClientResult<Snake> GetFixedModel() => throw null;

public virtual Task<ClientResult<Snake>> GetFixedModelAsync() => throw null;

public virtual ClientResult PutFixedModel(BinaryContent content, RequestOptions options) => throw null;

public virtual Task<ClientResult> PutFixedModelAsync(BinaryContent content, RequestOptions options) => throw null;

public virtual ClientResult PutFixedModel(Snake input) => throw null;

public virtual Task<ClientResult> PutFixedModelAsync(Snake input) => throw null;

public virtual ClientResult GetFixedModelMissingDiscriminator(RequestOptions options) => throw null;

public virtual Task<ClientResult> GetFixedModelMissingDiscriminatorAsync(RequestOptions options) => throw null;

public virtual ClientResult<Snake> GetFixedModelMissingDiscriminator() => throw null;

public virtual Task<ClientResult<Snake>> GetFixedModelMissingDiscriminatorAsync() => throw null;

public virtual ClientResult GetFixedModelWrongDiscriminator(RequestOptions options) => throw null;

public virtual Task<ClientResult> GetFixedModelWrongDiscriminatorAsync(RequestOptions options) => throw null;

public virtual ClientResult<Snake> GetFixedModelWrongDiscriminator() => throw null;

public virtual Task<ClientResult<Snake>> GetFixedModelWrongDiscriminatorAsync() => throw null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// <auto-generated/>

#nullable disable

using System.ClientModel.Primitives;

namespace _Type.Model.Inheritance.EnumDiscriminator
{
public partial class EnumDiscriminatorClientOptions : ClientPipelineOptions
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// <auto-generated/>

#nullable disable

using System;
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Text.Json;

namespace _Type.Model.Inheritance.EnumDiscriminator.Models
{
public partial class Cobra : IJsonModel<Cobra>
{
void IJsonModel<Cobra>.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) => throw null;

protected override void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) => throw null;

Cobra IJsonModel<Cobra>.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => throw null;

protected override Snake JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => throw null;

BinaryData IPersistableModel<Cobra>.Write(ModelReaderWriterOptions options) => throw null;

protected override BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) => throw null;

Cobra IPersistableModel<Cobra>.Create(BinaryData data, ModelReaderWriterOptions options) => throw null;

protected override Snake PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) => throw null;

string IPersistableModel<Cobra>.GetFormatFromOptions(ModelReaderWriterOptions options) => throw null;

public static implicit operator BinaryContent(Cobra cobra) => throw null;

public static explicit operator Cobra(ClientResult result) => throw null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// <auto-generated/>

#nullable disable

using System;
using System.Collections.Generic;

namespace _Type.Model.Inheritance.EnumDiscriminator.Models
{
public partial class Cobra : Snake
{
public Cobra(int length) : base(SnakeKind.Cobra, length) => throw null;

internal Cobra(int length, IDictionary<string, BinaryData> serializedAdditionalRawData) : base(SnakeKind.Cobra, length, serializedAdditionalRawData) => throw null;
}
}
Loading

0 comments on commit c2b80f7

Please sign in to comment.