Skip to content

Commit

Permalink
feat(generator): flatten model properties
Browse files Browse the repository at this point in the history
This commit will flatten the model property in generator, instead of in the emitter.

- Modify `InputModelProperty`
  - add an `IsFlatten` property for original emitter input
  - remove `FlattenNames` property from constructor
- change model json converter:
  - convert `TypeSpecInputModelProperty` to `InputModelProperty`
  - flatten model property if necessary
- refactor

resolve #4449
  • Loading branch information
Mingzhe Huang (from Dev Box) committed Sep 27, 2024
1 parent 22992df commit 5b03a8f
Show file tree
Hide file tree
Showing 90 changed files with 1,735 additions and 266 deletions.
13 changes: 5 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions src/AutoRest.CSharp/Common/Input/CodeModelConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -436,8 +436,10 @@ private InputModelProperty GetOrCreateProperty(Property property)
ConstantValue: property.Schema is ConstantSchema constantSchema ? CreateConstant(constantSchema, constantSchema.Extensions?.Format, property.IsNullable) : null,
IsRequired: property.IsRequired,
IsReadOnly: property.IsReadOnly,
IsDiscriminator: property.IsDiscriminator ?? false,
FlattenedNames: property.FlattenedNames.ToList());
IsDiscriminator: property.IsDiscriminator ?? false)
{
FlattenedNames = property.FlattenedNames.ToList(),
};
_propertiesCache.Add(property, inputProperty);

return inputProperty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@

namespace AutoRest.CSharp.Common.Input;

internal record InputModelProperty(string Name, string SerializedName, string Description, InputType Type, InputConstant? ConstantValue, bool IsRequired, bool IsReadOnly, bool IsDiscriminator, IReadOnlyList<string>? FlattenedNames = null)
internal record InputModelProperty(string Name, string SerializedName, string Description, InputType Type, InputConstant? ConstantValue, bool IsRequired, bool IsReadOnly, bool IsDiscriminator)
{
public FormattableString? DefaultValue { get; init; }

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

// original emitter input
public bool Flatten { get; internal set; }

// calculated flatten prefix names
public IReadOnlyList<string>? FlattenedNames { get; internal set; }
};
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ private static InputModelProperty ReadInputModelProperty(ref Utf8JsonReader read
bool isOptional = false;
bool isDiscriminator = false;
IReadOnlyList<InputDecoratorInfo>? decorators = null;
IReadOnlyList<string>? flattenedNames = null;
bool flatten = false;

while (reader.TokenType != JsonTokenType.EndObject)
{
Expand All @@ -48,7 +48,7 @@ private static InputModelProperty ReadInputModelProperty(ref Utf8JsonReader read
|| reader.TryReadBoolean("optional", ref isOptional)
|| reader.TryReadBoolean("discriminator", ref isDiscriminator)
|| reader.TryReadWithConverter("decorators", options, ref decorators)
|| reader.TryReadStringArray("flattenedNames", ref flattenedNames);
|| reader.TryReadBoolean("flatten", ref flatten);

if (!isKnownProperty)
{
Expand All @@ -66,7 +66,11 @@ private static InputModelProperty ReadInputModelProperty(ref Utf8JsonReader read
propertyType = lt.ValueType;
}

var property = new InputModelProperty(name, serializedName ?? name, description, propertyType, defaultValue, !isOptional, isReadOnly, isDiscriminator, flattenedNames) { Decorators = decorators ?? [] };
var property = new InputModelProperty(name, serializedName ?? name, description, propertyType, defaultValue, !isOptional, isReadOnly, isDiscriminator)
{
Decorators = decorators ?? [],
Flatten = flatten
};
if (id != null)
{
resolver.AddReference(id, property);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public static InputModelType CreateModelType(ref Utf8JsonReader reader, string?
{
model = model ?? CreateInputModelTypeInstance(id, name, crossLanguageDefinitionId, access, deprecation, description, usageString, discriminatorValue, discriminatorProperty, baseModel, properties, discriminatedSubtypes, additionalProperties, decorators, resolver);
reader.Read();
CreateProperties(ref reader, properties, options, model.Usage.HasFlag(InputModelTypeUsage.MultipartFormData));
CreateProperties(ref reader, properties, options, name!, model.Usage.HasFlag(InputModelTypeUsage.MultipartFormData));
continue;
}
if (reader.GetString() == "discriminatedSubtypes")
Expand Down Expand Up @@ -135,58 +135,65 @@ private static InputModelType CreateInputModelTypeInstance(string? id, string? n
return model;
}

private static void CreateProperties(ref Utf8JsonReader reader, ICollection<InputModelProperty> properties, JsonSerializerOptions options, bool isMultipartType)
private static void CreateProperties(ref Utf8JsonReader reader, ICollection<InputModelProperty> properties, JsonSerializerOptions options, string modelName, bool isMultipartType)
{
if (reader.TokenType != JsonTokenType.StartArray)
{
throw new JsonException();
throw new JsonException($"Invalid JSON format. 'properties' property of '{modelName}' should be an array.");
}
reader.Read();

while (reader.TokenType != JsonTokenType.EndArray)
{
var property = reader.ReadWithConverter<InputModelProperty>(options);
/* TODO: in Multipart body model, if the property is of type Bytes, it should be converted to Stream
* In future, we will convert this in emitter when we adopt tcgc.
*/
if (property != null && isMultipartType)
var rawProperty = reader.ReadWithConverter<InputModelProperty>(options);
if (rawProperty == null)
{
static InputType ConvertPropertyType(InputType propertyType)
{
return propertyType switch
{
InputPrimitiveType { Kind: InputPrimitiveTypeKind.Bytes } => InputPrimitiveType.Stream,
InputListType listType => new InputListType(listType.Name, listType.CrossLanguageDefinitionId, ConvertPropertyType(listType.ValueType))
{
Decorators = listType.Decorators
},
InputDictionaryType dictionaryType => new InputDictionaryType(dictionaryType.Name, dictionaryType.KeyType, ConvertPropertyType(dictionaryType.ValueType))
{
Decorators = dictionaryType.Decorators
},
InputNullableType nullableType => new InputNullableType(ConvertPropertyType(nullableType.Type))
{
Decorators = nullableType.Decorators
},
_ => propertyType
};
}
throw new JsonException($"Property of model '{modelName}' cannot be null.");
}

property = new InputModelProperty(property.Name,
property.SerializedName,
property.Description,
ConvertPropertyType(property.Type),
property.ConstantValue,
property.IsRequired,
property.IsReadOnly,
property.IsDiscriminator,
property.FlattenedNames);
var flattenedProperties = FlattenPropertyIfNecessary(rawProperty);

foreach (var property in flattenedProperties)
{
properties.Add(isMultipartType ? ConvertMultipartProperty(property) : property);
}
properties.Add(property ?? throw new JsonException($"null {nameof(InputModelProperty)} is not allowed"));
}
reader.Read();
}

/// <summary>
/// Flatten the property if <see cref="InputModelProperty.Flatten"/> is true.
/// </summary>
/// <param name="propertyToFlatten"> <see cref="InputModelProperty"/> model property type passed in by emitter to be flattened. </param>
/// <returns> One or more <see cref="InputModelProperty"/> instances. </returns>
private static IEnumerable<InputModelProperty> FlattenPropertyIfNecessary(InputModelProperty propertyToFlatten)
{
if (propertyToFlatten.Flatten is false)
{
yield return propertyToFlatten;
}
else if (propertyToFlatten.Type is InputModelType model)
{
foreach (var p in model.Properties)
{
var newFlattenedNames = new List<string>() { propertyToFlatten.Name };
if (p.FlattenedNames != null)
{
newFlattenedNames.AddRange(p.FlattenedNames);
}
else
{
newFlattenedNames.Add(p.Name);
}
yield return p with { FlattenedNames = newFlattenedNames };
}
}
else
{
throw new JsonException($"Flattened property '{propertyToFlatten.Name}' must be a model type.");
}
}

private static void CreateDiscriminatedSubtypes(ref Utf8JsonReader reader, IDictionary<string, InputModelType> discriminatedSubtypes, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
Expand All @@ -212,5 +219,45 @@ private static void CreateDiscriminatedSubtypes(ref Utf8JsonReader reader, IDict

reader.Read();
}

/* TODO: in Multipart body model, if the property is of type Bytes, it should be converted to Stream
* In future, we will convert this in emitter when we adopt tcgc.
*/
private static InputModelProperty ConvertMultipartProperty(InputModelProperty property)
=> new InputModelProperty(
property.Name,
property.SerializedName,
property.Description,
ConvertPropertyType(property.Type),
property.ConstantValue,
property.IsRequired,
property.IsReadOnly,
property.IsDiscriminator)
{
DefaultValue = property.DefaultValue,
Decorators = property.Decorators,
FlattenedNames = property.FlattenedNames
};

private static InputType ConvertPropertyType(InputType propertyType)
{
return propertyType switch
{
InputPrimitiveType { Kind: InputPrimitiveTypeKind.Bytes } => InputPrimitiveType.Stream,
InputListType listType => new InputListType(listType.Name, listType.CrossLanguageDefinitionId, ConvertPropertyType(listType.ValueType))
{
Decorators = listType.Decorators
},
InputDictionaryType dictionaryType => new InputDictionaryType(dictionaryType.Name, dictionaryType.KeyType, ConvertPropertyType(dictionaryType.ValueType))
{
Decorators = dictionaryType.Decorators
},
InputNullableType nullableType => new InputNullableType(ConvertPropertyType(nullableType.Type))
{
Decorators = nullableType.Decorators
},
_ => propertyType
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ protected override IEnumerable<ObjectTypeProperty> BuildProperties()
{
inputType = new InputListType(string.Empty, string.Empty, InputPrimitiveType.Boolean);
}
inputModelProperty = new InputModelProperty(property.Name, GetSerializedName(property.Name, SystemType), GetPropertySummary(setter != null, property.Name), inputType, null, IsRequired(property, SystemType), property.IsReadOnly(), false, null);
inputModelProperty = new InputModelProperty(property.Name, GetSerializedName(property.Name, SystemType), GetPropertySummary(setter != null, property.Name), inputType, null, IsRequired(property, SystemType), property.IsReadOnly(), false);

}

Expand Down
2 changes: 1 addition & 1 deletion src/TypeSpec.Extension/Emitter.Csharp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"dependencies": {
"@autorest/csharp": "3.0.0-beta.20240625.4",
"json-serialize-refs": "0.1.0-0",
"@typespec/http-client-csharp": "0.1.9-alpha.20240925.1"
"@typespec/http-client-csharp": "https://artprodcus3.artifacts.visualstudio.com/A0fb41ef4-5012-48a9-bf39-4ee3de03ee35/29ec6040-b234-4e31-b139-33dc4287b756/_apis/artifact/cGlwZWxpbmVhcnRpZmFjdDovL2F6dXJlLXNkay9wcm9qZWN0SWQvMjllYzYwNDAtYjIzNC00ZTMxLWIxMzktMzNkYzQyODdiNzU2L2J1aWxkSWQvNDE3NzgzOS9hcnRpZmFjdE5hbWUvYnVpbGRfYXJ0aWZhY3RzX2NzaGFycA2/content?format=file&subPath=%2fpackages%2ftypespec-http-client-csharp-0.1.9-alpha.20240926.51.tgz"
},
"peerDependencies": {
"@azure-tools/typespec-azure-core": ">=0.36.0 <1.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"optional": false,
"readOnly": false,
"discriminator": false,
"flatten": false,
"decorators": [],
"crossLanguageDefinitionId": "Authentication.ApiKey.InvalidAuth.error"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"optional": false,
"readOnly": false,
"discriminator": false,
"flatten": false,
"decorators": [],
"crossLanguageDefinitionId": "Authentication.Http.Custom.InvalidAuth.error"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"optional": false,
"readOnly": false,
"discriminator": false,
"flatten": false,
"decorators": [],
"crossLanguageDefinitionId": "Authentication.OAuth2.InvalidAuth.error"
}
Expand Down
Loading

0 comments on commit 5b03a8f

Please sign in to comment.