Merge remote-tracking branch 'origin/dev' into main-dev
This commit is contained in:
@@ -41,13 +41,7 @@ internal sealed class SpeckleHttpClientHandler : DelegatingHandler
|
||||
activity?.InjectHeaders((k, v) => request.Headers.TryAddWithoutValidation(k, v));
|
||||
|
||||
var policyResult = await _resiliencePolicy
|
||||
.ExecuteAndCaptureAsync(
|
||||
ctx =>
|
||||
{
|
||||
return base.SendAsync(request, cancellationToken);
|
||||
},
|
||||
context
|
||||
)
|
||||
.ExecuteAndCaptureAsync(ctx => base.SendAsync(request, cancellationToken), context)
|
||||
.ConfigureAwait(false);
|
||||
context.TryGetValue("retryCount", out var retryCount);
|
||||
activity?.SetTag("retryCount", retryCount);
|
||||
|
||||
@@ -127,10 +127,10 @@ public sealed class Client : ISpeckleGraphQLClient, IClient
|
||||
activity?.SetStatus(SdkActivityStatusCode.Ok);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
activity?.SetStatus(SdkActivityStatusCode.Error);
|
||||
activity?.RecordException(ex);
|
||||
// Don't record exception as it's rethrown.
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,14 @@ internal static class TypeLoader
|
||||
private static ConcurrentDictionary<string, Type> s_cachedTypes = new();
|
||||
private static ConcurrentDictionary<Type, string> s_fullTypeStrings = new();
|
||||
private static ConcurrentDictionary<PropertyInfo, JsonPropertyAttribute?> s_jsonPropertyAttribute = new();
|
||||
private static readonly ConcurrentDictionary<PropertyInfo, bool> s_obsolete = new();
|
||||
private static ConcurrentDictionary<Type, IReadOnlyList<PropertyInfo>> s_propInfoCache = new();
|
||||
|
||||
public static IEnumerable<LoadedType> Types => s_availableTypes;
|
||||
|
||||
public static bool IsObsolete(PropertyInfo property) =>
|
||||
s_obsolete.GetOrAdd(property, p => p.IsDefined(typeof(ObsoleteAttribute), true));
|
||||
|
||||
public static JsonPropertyAttribute? GetJsonPropertyAttribute(PropertyInfo property) =>
|
||||
s_jsonPropertyAttribute.GetOrAdd(property, p => p.GetCustomAttribute<JsonPropertyAttribute>(true));
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Newtonsoft.Json.Linq;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Helpers;
|
||||
using Speckle.Sdk.Host;
|
||||
using Speckle.Sdk.Serialisation;
|
||||
@@ -92,8 +91,7 @@ public class Base : DynamicBase, ISpeckleObject
|
||||
var typedProps = @base.GetInstanceMembers();
|
||||
foreach (var prop in typedProps.Where(p => p.CanRead))
|
||||
{
|
||||
bool isIgnored =
|
||||
prop.IsDefined(typeof(ObsoleteAttribute), true) || prop.IsDefined(typeof(JsonIgnoreAttribute), true);
|
||||
bool isIgnored = TypeLoader.IsObsolete(prop) || prop.IsDefined(typeof(JsonIgnoreAttribute), true);
|
||||
if (isIgnored)
|
||||
{
|
||||
continue;
|
||||
@@ -193,30 +191,4 @@ public class Base : DynamicBase, ISpeckleObject
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a shallow copy of the current base object.
|
||||
/// This operation does NOT copy/duplicate the data inside each prop.
|
||||
/// The new object's property values will be pointers to the original object's property value.
|
||||
/// </summary>
|
||||
/// <returns>A shallow copy of the original object.</returns>
|
||||
public Base ShallowCopy()
|
||||
{
|
||||
Type type = GetType();
|
||||
Base myDuplicate = (Base)Activator.CreateInstance(type).NotNull();
|
||||
myDuplicate.id = id;
|
||||
myDuplicate.applicationId = applicationId;
|
||||
|
||||
foreach (var kvp in GetMembers())
|
||||
{
|
||||
var propertyInfo = type.GetProperty(kvp.Key);
|
||||
if (propertyInfo is not null && !propertyInfo.CanWrite)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
myDuplicate[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
return myDuplicate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Speckle.Sdk.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Base class implementing a bunch of nice dynamic object methods, like adding and removing props dynamically. Makes c# feel like json.
|
||||
/// <para>Orginally adapted from Rick Strahl 🤘</para>
|
||||
/// <para>Originally adapted from Rick Strahl 🤘</para>
|
||||
/// <para>https://weblog.west-wind.com/posts/2012/feb/08/creating-a-dynamic-extensible-c-expando-object</para>
|
||||
/// </summary>
|
||||
public class DynamicBase : DynamicObject, IDynamicMetaObjectProvider
|
||||
@@ -84,6 +84,44 @@ public class DynamicBase : DynamicObject, IDynamicMetaObjectProvider
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a shallow copy of the current base object.
|
||||
/// This operation does NOT copy/duplicate the data inside each prop.
|
||||
/// The new object's property values will be pointers to the original object's property value.
|
||||
/// </summary>
|
||||
/// <returns>A shallow copy of the original object.</returns>
|
||||
public DynamicBase ShallowCopy()
|
||||
{
|
||||
Type type = GetType();
|
||||
DynamicBase myDuplicate = (DynamicBase)(
|
||||
Activator.CreateInstance(type) ?? throw new SpeckleException($"Failed to create instance of {type.Name}")
|
||||
);
|
||||
|
||||
// Add dynamic members
|
||||
foreach (var kvp in _properties)
|
||||
{
|
||||
myDuplicate._properties[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
var pinfos = TypeLoader.GetBaseProperties(type).Where(x => !TypeLoader.IsObsolete(x));
|
||||
foreach (var pi in pinfos)
|
||||
{
|
||||
if (pi.CanWrite)
|
||||
{
|
||||
try
|
||||
{
|
||||
pi.SetValue(myDuplicate, pi.GetValue(this));
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsFatal())
|
||||
{
|
||||
throw new SpeckleException($"Failed to set value for {type.Name}.{pi.Name}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return myDuplicate;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Gets properties via the dot syntax.
|
||||
@@ -232,7 +270,7 @@ public class DynamicBase : DynamicObject, IDynamicMetaObjectProvider
|
||||
.GetBaseProperties(GetType())
|
||||
.Where(x =>
|
||||
{
|
||||
var hasObsolete = x.IsDefined(typeof(ObsoleteAttribute), true);
|
||||
var hasObsolete = TypeLoader.IsObsolete(x);
|
||||
|
||||
// If obsolete is false and prop has obsolete attr
|
||||
// OR
|
||||
|
||||
@@ -18,7 +18,7 @@ public class SpecklePathTests
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
pattern = @"\/Users\/.*\/\.config";
|
||||
pattern = @"\/Users\/.*\/Library\/Application Support";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
@@ -57,7 +57,7 @@ public class SpecklePathTests
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
pattern = @"\/Users\/.*\/\.config";
|
||||
pattern = @"\/Users\/.*\/Library\/Application Support";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
|
||||
@@ -213,7 +213,9 @@ public class BaseTests
|
||||
[Fact]
|
||||
public void CanShallowCopy()
|
||||
{
|
||||
var sample = new SampleObject();
|
||||
var sample = new SampleObject { id = "sampleId" };
|
||||
dynamic x = sample;
|
||||
x.test = "test";
|
||||
var copy = sample.ShallowCopy();
|
||||
|
||||
var selectedMembers = DynamicBaseMemberType.Dynamic | DynamicBaseMemberType.Instance;
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.CSharp.RuntimeBinder;
|
||||
using Speckle.Sdk.Host;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Sdk.Tests.Unit.Models;
|
||||
|
||||
public class DynamicBaseTests
|
||||
{
|
||||
public DynamicBaseTests()
|
||||
{
|
||||
TypeLoader.Reset();
|
||||
TypeLoader.Initialize(typeof(Base).Assembly, typeof(BaseTests).Assembly);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Indexer_SetAndGet()
|
||||
{
|
||||
// Arrange
|
||||
var dynamicBase = new DynamicBase();
|
||||
var key = "testProperty";
|
||||
var value = "testValue";
|
||||
|
||||
// Act
|
||||
dynamicBase[key] = value;
|
||||
var result = dynamicBase[key];
|
||||
|
||||
// Assert
|
||||
result.Should().Be(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DynamicProperty_SetAndGet()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicBase = new DynamicBase();
|
||||
var value = "dynamicValue";
|
||||
|
||||
// Act
|
||||
dynamicBase.dynamicProperty = value;
|
||||
object result = dynamicBase.dynamicProperty;
|
||||
|
||||
// Assert
|
||||
result.Should().Be(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMembers_Default()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicBase = new DynamicBase();
|
||||
dynamicBase.dynamicProp = "hello";
|
||||
|
||||
// Act
|
||||
IDictionary<string, object?> members = dynamicBase.GetMembers();
|
||||
|
||||
// Assert
|
||||
members.Should().ContainKey("dynamicProp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMembers_Instance()
|
||||
{
|
||||
// Arrange
|
||||
var dynamicBase = new TestDynamicBase();
|
||||
|
||||
// Act
|
||||
var members = dynamicBase.GetMembers(DynamicBaseMemberType.Instance);
|
||||
|
||||
// Assert
|
||||
members.Should().ContainKey(nameof(TestDynamicBase.InstanceProperty));
|
||||
members.Should().NotContainKey("dynamicProp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDynamicMemberNames()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicBase = new DynamicBase();
|
||||
dynamicBase.prop1 = 1;
|
||||
dynamicBase.prop2 = "test";
|
||||
|
||||
// Act
|
||||
IEnumerable<string> memberNames = dynamicBase.GetDynamicMemberNames();
|
||||
|
||||
// Assert
|
||||
memberNames.Should().BeEquivalentTo(["DynamicPropertyKeys", "prop1", "prop2"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetMember_Existing()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicBase = new DynamicBase();
|
||||
dynamicBase.existingProp = "I exist";
|
||||
|
||||
// Act
|
||||
var result = dynamicBase.existingProp;
|
||||
|
||||
// Assert
|
||||
((object)result)
|
||||
.Should()
|
||||
.Be("I exist");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetMember_NonExisting()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicBase = new DynamicBase();
|
||||
|
||||
// Act
|
||||
Action act = () =>
|
||||
{
|
||||
var result = dynamicBase.nonExistingProp;
|
||||
};
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<RuntimeBinderException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TrySetMember()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicBase = new DynamicBase();
|
||||
|
||||
// Act
|
||||
dynamicBase.newProp = "newValue";
|
||||
|
||||
// Assert
|
||||
((object)dynamicBase.newProp)
|
||||
.Should()
|
||||
.Be("newValue");
|
||||
}
|
||||
|
||||
private class TestDynamicBase : DynamicBase
|
||||
{
|
||||
public string InstanceProperty { get; set; } = "instance";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user