diff --git a/sdmap/Common.Version.props b/sdmap/Common.Version.props new file mode 100644 index 0000000..702704d --- /dev/null +++ b/sdmap/Common.Version.props @@ -0,0 +1,14 @@ + + + + 0.17.0 + + + + $(Version) + $(Version) + $(Version) + $(Version) + + + diff --git a/sdmap/sdmap.sln b/sdmap/sdmap.sln index 2e5d55f..5c13963 100644 --- a/sdmap/sdmap.sln +++ b/sdmap/sdmap.sln @@ -12,6 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\README.md = ..\README.md ..\ReleaseNotes.md = ..\ReleaseNotes.md vstool.png = vstool.png + Common.Version.props = Common.Version.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{37A082F1-8179-4EEA-93D8-1C282B5AC65A}" diff --git a/sdmap/src/sdmap.ext.Dapper/sdmap.ext.Dapper.csproj b/sdmap/src/sdmap.ext.Dapper/sdmap.ext.Dapper.csproj index 2b0d125..8b87e65 100644 --- a/sdmap/src/sdmap.ext.Dapper/sdmap.ext.Dapper.csproj +++ b/sdmap/src/sdmap.ext.Dapper/sdmap.ext.Dapper.csproj @@ -1,5 +1,7 @@  + + net6.0;netstandard20 sdmap.ext.Dapper @@ -14,13 +16,10 @@ https://github.com/sdcb/sdmap/blob/master/ReleaseNotes.md false false false - 0.16.5 Dapper extensions for sdmap. https://github.com/sdcb/sdmap sdcb MIT - 0.16.5 - 0.16.5 LICENSE true true diff --git a/sdmap/src/sdmap.ext/sdmap.ext.csproj b/sdmap/src/sdmap.ext/sdmap.ext.csproj index d6b0843..b3d61cc 100644 --- a/sdmap/src/sdmap.ext/sdmap.ext.csproj +++ b/sdmap/src/sdmap.ext/sdmap.ext.csproj @@ -1,5 +1,7 @@  + + net6.0;netstandard20 sdmap.ext @@ -14,13 +16,10 @@ https://github.com/sdcb/sdmap/blob/master/ReleaseNotes.md false false false - 0.16.5 Useful extensions for sdmap/Dapper. https://github.com/sdcb/sdmap sdcb MIT - 0.16.5 - 0.16.5 LICENSE true true diff --git a/sdmap/src/sdmap/IsExternalInit.cs b/sdmap/src/sdmap/IsExternalInit.cs new file mode 100644 index 0000000..589db89 --- /dev/null +++ b/sdmap/src/sdmap/IsExternalInit.cs @@ -0,0 +1,8 @@ +// ReSharper disable CheckNamespace +// ReSharper disable UnusedType.Global + +namespace System.Runtime.CompilerServices; + +internal sealed class IsExternalInit +{ +} \ No newline at end of file diff --git a/sdmap/src/sdmap/Macros/Implements/PropertyMetadataRetriever.cs b/sdmap/src/sdmap/Macros/Implements/PropertyMetadataRetriever.cs new file mode 100644 index 0000000..63ad896 --- /dev/null +++ b/sdmap/src/sdmap/Macros/Implements/PropertyMetadataRetriever.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections; +using System.Linq; + +namespace sdmap.Macros.Implements; + +using static PropertyMetadata; + +internal readonly record struct PropertyMetadata(string Name, object Value, bool Exists = true) +{ + public Type Type => Value?.GetType() ?? typeof(object); + + public static PropertyMetadata DoesNotExist => new(string.Empty, default, false); + + public static PropertyMetadata Root(object value) => new(string.Empty, value); +} + +internal static class PropertyMetadataRetriever +{ + public static PropertyMetadata Get(object target, string propertyAccess) + { + if (string.IsNullOrWhiteSpace(propertyAccess)) + { + return DoesNotExist; + } + + return propertyAccess + .Split('.') + .Aggregate(Root(target), (metadata, next) => GetByKey(metadata.Value, next)); + } + + private static PropertyMetadata GetByKey(object target, string key) + => target switch + { + _ when string.IsNullOrWhiteSpace(key) + => DoesNotExist, + + IDictionary dictionary + => dictionary.Contains(key) + ? new(key, dictionary[key]) + : DoesNotExist, + + not null + => GetByMemberName(target, key), + + _ => DoesNotExist + }; + + private static PropertyMetadata GetByMemberName(object target, string memberName) + { + var type = target.GetType(); + + if (type.GetProperty(memberName) is { } property) + { + return new(memberName, property.GetValue(target)); + } + + if (type.GetField(memberName) is { } field) + { + return new(memberName, field.GetValue(target)); + } + + return DoesNotExist; + } +} \ No newline at end of file diff --git a/sdmap/src/sdmap/Macros/Implements/RuntimeMacros.cs b/sdmap/src/sdmap/Macros/Implements/RuntimeMacros.cs index c953d86..003edc7 100644 --- a/sdmap/src/sdmap/Macros/Implements/RuntimeMacros.cs +++ b/sdmap/src/sdmap/Macros/Implements/RuntimeMacros.cs @@ -4,7 +4,6 @@ using System; using System.Collections; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Text; @@ -440,48 +439,16 @@ private static Result RequireType(Type type, string expected, public static QueryPropertyInfo GetProp(object self, object syntax) { - var props = (syntax as string).Split('.'); - var fronts = props.Take(props.Length - 1); - - if (self is IDictionary dicSelf) + if (syntax is not string propertyAccess) { - if (!dicSelf.Contains(syntax)) - return null; - - var val = dicSelf[syntax]; - if (val == null) - return new QueryPropertyInfo(props[0], typeof(object)); - - return new QueryPropertyInfo(props[0], val.GetType()); + throw new ArgumentException("Variable is expected to be of type string.", nameof(syntax)); } - else - { - var frontValue = fronts.Aggregate(self, (s, p) => - s?.GetType().GetTypeInfo().GetProperty(p)?.GetValue(s)); - - var pi = frontValue - ?.GetType() - .GetTypeInfo() - .GetProperty(props.Last()); - if (pi == null) return null; - return new QueryPropertyInfo(pi.Name, pi.PropertyType); - } + var metadata = PropertyMetadataRetriever.Get(self, propertyAccess); + return metadata.Exists ? new(metadata.Name, metadata.Type) : null; } - public static object GetPropValue(object self, string prop) - { - if (self is IDictionary dicSelf) - { - return dicSelf[prop]; - } - else - { - var props = prop.Split('.'); - return props.Aggregate(self, (s, p) => - s?.GetType().GetTypeInfo().GetProperty(p)?.GetValue(s)); - } - } + public static object GetPropValue(object self, string prop) => PropertyMetadataRetriever.Get(self, prop).Value; public static bool IsEmpty(object v) { diff --git a/sdmap/src/sdmap/sdmap.csproj b/sdmap/src/sdmap/sdmap.csproj index 3ec42ae..f85af39 100644 --- a/sdmap/src/sdmap/sdmap.csproj +++ b/sdmap/src/sdmap/sdmap.csproj @@ -1,5 +1,7 @@  + + A template engine for writing dynamic sql. net6;netstandard20 @@ -15,9 +17,6 @@ false false false - 0.16.5 - 0.16.5 - 0.16.5 sdcb MIT https://github.com/sdcb/sdmap diff --git a/sdmap/test/sdmap.test/PropertyMetadataRetrieverTests.cs b/sdmap/test/sdmap.test/PropertyMetadataRetrieverTests.cs new file mode 100644 index 0000000..266b22d --- /dev/null +++ b/sdmap/test/sdmap.test/PropertyMetadataRetrieverTests.cs @@ -0,0 +1,261 @@ +using System.Collections.Generic; +using FluentAssertions; +using sdmap.Macros.Implements; +using Xunit; + +// ReSharper disable UnusedAutoPropertyAccessor.Local + +namespace sdmap.test; + +public class PropertyMetadataRetrieverTests +{ + [Fact] + public void NullObject_ReturnsNull() + { + var metadata = PropertyMetadataRetriever.Get(target: null, "Prop"); + + ShouldBeEmpty(metadata); + } + + [Theory] + [InlineData(data: null)] + [InlineData("")] + [InlineData(" ")] + public void NullOrEmptyProp_ReturnsNull(string propertyAccess) + { + var target = new ComplexObject { Prop = "Test" }; + + var metadata = PropertyMetadataRetriever.Get(target, propertyAccess); + + ShouldBeEmpty(metadata); + } + + [Fact] + public void NonExistentProp_ReturnsNull() + { + var target = new ComplexObject { Prop = "Test" }; + + var metadata = PropertyMetadataRetriever.Get(target, "NonExistent"); + + ShouldBeEmpty(metadata); + } + + [Fact] + public void ValidProp_ReturnsValue() + { + var target = new ComplexObject { Prop = "Test" }; + + var metadata = PropertyMetadataRetriever.Get(target, "Prop"); + + metadata.Name.Should().Be("Prop"); + metadata.Value.Should().Be("Test"); + metadata.Type.Should().Be(typeof(string)); + } + + [Fact] + public void ValidField_ReturnsValue() + { + var target = new ComplexObject { Field = "Test" }; + + var metadata = PropertyMetadataRetriever.Get(target, "Field"); + + metadata.Name.Should().Be("Field"); + metadata.Value.Should().Be("Test"); + metadata.Type.Should().Be(typeof(string)); + } + + [Fact] + public void NestedProp_ReturnsValue() + { + var target = new ComplexObject + { + Prop = new ComplexObject + { + Prop = "NestedTest" + } + }; + + var metadata = PropertyMetadataRetriever.Get(target, "Prop.Prop"); + + metadata.Name.Should().Be("Prop"); + metadata.Value.Should().Be("NestedTest"); + metadata.Type.Should().Be(typeof(string)); + } + + [Fact] + public void NestedPropAndField_ReturnsValue() + { + var target = new ComplexObject + { + Prop = new ComplexObject + { + Field = "NestedTest" + } + }; + + var metadata = PropertyMetadataRetriever.Get(target, "Prop.Field"); + + metadata.Name.Should().Be("Field"); + metadata.Value.Should().Be("NestedTest"); + metadata.Type.Should().Be(typeof(string)); + } + + [Fact] + public void Dictionary_ReturnsValue() + { + var target = new Dictionary + { + ["Key"] = "Value" + }; + + var metadata = PropertyMetadataRetriever.Get(target, "Key"); + + metadata.Name.Should().Be("Key"); + metadata.Value.Should().Be("Value"); + metadata.Type.Should().Be(typeof(string)); + } + + [Fact] + public void DictionaryNonExistentKey_ReturnsNull() + { + var target = new Dictionary + { + ["Key"] = "Value" + }; + + var metadata = PropertyMetadataRetriever.Get(target, "NonExistentKey"); + + ShouldBeEmpty(metadata); + } + + [Fact] + public void DictionaryWithNestedDictionary_ReturnsValue() + { + var target = new Dictionary + { + ["Key"] = new Dictionary { ["NestedKey"] = "NestedValue" } + }; + + var metadata = PropertyMetadataRetriever.Get(target, "Key.NestedKey"); + + metadata.Name.Should().Be("NestedKey"); + metadata.Value.Should().Be("NestedValue"); + metadata.Type.Should().Be(typeof(string)); + } + + [Fact] + public void ObjectWithNestedDictionary_ReturnsValue() + { + var target = new ComplexObject + { + Prop = new Dictionary + { + ["DictionaryKey"] = "DictionaryValue" + } + }; + + var metadata = PropertyMetadataRetriever.Get(target, "Prop.DictionaryKey"); + + metadata.Name.Should().Be("DictionaryKey"); + metadata.Value.Should().Be("DictionaryValue"); + metadata.Type.Should().Be(typeof(string)); + } + + [Fact] + public void ObjectWithNestedDictionary_NonExistentKey_ReturnsNull() + { + var target = new ComplexObject + { + Prop = new Dictionary + { + ["DictionaryKey"] = "DictionaryValue" + } + }; + + var metadata = PropertyMetadataRetriever.Get(target, "Prop.NonExistentKey"); + + ShouldBeEmpty(metadata); + } + + [Fact] + public void DictionaryWithNestedObject_ReturnsValue() + { + var target = new Dictionary + { + ["ObjectKey"] = new ComplexObject { Prop = new ComplexObject() } + }; + + var metadata = PropertyMetadataRetriever.Get(target, "ObjectKey.Prop"); + + metadata.Name.Should().Be("Prop"); + metadata.Value.Should().BeEquivalentTo(new ComplexObject()); + metadata.Type.Should().Be(typeof(ComplexObject)); + } + + [Fact] + public void DictionaryWithNestedObject_NonExistentProperty_ReturnsNull() + { + var target = new Dictionary + { + ["ObjectKey"] = new ComplexObject { Prop = "NestedObjectValue" } + }; + + var metadata = PropertyMetadataRetriever.Get(target, "ObjectKey.NonExistentProp"); + + ShouldBeEmpty(metadata); + } + + [Fact] + public void ObjectWithNestedDictionaryAndObject_ReturnsValue() + { + var target = new ComplexObject + { + Prop = new Dictionary + { + ["NestedObjectKey"] = new ComplexObject { Prop = "NestedObjectValue" } + } + }; + + var metadata = PropertyMetadataRetriever.Get(target, "Prop.NestedObjectKey.Prop"); + + metadata.Name.Should().Be("Prop"); + metadata.Value.Should().Be("NestedObjectValue"); + metadata.Type.Should().Be(typeof(string)); + } + + [Fact] + public void DictionaryWithNestedObjectAndDictionary_ReturnsValue() + { + var target = new Dictionary + { + ["NestedObjectKey"] = new ComplexObject + { + Prop = new Dictionary + { + ["NestedDictionaryKey"] = "NestedDictionaryValue" + } + } + }; + + var metadata = PropertyMetadataRetriever.Get(target, "NestedObjectKey.Prop.NestedDictionaryKey"); + + metadata.Name.Should().Be("NestedDictionaryKey"); + metadata.Value.Should().Be("NestedDictionaryValue"); + metadata.Type.Should().Be(typeof(string)); + } + + private static void ShouldBeEmpty(PropertyMetadata metadata) + { + metadata.Name.Should().BeEmpty(); + metadata.Value.Should().Be(null); + metadata.Type.Should().Be(typeof(object)); + } + + private sealed record ComplexObject + { + public object Prop { get; set; } + + // ReSharper disable once NotAccessedField.Local + public object Field; + } +} \ No newline at end of file diff --git a/sdmap/test/sdmap.test/sdmap.test.csproj b/sdmap/test/sdmap.test/sdmap.test.csproj index 68ed873..991168a 100644 --- a/sdmap/test/sdmap.test/sdmap.test.csproj +++ b/sdmap/test/sdmap.test/sdmap.test.csproj @@ -5,6 +5,7 @@ + all