Skip to content

Commit

Permalink
Merge pull request #291 from MartinM85/fix/247-enums-sendprimitiveasync
Browse files Browse the repository at this point in the history
SendPrimitiveAsync throws InvalidOperationException for enums
  • Loading branch information
andrueastman authored Jul 12, 2024
2 parents d141b77 + 1a01fe0 commit 880bff6
Show file tree
Hide file tree
Showing 19 changed files with 746 additions and 104 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.9.9] - 2024-07-12

- Fix enum deserialization for SendPrimitiveAsync and SendPrimitiveCollectionAsync

## [1.9.8] - 2024-07-08

- Migrated source of various libraries to mono repository at <https://github.com/microsoft/kiota-dotnet>.
Expand Down
41 changes: 1 addition & 40 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<!-- Common default project properties for ALL projects-->
<PropertyGroup>
<VersionPrefix>1.9.8</VersionPrefix>
<VersionPrefix>1.9.9</VersionPrefix>
<VersionSuffix></VersionSuffix>
<!-- This is overidden in test projects by setting to true-->
<IsTestProject>false</IsTestProject>
Expand All @@ -10,47 +10,8 @@
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- Common project properties for PACKAGED projects TO BE RELEASED-->
<PropertyGroup Condition="('$(IsTestProject)' != 'true') and ('$(IsAnalyzerProject)' != 'true')">
<Authors>Microsoft</Authors>
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<PackageIconUrl>http://go.microsoft.com/fwlink/?LinkID=288890</PackageIconUrl>
<RepositoryUrl>https://github.com/microsoft/kiota-dotnet</RepositoryUrl>
<PackageProjectUrl>https://aka.ms/kiota/docs</PackageProjectUrl>
<PackageReleaseNotes>
https://github.com/microsoft/kiota-dotnet/releases
</PackageReleaseNotes>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<Deterministic>true</Deterministic>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
<AssemblyOriginatorKeyFile>35MSSharedLib1024.snk</AssemblyOriginatorKeyFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>$(NoWarn);NU5048;NETSDK1138</NoWarn>
<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net5.0'))">true</IsTrimmable>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net8.0'))">true</IsAotCompatible>
<ContinuousIntegrationBuild Condition="'$(TF_BUILD)' == 'true'">true</ContinuousIntegrationBuild>
</PropertyGroup>
<!-- Common project properties for TEST projects-->
<PropertyGroup Condition="'$(IsTestProject)' == 'true'">
<IsPackable>false</IsPackable>
<OutputType>Library</OutputType>
</PropertyGroup>
<!-- Include ReadMe.md for PACKAGED projects TO BE RELEASED-->
<ItemGroup Condition="('$(IsTestProject)' != 'true') and ('$(IsAnalyzerProject)' != 'true')">
<None Include="README.md">
<Pack>True</Pack>
<PackagePath>
</PackagePath>
</None>
</ItemGroup>
<!-- Include SourceLink Dependency for packages TO BE RELEASED-->
<ItemGroup Condition="('$(IsTestProject)' != 'true') and ('$(IsAnalyzerProject)' != 'true')">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>
</Project>
6 changes: 6 additions & 0 deletions Microsoft.Kiota.sln
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Kiota.Serializati
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Kiota.Serialization.Text.Tests", "tests\serialization\text\Microsoft.Kiota.Serialization.Text.Tests.csproj", "{5F6AC278-C4A4-4EED-A7D3-1750A4D6FD15}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Kiota.Trimming.Validation", "tests\trimming\Microsoft.Kiota.Trimming.Validation\Microsoft.Kiota.Trimming.Validation.csproj", "{13992912-662D-4A3B-9354-DD49DB1AC2D5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -110,6 +112,10 @@ Global
{5F6AC278-C4A4-4EED-A7D3-1750A4D6FD15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F6AC278-C4A4-4EED-A7D3-1750A4D6FD15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F6AC278-C4A4-4EED-A7D3-1750A4D6FD15}.Release|Any CPU.Build.0 = Release|Any CPU
{13992912-662D-4A3B-9354-DD49DB1AC2D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{13992912-662D-4A3B-9354-DD49DB1AC2D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13992912-662D-4A3B-9354-DD49DB1AC2D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13992912-662D-4A3B-9354-DD49DB1AC2D5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
41 changes: 41 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
<!-- Common project properties for PACKAGED projects TO BE RELEASED-->
<PropertyGroup Condition="('$(IsTestProject)' != 'true') and ('$(IsAnalyzerProject)' != 'true')">
<Authors>Microsoft</Authors>
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<PackageIconUrl>http://go.microsoft.com/fwlink/?LinkID=288890</PackageIconUrl>
<RepositoryUrl>https://github.com/microsoft/kiota-dotnet</RepositoryUrl>
<PackageProjectUrl>https://aka.ms/kiota/docs</PackageProjectUrl>
<PackageReleaseNotes>
https://github.com/microsoft/kiota-dotnet/releases
</PackageReleaseNotes>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<Deterministic>true</Deterministic>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
<AssemblyOriginatorKeyFile>35MSSharedLib1024.snk</AssemblyOriginatorKeyFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>$(NoWarn);NU5048;NETSDK1138</NoWarn>
<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net5.0'))">true</IsTrimmable>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net8.0'))">true</IsAotCompatible>
<ContinuousIntegrationBuild Condition="'$(TF_BUILD)' == 'true'">true</ContinuousIntegrationBuild>
</PropertyGroup>
<!-- Include ReadMe.md for PACKAGED projects TO BE RELEASED-->
<ItemGroup Condition="('$(IsTestProject)' != 'true') and ('$(IsAnalyzerProject)' != 'true')">
<None Include="README.md">
<Pack>True</Pack>
<PackagePath>
</PackagePath>
</None>
</ItemGroup>
<!-- Include SourceLink Dependency for packages TO BE RELEASED-->
<ItemGroup Condition="('$(IsTestProject)' != 'true') and ('$(IsAnalyzerProject)' != 'true')">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>
</Project>
19 changes: 9 additions & 10 deletions src/abstractions/Helpers/EnumHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,46 +86,47 @@ private static ReadOnlySpan<char> ToEnumRawName<T>(ReadOnlySpan<char> span) wher
{
return null;
}
if(type.IsDefined(typeof(FlagsAttribute)))
Type enumType = (Nullable.GetUnderlyingType(type) is { IsEnum: true } underlyingType) ? underlyingType : type;
if(enumType.IsDefined(typeof(FlagsAttribute)))
{
int intValue = 0;
while(rawValue.Length > 0)
{
int commaIndex = rawValue.IndexOf(',');
var valueName = commaIndex < 0 ? rawValue : rawValue.Substring(0, commaIndex);
if(TryGetFieldValueName(type, valueName, out var value))
if(TryGetFieldValueName(enumType, valueName, out var value))
{
valueName = value;
}
#if NET5_0_OR_GREATER
if(Enum.TryParse(type, valueName, true, out var enumPartResult))
if(Enum.TryParse(enumType, valueName, true, out var enumPartResult))
intValue |= (int)enumPartResult!;
#else
try
{
intValue |= (int)Enum.Parse(type, valueName, true);
intValue |= (int)Enum.Parse(enumType, valueName, true);
}
catch { }
#endif

rawValue = commaIndex < 0 ? string.Empty : rawValue.Substring(commaIndex + 1);
}
result = intValue > 0 ? Enum.Parse(type, intValue.ToString(), true) : null;
result = intValue > 0 ? Enum.Parse(enumType, intValue.ToString(), true) : null;
}
else
{
if(TryGetFieldValueName(type, rawValue, out var value))
if(TryGetFieldValueName(enumType, rawValue, out var value))
{
rawValue = value;
}

#if NET5_0_OR_GREATER
Enum.TryParse(type, rawValue, true, out object? enumResult);
Enum.TryParse(enumType, rawValue, true, out object? enumResult);
result = enumResult;
#else
try
{
result = Enum.Parse(type, rawValue, true);
result = Enum.Parse(enumType, rawValue, true);
}
catch
{
Expand All @@ -134,8 +135,6 @@ private static ReadOnlySpan<char> ToEnumRawName<T>(ReadOnlySpan<char> span) wher
#endif
}
return result;


}

#if NET5_0_OR_GREATER
Expand Down
4 changes: 4 additions & 0 deletions src/abstractions/serialization/IParseNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ public interface IParseNode
/// Gets the collection of primitive values of the node.
/// </summary>
/// <returns>The collection of primitive values.</returns>
#if NET5_0_OR_GREATER
IEnumerable<T> GetCollectionOfPrimitiveValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>();
#else
IEnumerable<T> GetCollectionOfPrimitiveValues<T>();
#endif
/// <summary>
/// Gets the collection of enum values of the node.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/http/httpClient/HttpClientRequestAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Authentication;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Helpers;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions.Store;
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware;
Expand Down Expand Up @@ -302,6 +303,10 @@ public string? BaseUrl
{
result = rootNode.GetDateValue();
}
else if(rootNode.GetStringValue() is { Length: > 0 } rawValue)
{
result = EnumHelpers.GetEnumValue(modelType, rawValue);
}
else throw new InvalidOperationException("error handling the response, unexpected type");
SetResponseType(result, span);
return (ModelType)result!;
Expand Down
36 changes: 7 additions & 29 deletions src/serialization/form/FormParseNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Xml;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Helpers;
using Microsoft.Kiota.Abstractions.Serialization;
#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
Expand Down Expand Up @@ -96,7 +97,11 @@ private static string SanitizeKey(string key)
/// Get the collection of primitives of type <typeparam name="T"/>from the form node
/// </summary>
/// <returns>A collection of objects</returns>
#if NET5_0_OR_GREATER
public IEnumerable<T> GetCollectionOfPrimitiveValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>()
#else
public IEnumerable<T> GetCollectionOfPrimitiveValues<T>()
#endif
{
var genericType = typeof(T);
var primitiveValueCollection = DecodedValue.Split(ComaSeparator, StringSplitOptions.RemoveEmptyEntries);
Expand Down Expand Up @@ -226,7 +231,7 @@ private void AssignFieldValues<T>(T item) where T : IParsable
#endif
{
foreach(var v in DecodedValue.Split(ComaSeparator, StringSplitOptions.RemoveEmptyEntries))
yield return GetEnumValueInternal<T>(v);
yield return EnumHelpers.GetEnumValue<T>(v);
}

#if NET5_0_OR_GREATER
Expand All @@ -235,33 +240,6 @@ private void AssignFieldValues<T>(T item) where T : IParsable
T? IParseNode.GetEnumValue<T>()
#endif
{
return GetEnumValueInternal<T>(DecodedValue);
}

private static T? GetEnumValueInternal<T>(string rawValue) where T : struct, Enum
{
if(string.IsNullOrEmpty(rawValue))
return null;
if(typeof(T).IsDefined(typeof(FlagsAttribute)))
{
ReadOnlySpan<char> valueSpan = rawValue.AsSpan();
int value = 0;
while(valueSpan.Length > 0)
{
int commaIndex = valueSpan.IndexOf(',');
ReadOnlySpan<char> valueNameSpan = commaIndex < 0 ? valueSpan : valueSpan.Slice(0, commaIndex);
#if NET6_0_OR_GREATER
if(Enum.TryParse<T>(valueNameSpan, true, out var result))
#else
if(Enum.TryParse<T>(valueNameSpan.ToString(), true, out var result))
#endif
value |= (int)(object)result;
valueSpan = commaIndex < 0 ? ReadOnlySpan<char>.Empty : valueSpan.Slice(commaIndex + 1);
}
return (T)(object)value;
}
else if(Enum.TryParse<T>(rawValue, out var result))
return result;
return null;
return EnumHelpers.GetEnumValue<T>(DecodedValue);
}
}
33 changes: 10 additions & 23 deletions src/serialization/json/JsonParseNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Xml;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Helpers;
using Microsoft.Kiota.Abstractions.Serialization;

#if NET5_0_OR_GREATER
Expand Down Expand Up @@ -217,29 +218,7 @@ public JsonParseNode(JsonElement node, KiotaJsonSerializationContext jsonSeriali
#endif
{
var rawValue = _jsonNode.GetString();
if(string.IsNullOrEmpty(rawValue)) return null;

rawValue = ToEnumRawName<T>(rawValue!);
if(typeof(T).IsDefined(typeof(FlagsAttribute)))
{
ReadOnlySpan<char> valueSpan = rawValue.AsSpan();
int value = 0;
while(valueSpan.Length > 0)
{
int commaIndex = valueSpan.IndexOf(',');
ReadOnlySpan<char> valueNameSpan = commaIndex < 0 ? valueSpan : valueSpan.Slice(0, commaIndex);
#if NET6_0_OR_GREATER
if(Enum.TryParse<T>(valueNameSpan, true, out var result))
#else
if(Enum.TryParse<T>(valueNameSpan.ToString(), true, out var result))
#endif
value |= (int)(object)result;
valueSpan = commaIndex < 0 ? ReadOnlySpan<char>.Empty : valueSpan.Slice(commaIndex + 1);
}
return (T)(object)value;
}
else
return Enum.TryParse<T>(rawValue, true, out var result) ? result : null;
return EnumHelpers.GetEnumValue<T>(rawValue!);
}

/// <summary>
Expand Down Expand Up @@ -309,7 +288,11 @@ public IEnumerable<T> GetCollectionOfObjectValues<T>(ParsableFactory<T> factory)
/// Get the collection of primitives of type <typeparam name="T"/>from the json node
/// </summary>
/// <returns>A collection of objects</returns>
#if NET5_0_OR_GREATER
public IEnumerable<T> GetCollectionOfPrimitiveValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>()
#else
public IEnumerable<T> GetCollectionOfPrimitiveValues<T>()
#endif
{
if(_jsonNode.ValueKind == JsonValueKind.Array)
{
Expand Down Expand Up @@ -347,6 +330,10 @@ public IEnumerable<T> GetCollectionOfPrimitiveValues<T>()
yield return (T)(object)currentParseNode.GetDateValue()!;
else if(genericType == TypeConstants.TimeType)
yield return (T)(object)currentParseNode.GetTimeValue()!;
else if(currentParseNode.GetStringValue() is { Length: > 0 } rawValue)
{
yield return (T)EnumHelpers.GetEnumValue(genericType, rawValue)!;
}
else
throw new InvalidOperationException($"unknown type for deserialization {genericType.FullName}");
}
Expand Down
6 changes: 5 additions & 1 deletion src/serialization/json/JsonSerializationWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -432,15 +432,19 @@ public void WriteAdditionalData(IDictionary<string, object> value)
WriteAnyValue(dataValue.Key, dataValue.Value);
}

#if NET5_0_OR_GREATER
private void WriteNonParsableObjectValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string? key,T value)
#else
private void WriteNonParsableObjectValue<T>(string? key, T value)
#endif
{
if(!string.IsNullOrEmpty(key))
writer.WritePropertyName(key!);
writer.WriteStartObject();
if(value == null)
writer.WriteNullValue();
else
foreach(var oProp in value.GetType().GetProperties())
foreach(var oProp in typeof(T).GetProperties())
WriteAnyValue(oProp.Name, oProp.GetValue(value));
writer.WriteEndObject();
}
Expand Down
7 changes: 6 additions & 1 deletion src/serialization/text/TextParseNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Xml;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Helpers;
using Microsoft.Kiota.Abstractions.Serialization;
#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
Expand Down Expand Up @@ -38,7 +39,11 @@ public TextParseNode(string? text)
/// <inheritdoc />
public IEnumerable<T> GetCollectionOfObjectValues<T>(ParsableFactory<T> factory) where T : IParsable => throw new InvalidOperationException(NoStructuredDataMessage);
/// <inheritdoc />
#if NET5_0_OR_GREATER
public IEnumerable<T> GetCollectionOfPrimitiveValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>()=> throw new InvalidOperationException(NoStructuredDataMessage);
#else
public IEnumerable<T> GetCollectionOfPrimitiveValues<T>() => throw new InvalidOperationException(NoStructuredDataMessage);
#endif
/// <inheritdoc />
public DateTimeOffset? GetDateTimeOffsetValue() => DateTimeOffset.TryParse(Text, out var result) ? result : null;
/// <inheritdoc />
Expand Down Expand Up @@ -78,5 +83,5 @@ public TextParseNode(string? text)
#else
public T? GetEnumValue<T>() where T : struct, Enum
#endif
=> Enum.TryParse<T>(Text, true, out var result) ? result : null;
=> Text is null ? null : EnumHelpers.GetEnumValue<T>(Text);
}
Loading

0 comments on commit 880bff6

Please sign in to comment.