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 discriminator of models #3599

Draft
wants to merge 23 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b7a391a
remove the link temporarily
ArcturusZhang Jul 2, 2024
8180b12
add a step to build the test project
ArcturusZhang Jul 2, 2024
9d534d8
fix the base type issue
ArcturusZhang Jul 2, 2024
f1b692d
fix issues
ArcturusZhang Jul 2, 2024
ec92093
Merge branch 'main' into fix-base-type-issue
ArcturusZhang Jul 3, 2024
2afcc7b
Merge branch 'main' into remove-the-link-to-make-the-project-build
ArcturusZhang Jul 3, 2024
3504d18
update the base ctor
ArcturusZhang Jul 3, 2024
ca1e5b9
fix the deserialization static method issue
ArcturusZhang Jul 3, 2024
942f936
refactor
ArcturusZhang Jul 3, 2024
65ebb99
fix typo
ArcturusZhang Jul 3, 2024
a649a22
add the link back
ArcturusZhang Jul 3, 2024
a9a84f7
fix typo
ArcturusZhang Jul 3, 2024
f79423c
Merge remote-tracking branch 'forked/remove-the-link-to-make-the-proj…
ArcturusZhang Jul 3, 2024
1704461
fix the return type issue
ArcturusZhang Jul 3, 2024
e1edc70
fix test cases
ArcturusZhang Jul 3, 2024
b38bf1f
Merge remote-tracking branch 'origin/main' into fix-base-type-issue
ArcturusZhang Jul 3, 2024
47ee984
fix after merge
ArcturusZhang Jul 3, 2024
3b44a4b
overhauls the test cases for mrw serialization
ArcturusZhang Jul 4, 2024
0d53c60
refine
ArcturusZhang Jul 4, 2024
b21a84b
Merge branch 'main' into fix-base-type-issue
ArcturusZhang Jul 5, 2024
2e3caa4
Merge remote-tracking branch 'origin/main' into fix-base-type-issue
ArcturusZhang Jul 8, 2024
4b635b6
resolve comments
ArcturusZhang Jul 8, 2024
6c0ccb6
implement the discriminator
ArcturusZhang Jul 8, 2024
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
Prev Previous commit
Next Next commit
update the base ctor
  • Loading branch information
ArcturusZhang committed Jul 3, 2024
commit 3504d18965beaaf23cce04b454083e35b2d982ea
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,16 @@ protected override MethodProvider[] BuildConstructors()
: _inputModel.Usage.HasFlag(InputModelTypeUsage.Input)
? MethodSignatureModifiers.Public
: MethodSignatureModifiers.Internal;
var constructorParameters = BuildConstructorParameters();
var (baseConstructor, constructorInitializer) = BuildConstructorInitializer();
var constructorParameters = BuildConstructorParameters(baseConstructor);

var constructor = new MethodProvider(
signature: new ConstructorSignature(
Type,
$"Initializes a new instance of {Type:C}",
accessibility,
constructorParameters),
constructorParameters,
Initializer: constructorInitializer),
bodyStatements: new MethodBodyStatement[]
{
GetPropertyInitializers(constructorParameters)
Expand All @@ -101,9 +103,44 @@ protected override MethodProvider[] BuildConstructors()
return [constructor];
}

private IReadOnlyList<ParameterProvider> BuildConstructorParameters()
private (ConstructorSignature? BaseSignature, ConstructorInitializer? Initializer) BuildConstructorInitializer()
{
List<ParameterProvider> constructorParameters = new List<ParameterProvider>();
// find the constructor on the base type
if (Inherits is not { IsFrameworkType: false, Implementation: TypeProvider baseType })
{
return (null, null);
}

if (baseType.Constructors.Count == 0)
{
return (null, null);
}

// we cannot know which ctor to call, but in our implemenation, there should only be one
var ctor = baseType.Constructors[0];
if (ctor.Signature is not ConstructorSignature ctorSignature || ctorSignature.Parameters.Count == 0)
{
return (null, null);
}
// construct the initializer using the parameters from base signature
var initializer = new ConstructorInitializer(true, ctorSignature.Parameters);

return (ctorSignature, initializer);
}

private IReadOnlyList<ParameterProvider> BuildConstructorParameters(ConstructorSignature? baseConstructor)
{
var baseParameters = baseConstructor?.Parameters ?? Array.Empty<ParameterProvider>();
var parameterCapacity = baseParameters.Count + _inputModel.Properties.Count;
var parameterNames = new HashSet<string>(parameterCapacity);
var constructorParameters = new List<ParameterProvider>(baseParameters.Count + _inputModel.Properties.Count);

// add the base parameters
foreach (var parameter in baseParameters)
{
parameterNames.Add(parameter.Name);
constructorParameters.Add(parameter);
}

foreach (var property in _inputModel.Properties)
{
Expand All @@ -112,10 +149,14 @@ private IReadOnlyList<ParameterProvider> BuildConstructorParameters()
{
if (!property.IsReadOnly)
{
constructorParameters.Add(new ParameterProvider(property)
var parameter = new ParameterProvider(property)
{
Type = propertyType.InputType
});
};
if (!parameterNames.Contains(parameter.Name))
{
constructorParameters.Add(parameter);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,7 @@ public IDisposable WriteMethodDeclarationNoScope(MethodSignatureBase methodBase,

if (!isBase || arguments.Any())
{
AppendRaw(isBase ? ": base(" : ": this(");
AppendRaw(isBase ? " : base(" : " : this(");
var iterator = arguments.GetEnumerator();
if (iterator.MoveNext())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ model Thing {

@doc("required nullable collection")
requiredNullableList: int32[] | null;

@doc("the derived model with its base without required properties")
baseWithoutRequired?: ModelWithBaseModelWithoutRequired;

@doc("the derived model with its base with required properties")
baseWithRequired?: ModelWithBaseModelWithRequired;
}

@doc("A model with a few required nullable properties")
Expand All @@ -140,15 +146,39 @@ model ModelWithRequiredNullableProperties {
requiredFixedEnum: StringFixedEnum | null;
}

@doc("A simple base model")
model FriendBase {
@doc("base property")
baseProperty?: string;
@doc("This is a base model without any required properties")
model BaseModelWithoutRequired {
@doc("optional property")
optional?: int32;
}

@doc("This is a model that extends BaseModelWithoutRequired")
model ModelWithBaseModelWithoutRequired extends BaseModelWithoutRequired {
@doc("name of the ModelWithBaseModelWithoutRequired")
name: string;

@doc("address of the ModelWithBaseModelWithoutRequired")
address?: string;
}

@doc("This is a base model with required properties")
model BaseModelWithRequired {
@doc("required property")
required: int32;
}

@doc("This is a model that extends BaseModelWithRequired")
model ModelWithBaseModelWithRequired extends BaseModelWithRequired {
@doc("name of the ModelWithBaseModelWithRequired")
name: string;

@doc("address of the ModelWithBaseModelWithRequired")
address?: string;
}

@doc("this is not a friendly model but with a friendly name")
@friendlyName("Friend")
model NotFriend extends FriendBase {
model NotFriend {
@doc("name of the NotFriend")
name: string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal partial class ErrorResult<T> : ClientResult<T>
private readonly PipelineResponse _response;
private readonly ClientResultException _exception;

public ErrorResult(PipelineResponse response, ClientResultException exception): base(default, response)
public ErrorResult(PipelineResponse response, ClientResultException exception) : base(default, response)
{
_response = response;
_exception = exception;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// <auto-generated/>

#nullable disable

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

namespace UnbrandedTypeSpec.Models
{
/// <summary></summary>
public partial class BaseModelWithRequired : IJsonModel<BaseModelWithRequired>
{
private IDictionary<string, BinaryData> _serializedAdditionalRawData;

internal BaseModelWithRequired(int @required, IDictionary<string, BinaryData> serializedAdditionalRawData)
{
Required = @required;
_serializedAdditionalRawData = serializedAdditionalRawData;
}

internal BaseModelWithRequired()
{
}

void IJsonModel<BaseModelWithRequired>.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options)
{
writer.WriteStartObject();
JsonModelWriteCore(writer, options);
writer.WriteEndObject();
}

/// <param name="writer"> The JSON writer. </param>
/// <param name="options"> The client options for reading and writing models. </param>
protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options)
{
string format = options.Format == "W" ? ((IPersistableModel<BaseModelWithRequired>)this).GetFormatFromOptions(options) : options.Format;
if (format != "J")
{
throw new FormatException($"The model {nameof(BaseModelWithRequired)} does not support writing '{format}' format.");
}
writer.WritePropertyName("required"u8);
writer.WriteNumberValue(Required);
if (options.Format != "W" && _serializedAdditionalRawData != null)
{
foreach (var item in _serializedAdditionalRawData)
{
writer.WritePropertyName(item.Key);
#if NET6_0_OR_GREATER
writer.WriteRawValue(item.Value);
#else
using (JsonDocument document = JsonDocument.Parse(item.Value))
{
JsonSerializer.Serialize(writer, document.RootElement);
}
#endif
}
}
}

BaseModelWithRequired IJsonModel<BaseModelWithRequired>.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options)
{
throw new NotImplementedException("Not implemented");
}

internal static BaseModelWithRequired DeserializeBaseModelWithRequired(JsonElement element, ModelReaderWriterOptions options)
{
if (element.ValueKind == JsonValueKind.Null)
{
return null;
}
int @required = default;
IDictionary<string, BinaryData> serializedAdditionalRawData = default;
Dictionary<string, BinaryData> rawDataDictionary = new Dictionary<string, BinaryData>();
foreach (var prop in element.EnumerateObject())
{
if (prop.NameEquals("required"u8))
{
@required = prop.Value.GetInt32();
continue;
}
if (options.Format != "W")
{
rawDataDictionary.Add(prop.Name, BinaryData.FromString(prop.Value.GetRawText()));
}
}
serializedAdditionalRawData = rawDataDictionary;
return new BaseModelWithRequired(@required, serializedAdditionalRawData);
}

BinaryData IPersistableModel<BaseModelWithRequired>.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options);

/// <param name="options"> The client options for reading and writing models. </param>
protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options)
{
string format = options.Format == "W" ? ((IPersistableModel<BaseModelWithRequired>)this).GetFormatFromOptions(options) : options.Format;
switch (format)
{
case "J":
return ModelReaderWriter.Write(this, options);
default:
throw new FormatException($"The model {nameof(BaseModelWithRequired)} does not support writing '{options.Format}' format.");
}
}

BaseModelWithRequired IPersistableModel<BaseModelWithRequired>.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options);

/// <param name="data"> The data to parse. </param>
/// <param name="options"> The client options for reading and writing models. </param>
protected virtual BaseModelWithRequired PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options)
{
string format = options.Format == "W" ? ((IPersistableModel<BaseModelWithRequired>)this).GetFormatFromOptions(options) : options.Format;
switch (format)
{
case "J":
using (JsonDocument document = JsonDocument.Parse(data))
{
return DeserializeBaseModelWithRequired(document.RootElement, options);
}
default:
throw new FormatException($"The model {nameof(BaseModelWithRequired)} does not support reading '{options.Format}' format.");
}
}

string IPersistableModel<BaseModelWithRequired>.GetFormatFromOptions(ModelReaderWriterOptions options) => "J";

/// <param name="baseModelWithRequired"> The <see cref="BaseModelWithRequired"/> to serialize into <see cref="BinaryContent"/>. </param>
public static implicit operator BinaryContent(BaseModelWithRequired baseModelWithRequired)
{
throw new NotImplementedException("Not implemented");
}

/// <param name="result"> The <see cref="ClientResult"/> to deserialize the <see cref="BaseModelWithRequired"/> from. </param>
public static explicit operator BaseModelWithRequired(ClientResult result)
{
throw new NotImplementedException("Not implemented");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// <auto-generated/>

#nullable disable

namespace UnbrandedTypeSpec.Models
{
/// <summary> This is a base model with required properties. </summary>
public partial class BaseModelWithRequired
{
/// <summary> Initializes a new instance of <see cref="BaseModelWithRequired"/>. </summary>
/// <param name="required"> required property. </param>
public BaseModelWithRequired(int @required)
{
Required = @required;
}

/// <summary> required property. </summary>
public int Required { get; set; }
}
}
Loading
Loading